mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 12:45:28 -05:00
Compare commits
129 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2ad1ced77 | ||
|
|
e76df589e2 | ||
|
|
1390565fc6 | ||
|
|
d76a37c785 | ||
|
|
ce8a35adea | ||
|
|
9030ba1adf | ||
|
|
5b8d829bba | ||
|
|
ad03b92d2d | ||
|
|
88fc412900 | ||
|
|
c2ec6848aa | ||
|
|
01da2877b0 | ||
|
|
107d77f1b8 | ||
|
|
1030868a75 | ||
|
|
797dc2ceb1 | ||
|
|
b971bfe800 | ||
|
|
f88407b731 | ||
|
|
725e3cacb8 | ||
|
|
c9eed5d997 | ||
|
|
bf682136c3 | ||
|
|
7be2a4f544 | ||
|
|
616664e6f3 | ||
|
|
0caf8628b7 | ||
|
|
463e7caa2c | ||
|
|
0e2315807f | ||
|
|
46bf6c666f | ||
|
|
902be7759d | ||
|
|
b4a1593dc9 | ||
|
|
577cfc6ba9 | ||
|
|
f7a29dfa69 | ||
|
|
e3475cf8ee | ||
|
|
b344f75a30 | ||
|
|
338a478ead | ||
|
|
2658ced18e | ||
|
|
cd6ff4d52b | ||
|
|
5438a6a364 | ||
|
|
ea1dea8621 | ||
|
|
3a556113aa | ||
|
|
eded0367db | ||
|
|
6c90f517e4 | ||
|
|
c07c00325f | ||
|
|
0a2e2cd771 | ||
|
|
8f3b5f9dfc | ||
|
|
78499bcf5f | ||
|
|
78fa776ce5 | ||
|
|
93c6415547 | ||
|
|
12f26c29e6 | ||
|
|
e4f6d94247 | ||
|
|
a04c51397d | ||
|
|
2073ef6ce9 | ||
|
|
7dfd9ddcb5 | ||
|
|
6215c39024 | ||
|
|
1c856911e8 | ||
|
|
ed17c2ac6d | ||
|
|
1ce332a91d | ||
|
|
2aa27fc051 | ||
|
|
31b239d5dc | ||
|
|
7c3779e287 | ||
|
|
3f13c33b10 | ||
|
|
8727a65513 | ||
|
|
baf3d0e63c | ||
|
|
ea2d1e7e9d | ||
|
|
f9f26ad50b | ||
|
|
9911c41a35 | ||
|
|
d590bc11f8 | ||
|
|
31c4c1f5d7 | ||
|
|
42ff48d637 | ||
|
|
d44376e869 | ||
|
|
4b1fd9e700 | ||
|
|
d61099f9c0 | ||
|
|
c2b73a02ca | ||
|
|
49a681bcfe | ||
|
|
4cedb29123 | ||
|
|
d80b3aaf69 | ||
|
|
b1cc15386f | ||
|
|
748744d1b7 | ||
|
|
912ff98e27 | ||
|
|
a9614c5306 | ||
|
|
cb2f97f11d | ||
|
|
017ea233ec | ||
|
|
e8206173ff | ||
|
|
680f72e4f9 | ||
|
|
930e8b38d4 | ||
|
|
ee1d0136d9 | ||
|
|
20c276f4ea | ||
|
|
bea4f66eb1 | ||
|
|
c5185765ee | ||
|
|
90130c119e | ||
|
|
a7ca06d25c | ||
|
|
5a5d590c51 | ||
|
|
336b398bc3 | ||
|
|
56c8f335db | ||
|
|
b6a4d48948 | ||
|
|
774ccb7fbd | ||
|
|
eda9dde23d | ||
|
|
bbf91efd0d | ||
|
|
b022d59391 | ||
|
|
cf46a010a7 | ||
|
|
595bab0d83 | ||
|
|
6fc819d72b | ||
|
|
0553a9b2ee | ||
|
|
3caa3a4735 | ||
|
|
2469057ad6 | ||
|
|
b230ec5bc5 | ||
|
|
1669b4cb72 | ||
|
|
2841fcaadb | ||
|
|
91810d9c40 | ||
|
|
ccb5c8ba2c | ||
|
|
265daebdf3 | ||
|
|
a149a11c57 | ||
|
|
d7cc7a4c7b | ||
|
|
41866bcb24 | ||
|
|
c73a5866d7 | ||
|
|
18aaf1dd31 | ||
|
|
d523a2e674 | ||
|
|
0f7eed6257 | ||
|
|
1f5a346190 | ||
|
|
7fd2979e86 | ||
|
|
2077e1971d | ||
|
|
873e073eb6 | ||
|
|
4f3d9e3a00 | ||
|
|
c02896ea17 | ||
|
|
4bfd40219b | ||
|
|
6a3de18069 | ||
|
|
8c51b44fbb | ||
|
|
10daa38921 | ||
|
|
6e0f7ab38f | ||
|
|
86061b3cf1 | ||
|
|
c3669cae3e | ||
|
|
a116360f87 |
@@ -19,14 +19,9 @@ inputs:
|
|||||||
runs:
|
runs:
|
||||||
using: 'composite'
|
using: 'composite'
|
||||||
steps:
|
steps:
|
||||||
- name: Setup cmake
|
|
||||||
uses: jwlawson/actions-setup-cmake@v1.13
|
|
||||||
with:
|
|
||||||
cmake-version: '3.x.x'
|
|
||||||
|
|
||||||
- name: Restore cached dependencies
|
- name: Restore cached dependencies
|
||||||
id: restore-cache
|
id: restore-cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.DEP_DIR }}
|
path: ${{ env.DEP_DIR }}
|
||||||
key: ${{ env.DEP_DIR }}-${{ runner.os }}-${{ inputs.target }}
|
key: ${{ env.DEP_DIR }}-${{ runner.os }}-${{ inputs.target }}
|
||||||
|
|||||||
2
.github/actions/run-clang-format/action.yaml
vendored
2
.github/actions/run-clang-format/action.yaml
vendored
@@ -29,6 +29,7 @@ runs:
|
|||||||
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
||||||
echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH
|
echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH
|
||||||
echo "/home/linuxbrew/.linuxbrew/opt/clang-format@16/bin" >> $GITHUB_PATH
|
echo "/home/linuxbrew/.linuxbrew/opt/clang-format@16/bin" >> $GITHUB_PATH
|
||||||
|
brew update
|
||||||
brew install --quiet zsh
|
brew install --quiet zsh
|
||||||
echo ::endgroup::
|
echo ::endgroup::
|
||||||
|
|
||||||
@@ -52,6 +53,7 @@ runs:
|
|||||||
|
|
||||||
if (( ${changes[(I)(*.c|*.h|*.cpp|*.hpp|*.m|*.mm)]} )) {
|
if (( ${changes[(I)(*.c|*.h|*.cpp|*.hpp|*.m|*.mm)]} )) {
|
||||||
echo ::group::Install clang-format-16
|
echo ::group::Install clang-format-16
|
||||||
|
brew update
|
||||||
brew install --quiet obsproject/tools/clang-format@16
|
brew install --quiet obsproject/tools/clang-format@16
|
||||||
echo ::endgroup::
|
echo ::endgroup::
|
||||||
|
|
||||||
|
|||||||
1
.github/scripts/.Aptfile
vendored
1
.github/scripts/.Aptfile
vendored
@@ -8,6 +8,7 @@ package 'libcurl4-openssl-dev'
|
|||||||
package 'libxtst-dev'
|
package 'libxtst-dev'
|
||||||
package 'libxss-dev'
|
package 'libxss-dev'
|
||||||
package 'libopencv-dev'
|
package 'libopencv-dev'
|
||||||
|
package 'libopencv-contrib-dev'
|
||||||
package 'libtesseract-dev'
|
package 'libtesseract-dev'
|
||||||
package 'libproc2-dev'
|
package 'libproc2-dev'
|
||||||
package 'libusb-1.0-0-dev'
|
package 'libusb-1.0-0-dev'
|
||||||
|
|||||||
2
.github/scripts/.Wingetfile
vendored
2
.github/scripts/.Wingetfile
vendored
@@ -1,4 +1,4 @@
|
|||||||
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'
|
package 'OpenSSL', path: 'OpenSSL', bin: 'openssl', version: '3.6.2'
|
||||||
package 'Microsoft.VisualStudio.Locator', path: 'vswhere', bin: 'vswhere'
|
package 'Microsoft.VisualStudio.Locator', path: 'vswhere', bin: 'vswhere'
|
||||||
58
.github/scripts/.build-deps.zsh
vendored
58
.github/scripts/.build-deps.zsh
vendored
@@ -284,30 +284,56 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
|||||||
case ${host_os} {
|
case ${host_os} {
|
||||||
macos)
|
macos)
|
||||||
local opencv_dir="${project_root}/deps/opencv"
|
local opencv_dir="${project_root}/deps/opencv"
|
||||||
local opencv_build_dir="${opencv_dir}/build_${target##*-}"
|
local opencv_contrib_dir="${project_root}/deps/opencv_contrib"
|
||||||
|
local opencv_install_x86="${opencv_dir}/install_x86_64"
|
||||||
|
local opencv_install_arm="${opencv_dir}/install_arm64"
|
||||||
|
|
||||||
local -a opencv_cmake_args=(
|
local -a opencv_cmake_args_common=(
|
||||||
-DCMAKE_BUILD_TYPE=Release
|
-DCMAKE_BUILD_TYPE=Release
|
||||||
-DBUILD_LIST=core,imgproc,objdetect
|
-DBUILD_LIST=core,imgproc,objdetect,xobjdetect,dnn
|
||||||
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
|
-DOPENCV_EXTRA_MODULES_PATH="${opencv_contrib_dir}/modules"
|
||||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
|
||||||
-DCMAKE_PREFIX_PATH="${advss_dep_path};${_plugin_deps}"
|
-DCMAKE_PREFIX_PATH="${advss_dep_path};${_plugin_deps}"
|
||||||
-DCMAKE_INSTALL_PREFIX="${advss_dep_path}"
|
-DWITH_KLEIDICV=OFF
|
||||||
)
|
)
|
||||||
|
|
||||||
if [ "${target}" != "macos-x86_64" ]; then
|
|
||||||
opencv_cmake_args+=(-DWITH_IPP=OFF)
|
|
||||||
fi
|
|
||||||
|
|
||||||
pushd ${opencv_dir}
|
pushd ${opencv_dir}
|
||||||
log_info "Configure OpenCV ..."
|
|
||||||
cmake -S . -B ${opencv_build_dir} ${opencv_cmake_args}
|
|
||||||
|
|
||||||
log_info "Building OpenCV ..."
|
log_info "Configure OpenCV (x86_64) ..."
|
||||||
cmake --build ${opencv_build_dir} --config Release
|
cmake -S . -B build_x86_64 ${opencv_cmake_args_common} \
|
||||||
|
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
||||||
|
-DCMAKE_PROJECT_INCLUDE="${SCRIPT_HOME}/opencv-force-processor-x86_64.cmake" \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="${opencv_install_x86}" \
|
||||||
|
-DWITH_IPP=OFF
|
||||||
|
|
||||||
log_info "Installing OpenCV ..."
|
log_info "Building OpenCV (x86_64) ..."
|
||||||
cmake --install ${opencv_build_dir} --prefix "${advss_dep_path}" --config Release || true
|
cmake --build build_x86_64 --config Release
|
||||||
|
|
||||||
|
log_info "Installing OpenCV (x86_64) ..."
|
||||||
|
cmake --install build_x86_64 --prefix "${opencv_install_x86}" --config Release || true
|
||||||
|
|
||||||
|
log_info "Configure OpenCV (arm64) ..."
|
||||||
|
cmake -S . -B build_arm64 ${opencv_cmake_args_common} \
|
||||||
|
-DCMAKE_OSX_ARCHITECTURES=arm64 \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="${opencv_install_arm}"
|
||||||
|
|
||||||
|
log_info "Building OpenCV (arm64) ..."
|
||||||
|
cmake --build build_arm64 --config Release
|
||||||
|
|
||||||
|
log_info "Installing OpenCV (arm64) ..."
|
||||||
|
cmake --install build_arm64 --prefix "${opencv_install_arm}" --config Release || true
|
||||||
|
|
||||||
|
log_info "Merging OpenCV into universal binaries ..."
|
||||||
|
cp -R "${opencv_install_arm}/." "${advss_dep_path}"
|
||||||
|
for arm_lib in ${opencv_install_arm}/lib/**/*.(dylib|a)(.); do
|
||||||
|
local rel="${arm_lib#${opencv_install_arm}/}"
|
||||||
|
local x86_lib="${opencv_install_x86}/${rel}"
|
||||||
|
if [[ -f "${x86_lib}" ]]; then
|
||||||
|
lipo -create "${x86_lib}" "${arm_lib}" -output "${advss_dep_path}/${rel}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
rm -rf "${opencv_install_x86}" "${opencv_install_arm}"
|
||||||
popd
|
popd
|
||||||
|
|
||||||
local leptonica_dir="${project_root}/deps/leptonica"
|
local leptonica_dir="${project_root}/deps/leptonica"
|
||||||
@@ -319,7 +345,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
|||||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
|
||||||
-DSW_BUILD=OFF
|
-DSW_BUILD=OFF
|
||||||
-DOPENJPEG_SUPPORT=OFF
|
-DOPENJPEG_SUPPORT=OFF
|
||||||
-DLIBWEBP_SUPPORT=OFF
|
-DENABLE_WEBP=OFF
|
||||||
-DCMAKE_DISABLE_FIND_PACKAGE_GIF=TRUE
|
-DCMAKE_DISABLE_FIND_PACKAGE_GIF=TRUE
|
||||||
-DCMAKE_DISABLE_FIND_PACKAGE_JPEG=TRUE
|
-DCMAKE_DISABLE_FIND_PACKAGE_JPEG=TRUE
|
||||||
-DCMAKE_DISABLE_FIND_PACKAGE_TIFF=TRUE
|
-DCMAKE_DISABLE_FIND_PACKAGE_TIFF=TRUE
|
||||||
|
|||||||
2
.github/scripts/.package.zsh
vendored
2
.github/scripts/.package.zsh
vendored
@@ -259,7 +259,7 @@ ${_usage_host:-}"
|
|||||||
cmake --build build_${target##*-} --config ${config} -t package ${cmake_args}
|
cmake --build build_${target##*-} --config ${config} -t package ${cmake_args}
|
||||||
|
|
||||||
# Mark certain deps as optional
|
# Mark certain deps as optional
|
||||||
build-aux/CI/linux/demote-deps.sh ${project_root}/release/*.deb Recommends '(mqtt)|(opencv)|(tesseract)|(usb)|(x11)'
|
build-aux/CI/linux/demote-deps.sh ${project_root}/release/*.deb Recommends '(mqtt)|(opencv)|(tesseract)|(usb)|(x11)|(libobs)'
|
||||||
|
|
||||||
if [ ! -e ${project_root}/release/${output_name}.deb ]; then
|
if [ ! -e ${project_root}/release/${output_name}.deb ]; then
|
||||||
mv ${project_root}/release/*.deb ${project_root}/release/${output_name}.deb
|
mv ${project_root}/release/*.deb ${project_root}/release/${output_name}.deb
|
||||||
|
|||||||
4
.github/scripts/Build-Deps-Windows.ps1
vendored
4
.github/scripts/Build-Deps-Windows.ps1
vendored
@@ -106,6 +106,7 @@ function Build {
|
|||||||
$ADVSSDepPath = "$(Resolve-Path -Path ${ProjectRoot}/${OutDirName})"
|
$ADVSSDepPath = "$(Resolve-Path -Path ${ProjectRoot}/${OutDirName})"
|
||||||
|
|
||||||
$OpenCVPath = "${ProjectRoot}/deps/opencv"
|
$OpenCVPath = "${ProjectRoot}/deps/opencv"
|
||||||
|
$OpenCVContribPath = "${ProjectRoot}/deps/opencv_contrib"
|
||||||
$OpenCVBuildPath = "${OpenCVPath}/build"
|
$OpenCVBuildPath = "${OpenCVPath}/build"
|
||||||
|
|
||||||
Push-Location -Stack BuildOpenCVTemp
|
Push-Location -Stack BuildOpenCVTemp
|
||||||
@@ -115,7 +116,8 @@ function Build {
|
|||||||
"-DCMAKE_BUILD_TYPE=Release"
|
"-DCMAKE_BUILD_TYPE=Release"
|
||||||
"-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}"
|
"-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}"
|
||||||
"-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}"
|
"-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}"
|
||||||
"-DBUILD_LIST=core,imgproc,objdetect"
|
"-DBUILD_LIST=core,imgproc,objdetect,xobjdetect"
|
||||||
|
"-DOPENCV_EXTRA_MODULES_PATH:PATH=${OpenCVContribPath}/modules"
|
||||||
)
|
)
|
||||||
|
|
||||||
Log-Information "Configuring OpenCV..."
|
Log-Information "Configuring OpenCV..."
|
||||||
|
|||||||
73
.github/scripts/Package-Windows.ps1
vendored
73
.github/scripts/Package-Windows.ps1
vendored
@@ -68,14 +68,53 @@ function Package {
|
|||||||
|
|
||||||
Remove-Item @RemoveArgs
|
Remove-Item @RemoveArgs
|
||||||
|
|
||||||
Log-Group "Archiving ${ProductName}..."
|
$ReleasePath = "${ProjectRoot}/release/${Configuration}"
|
||||||
$CompressArgs = @{
|
$NewBinPath = "${ReleasePath}/${ProductName}/bin/64bit"
|
||||||
Path = (Get-ChildItem -Path "${ProjectRoot}/release/${Configuration}" -Exclude "${OutputName}*.*")
|
$NewDataPath = "${ReleasePath}/${ProductName}/data"
|
||||||
CompressionLevel = 'Optimal'
|
$CIWindowsDir = "${ProjectRoot}/build-aux/CI/windows"
|
||||||
DestinationPath = "${ProjectRoot}/release/${OutputName}.zip"
|
|
||||||
Verbose = ($Env:CI -ne $null)
|
# --- Recommended zip (new layout, extract to %ProgramData%\obs-studio\) ---
|
||||||
|
Log-Group "Archiving ${ProductName} (recommended)..."
|
||||||
|
$RecStaging = "${ProjectRoot}/release/zip-staging-rec"
|
||||||
|
Remove-Item -Path $RecStaging -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
New-Item -ItemType Directory -Force -Path $RecStaging | Out-Null
|
||||||
|
Copy-Item -Path "${CIWindowsDir}/README.txt" -Destination "${RecStaging}/README.txt"
|
||||||
|
if ( Test-Path -Path $NewBinPath ) {
|
||||||
|
$RecBinPath = "${RecStaging}/${ProductName}/bin/64bit"
|
||||||
|
New-Item -ItemType Directory -Force -Path $RecBinPath | Out-Null
|
||||||
|
Copy-Item -Path "${NewBinPath}/*" -Destination $RecBinPath -Recurse -Force
|
||||||
}
|
}
|
||||||
Compress-Archive -Force @CompressArgs
|
if ( Test-Path -Path $NewDataPath ) {
|
||||||
|
$RecDataPath = "${RecStaging}/${ProductName}/data"
|
||||||
|
New-Item -ItemType Directory -Force -Path $RecDataPath | Out-Null
|
||||||
|
Copy-Item -Path "${NewDataPath}/*" -Destination $RecDataPath -Recurse -Force
|
||||||
|
}
|
||||||
|
Compress-Archive -Force -Path (Get-ChildItem -Path $RecStaging) `
|
||||||
|
-CompressionLevel Optimal `
|
||||||
|
-DestinationPath "${ProjectRoot}/release/${OutputName}.zip"
|
||||||
|
Remove-Item -Path $RecStaging -Recurse -Force
|
||||||
|
Log-Group
|
||||||
|
|
||||||
|
# --- Legacy zip (old layout, extract to OBS install directory) ---
|
||||||
|
Log-Group "Archiving ${ProductName} (portable)..."
|
||||||
|
$PortableStaging = "${ProjectRoot}/release/zip-staging-portable"
|
||||||
|
Remove-Item -Path $PortableStaging -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
New-Item -ItemType Directory -Force -Path $PortableStaging | Out-Null
|
||||||
|
Copy-Item -Path "${CIWindowsDir}/README-portable.txt" -Destination "${PortableStaging}/README.txt"
|
||||||
|
if ( Test-Path -Path $NewBinPath ) {
|
||||||
|
$PortableBinPath = "${PortableStaging}/obs-plugins/64bit"
|
||||||
|
New-Item -ItemType Directory -Force -Path $PortableBinPath | Out-Null
|
||||||
|
Copy-Item -Path "${NewBinPath}/*" -Destination $PortableBinPath -Recurse -Force
|
||||||
|
}
|
||||||
|
if ( Test-Path -Path $NewDataPath ) {
|
||||||
|
$PortableDataPath = "${PortableStaging}/data/obs-plugins/${ProductName}"
|
||||||
|
New-Item -ItemType Directory -Force -Path $PortableDataPath | Out-Null
|
||||||
|
Copy-Item -Path "${NewDataPath}/*" -Destination $PortableDataPath -Recurse -Force
|
||||||
|
}
|
||||||
|
Compress-Archive -Force -Path (Get-ChildItem -Path $PortableStaging) `
|
||||||
|
-CompressionLevel Optimal `
|
||||||
|
-DestinationPath "${ProjectRoot}/release/${OutputName}-portable.zip"
|
||||||
|
Remove-Item -Path $PortableStaging -Recurse -Force
|
||||||
Log-Group
|
Log-Group
|
||||||
|
|
||||||
if ( ( $BuildInstaller ) ) {
|
if ( ( $BuildInstaller ) ) {
|
||||||
@@ -89,7 +128,25 @@ function Package {
|
|||||||
Log-Information 'Creating InnoSetup installer...'
|
Log-Information 'Creating InnoSetup installer...'
|
||||||
Push-Location -Stack BuildTemp
|
Push-Location -Stack BuildTemp
|
||||||
Ensure-Location -Path "${ProjectRoot}/release"
|
Ensure-Location -Path "${ProjectRoot}/release"
|
||||||
Copy-Item -Path ${Configuration} -Destination Package -Recurse
|
Remove-Item -Path Package -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Recommended layout (for %ProgramData%\obs-studio\plugins\)
|
||||||
|
$PkgRec = "Package/recommended"
|
||||||
|
New-Item -ItemType Directory -Force -Path $PkgRec | Out-Null
|
||||||
|
Copy-Item -Path "${Configuration}/*" -Destination $PkgRec -Recurse -Force
|
||||||
|
|
||||||
|
# Legacy layout (for OBS installation directory)
|
||||||
|
if ( Test-Path "${Configuration}/${ProductName}/bin/64bit" ) {
|
||||||
|
$PkgLegBin = "Package/portable/obs-plugins/64bit"
|
||||||
|
New-Item -ItemType Directory -Force -Path $PkgLegBin | Out-Null
|
||||||
|
Copy-Item -Path "${Configuration}/${ProductName}/bin/64bit/*" -Destination $PkgLegBin -Recurse -Force
|
||||||
|
}
|
||||||
|
if ( Test-Path "${Configuration}/${ProductName}/data" ) {
|
||||||
|
$PkgLegData = "Package/portable/data/obs-plugins/${ProductName}"
|
||||||
|
New-Item -ItemType Directory -Force -Path $PkgLegData | Out-Null
|
||||||
|
Copy-Item -Path "${Configuration}/${ProductName}/data/*" -Destination $PkgLegData -Recurse -Force
|
||||||
|
}
|
||||||
|
|
||||||
Invoke-External iscc ${IsccFile} /O"${ProjectRoot}/release" /F"${OutputName}-Installer"
|
Invoke-External iscc ${IsccFile} /O"${ProjectRoot}/release" /F"${OutputName}-Installer"
|
||||||
Remove-Item -Path Package -Recurse
|
Remove-Item -Path Package -Recurse
|
||||||
Pop-Location -Stack BuildTemp
|
Pop-Location -Stack BuildTemp
|
||||||
|
|||||||
7
.github/scripts/opencv-force-processor-x86_64.cmake
vendored
Normal file
7
.github/scripts/opencv-force-processor-x86_64.cmake
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Injected via CMAKE_PROJECT_INCLUDE after opencv's project() call. Forces
|
||||||
|
# CMAKE_SYSTEM_PROCESSOR into the cache so that third-party subdirectories
|
||||||
|
# (mlas) that read the cache directly see x86_64 instead of the arm64 host
|
||||||
|
# processor on Apple Silicon CI runners.
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR
|
||||||
|
x86_64
|
||||||
|
CACHE INTERNAL "" FORCE)
|
||||||
1
.github/scripts/utils.zsh/check_macos
vendored
1
.github/scripts/utils.zsh/check_macos
vendored
@@ -18,4 +18,5 @@ if (( ! ${+commands[brew]} )) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
brew bundle --file ${SCRIPT_HOME}/.Brewfile
|
brew bundle --file ${SCRIPT_HOME}/.Brewfile
|
||||||
|
rehash
|
||||||
log_group
|
log_group
|
||||||
|
|||||||
21
.github/scripts/utils.zsh/setup_linux
vendored
21
.github/scripts/utils.zsh/setup_linux
vendored
@@ -35,14 +35,21 @@ if (( ! (${skips[(Ie)all]} + ${skips[(Ie)deps]}) )) {
|
|||||||
sudo apt-get install ${apt_args} gcc-${${target##*-}//_/-}-linux-gnu g++-${${target##*-}//_/-}-linux-gnu
|
sudo apt-get install ${apt_args} gcc-${${target##*-}//_/-}-linux-gnu g++-${${target##*-}//_/-}-linux-gnu
|
||||||
}
|
}
|
||||||
|
|
||||||
sudo add-apt-repository --yes ppa:obsproject/obs-studio
|
local dist_version
|
||||||
sudo apt update
|
read -r dist_version <<< "$(source /etc/os-release; print "${VERSION_ID}")"
|
||||||
|
|
||||||
sudo apt-get install ${apt_args} \
|
# The OBS PPA may not yet support newer Ubuntu versions.
|
||||||
build-essential \
|
# On those, use the native obs-studio packages and install libobs-dev separately,
|
||||||
libgles2-mesa-dev \
|
# as it is not bundled in obs-studio itself on newer Ubuntu releases.
|
||||||
libsimde-dev \
|
local -a obs_packages=(build-essential libgles2-mesa-dev libsimde-dev obs-studio)
|
||||||
obs-studio
|
if is-at-least 26.04 ${dist_version}; then
|
||||||
|
obs_packages+=(libobs-dev)
|
||||||
|
else
|
||||||
|
sudo add-apt-repository --yes ppa:obsproject/obs-studio
|
||||||
|
sudo apt update
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo apt-get install ${apt_args} ${obs_packages}
|
||||||
|
|
||||||
local -a _qt_packages=()
|
local -a _qt_packages=()
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/build-debian.yml
vendored
4
.github/workflows/build-debian.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
- name: check_libobs_revision
|
- name: check_libobs_revision
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
mv ../*.deb .
|
mv ../*.deb .
|
||||||
- name: Publish
|
- name: Publish
|
||||||
if: success()
|
if: success()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: "obs-scene-switcher.deb"
|
name: "obs-scene-switcher.deb"
|
||||||
path: ${{ github.workspace }}/*.deb
|
path: ${{ github.workspace }}/*.deb
|
||||||
|
|||||||
138
.github/workflows/build-project.yaml
vendored
138
.github/workflows/build-project.yaml
vendored
@@ -6,7 +6,7 @@ on:
|
|||||||
description: "Project name detected by parsing build spec file"
|
description: "Project name detected by parsing build spec file"
|
||||||
value: ${{ jobs.check-event.outputs.pluginName }}
|
value: ${{ jobs.check-event.outputs.pluginName }}
|
||||||
env:
|
env:
|
||||||
DEP_DIR: .deps/advss-build-dependencies-4
|
DEP_DIR: .deps/advss-build-dependencies-6
|
||||||
jobs:
|
jobs:
|
||||||
check-event:
|
check-event:
|
||||||
name: Check GitHub Event Data 🔎
|
name: Check GitHub Event Data 🔎
|
||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
commitHash: ${{ steps.setup.outputs.commitHash }}
|
commitHash: ${{ steps.setup.outputs.commitHash }}
|
||||||
pluginName: ${{ steps.setup.outputs.pluginName }}
|
pluginName: ${{ steps.setup.outputs.pluginName }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Check Event Data ☑️
|
- name: Check Event Data ☑️
|
||||||
@@ -81,7 +81,7 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
shell: zsh --no-rcs --errexit --pipefail {0}
|
shell: zsh --no-rcs --errexit --pipefail {0}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -111,7 +111,7 @@ jobs:
|
|||||||
sudo xcode-select --switch /Applications/Xcode_16.1.0.app/Contents/Developer
|
sudo xcode-select --switch /Applications/Xcode_16.1.0.app/Contents/Developer
|
||||||
print '::endgroup::'
|
print '::endgroup::'
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v5
|
||||||
id: ccache-cache
|
id: ccache-cache
|
||||||
with:
|
with:
|
||||||
path: ${{ github.workspace }}/.ccache
|
path: ${{ github.workspace }}/.ccache
|
||||||
@@ -163,13 +163,13 @@ jobs:
|
|||||||
codesignPass: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
|
codesignPass: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
|
||||||
|
|
||||||
- name: Upload Artifacts 📡
|
- name: Upload Artifacts 📡
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-macos-universal-${{ needs.check-event.outputs.commitHash }}
|
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-macos-universal-${{ needs.check-event.outputs.commitHash }}
|
||||||
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-macos-universal.*
|
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-macos-universal.*
|
||||||
|
|
||||||
- name: Upload Debug Symbol Artifacts 🪲
|
- name: Upload Debug Symbol Artifacts 🪲
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
if: ${{ needs.check-event.outputs.config == 'Release' }}
|
if: ${{ needs.check-event.outputs.config == 'Release' }}
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-macos-universal-${{ needs.check-event.outputs.commitHash }}-dSYMs
|
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-macos-universal-${{ needs.check-event.outputs.commitHash }}-dSYMs
|
||||||
@@ -183,7 +183,7 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -202,7 +202,7 @@ jobs:
|
|||||||
echo "pluginName=${product_name}" >> $GITHUB_OUTPUT
|
echo "pluginName=${product_name}" >> $GITHUB_OUTPUT
|
||||||
echo "pluginVersion=${git_tag}" >> $GITHUB_OUTPUT
|
echo "pluginVersion=${git_tag}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v5
|
||||||
id: ccache-cache
|
id: ccache-cache
|
||||||
with:
|
with:
|
||||||
path: ${{ github.workspace }}/.ccache
|
path: ${{ github.workspace }}/.ccache
|
||||||
@@ -210,13 +210,19 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-ccache-x86_64-
|
${{ runner.os }}-ccache-x86_64-
|
||||||
|
|
||||||
- name: Set up CMake 🏗️
|
|
||||||
uses: jwlawson/actions-setup-cmake@v1.13
|
|
||||||
with:
|
|
||||||
cmake-version: '3.x.x'
|
|
||||||
|
|
||||||
- name: Set up Homebrew 🍺
|
- name: Set up Homebrew 🍺
|
||||||
uses: Homebrew/actions/setup-homebrew@master
|
uses: Homebrew/actions/setup-homebrew@main
|
||||||
|
|
||||||
|
- name: Install Vulkan SDK 🌋
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc \
|
||||||
|
| sudo tee /etc/apt/trusted.gpg.d/lunarg.asc
|
||||||
|
sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-noble.list \
|
||||||
|
https://packages.lunarg.com/vulkan/lunarg-vulkan-noble.list
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y vulkan-sdk
|
||||||
|
echo "VULKAN_SDK=/usr" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Build Plugin 🧱
|
- name: Build Plugin 🧱
|
||||||
uses: ./.github/actions/build-plugin
|
uses: ./.github/actions/build-plugin
|
||||||
@@ -238,23 +244,111 @@ jobs:
|
|||||||
config: ${{ needs.check-event.outputs.config }}
|
config: ${{ needs.check-event.outputs.config }}
|
||||||
|
|
||||||
- name: Upload Source Tarball 🗜️
|
- name: Upload Source Tarball 🗜️
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-sources-${{ needs.check-event.outputs.commitHash }}
|
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-sources-${{ needs.check-event.outputs.commitHash }}
|
||||||
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-source.*
|
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-source.*
|
||||||
|
|
||||||
|
- name: Rename artifacts for Ubuntu 24 🏷️
|
||||||
|
run: |
|
||||||
|
: Rename artifacts for Ubuntu 24 🏷️
|
||||||
|
for f in ${{ github.workspace }}/release/*-x86_64-linux-gnu.*; do
|
||||||
|
[ -e "${f}" ] || continue
|
||||||
|
mv "${f}" "${f/x86_64-linux-gnu/x86_64-ubuntu24.04-linux-gnu}"
|
||||||
|
done
|
||||||
|
|
||||||
- name: Upload Artifacts 📡
|
- name: Upload Artifacts 📡
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-24.04-x86_64-${{ needs.check-event.outputs.commitHash }}
|
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-24.04-x86_64-${{ needs.check-event.outputs.commitHash }}
|
||||||
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-x86_64*.*
|
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-x86_64*.*
|
||||||
|
|
||||||
- name: Upload debug symbol artifacts 🪲
|
- name: Upload debug symbol artifacts 🪲
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
if: ${{ fromJSON(needs.check-event.outputs.package) }}
|
if: ${{ fromJSON(needs.check-event.outputs.package) }}
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-24.04-x86_64-${{ needs.check-event.outputs.commitHash }}-dbgsym
|
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-24.04-x86_64-${{ needs.check-event.outputs.commitHash }}-dbgsym
|
||||||
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-x86_64*-dbgsym.ddeb
|
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-x86_64*-dbgsym.ddeb
|
||||||
|
if-no-files-found: ignore
|
||||||
|
|
||||||
|
ubuntu-26-build:
|
||||||
|
name: Build for Ubuntu 26 🐧
|
||||||
|
runs-on: ubuntu-26.04
|
||||||
|
needs: check-event
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set Up Environment 🔧
|
||||||
|
id: setup
|
||||||
|
run: |
|
||||||
|
: Set Up Environment 🔧
|
||||||
|
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
|
||||||
|
|
||||||
|
git_tag="$(git describe --tags)"
|
||||||
|
|
||||||
|
read -r product_name product_version <<< \
|
||||||
|
"$(jq -r '. | {name, version} | join(" ")' buildspec.json)"
|
||||||
|
|
||||||
|
echo "pluginName=${product_name}" >> $GITHUB_OUTPUT
|
||||||
|
echo "pluginVersion=${git_tag}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- uses: actions/cache@v5
|
||||||
|
id: ccache-cache
|
||||||
|
with:
|
||||||
|
path: ${{ github.workspace }}/.ccache
|
||||||
|
key: ${{ runner.os }}-ccache-x86_64-${{ needs.check-event.outputs.config }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-ccache-x86_64-
|
||||||
|
|
||||||
|
- name: Set up Homebrew 🍺
|
||||||
|
uses: Homebrew/actions/setup-homebrew@main
|
||||||
|
|
||||||
|
- name: Build Plugin 🧱
|
||||||
|
uses: ./.github/actions/build-plugin
|
||||||
|
with:
|
||||||
|
target: x86_64
|
||||||
|
config: ${{ needs.check-event.outputs.config }}
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
uses: ./.github/actions/run-tests
|
||||||
|
with:
|
||||||
|
target: x86_64
|
||||||
|
config: ${{ needs.check-event.outputs.config }}
|
||||||
|
|
||||||
|
- name: Package Plugin 📀
|
||||||
|
uses: ./.github/actions/package-plugin
|
||||||
|
with:
|
||||||
|
package: ${{ fromJSON(needs.check-event.outputs.package) }}
|
||||||
|
target: x86_64
|
||||||
|
config: ${{ needs.check-event.outputs.config }}
|
||||||
|
|
||||||
|
- name: Rename artifacts for Ubuntu 26 🏷️
|
||||||
|
run: |
|
||||||
|
: Rename artifacts for Ubuntu 26 🏷️
|
||||||
|
for f in ${{ github.workspace }}/release/*-x86_64-linux-gnu.*; do
|
||||||
|
[ -e "${f}" ] || continue
|
||||||
|
mv "${f}" "${f/x86_64-linux-gnu/x86_64-ubuntu26.04-linux-gnu}"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Upload Artifacts 📡
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-26.04-x86_64-${{ needs.check-event.outputs.commitHash }}
|
||||||
|
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-x86_64*.*
|
||||||
|
|
||||||
|
- name: Upload debug symbol artifacts 🪲
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
if: ${{ fromJSON(needs.check-event.outputs.package) }}
|
||||||
|
with:
|
||||||
|
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-26.04-x86_64-${{ needs.check-event.outputs.commitHash }}-dbgsym
|
||||||
|
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-x86_64*-dbgsym.ddeb
|
||||||
|
if-no-files-found: ignore
|
||||||
|
|
||||||
windows-build:
|
windows-build:
|
||||||
name: Build for Windows 🪟
|
name: Build for Windows 🪟
|
||||||
@@ -264,7 +358,7 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -291,6 +385,12 @@ jobs:
|
|||||||
target: x64
|
target: x64
|
||||||
config: ${{ needs.check-event.outputs.config }}
|
config: ${{ needs.check-event.outputs.config }}
|
||||||
|
|
||||||
|
- name: Install Vulkan SDK 🌋
|
||||||
|
uses: jakoch/install-vulkan-sdk-action@v1
|
||||||
|
with:
|
||||||
|
install_runtime: false
|
||||||
|
cache: true
|
||||||
|
|
||||||
- name: Build Plugin 🧱
|
- name: Build Plugin 🧱
|
||||||
uses: ./.github/actions/build-plugin
|
uses: ./.github/actions/build-plugin
|
||||||
with:
|
with:
|
||||||
@@ -311,7 +411,7 @@ jobs:
|
|||||||
package: ${{ fromJSON(needs.check-event.outputs.package) }}
|
package: ${{ fromJSON(needs.check-event.outputs.package) }}
|
||||||
|
|
||||||
- name: Upload Artifacts 📡
|
- name: Upload Artifacts 📡
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-windows-x64-${{ needs.check-event.outputs.commitHash }}
|
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-windows-x64-${{ needs.check-event.outputs.commitHash }}
|
||||||
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-windows-x64*.*
|
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-windows-x64*.*
|
||||||
|
|||||||
4
.github/workflows/check-format.yaml
vendored
4
.github/workflows/check-format.yaml
vendored
@@ -5,7 +5,7 @@ jobs:
|
|||||||
clang-format:
|
clang-format:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: clang-format check 🐉
|
- name: clang-format check 🐉
|
||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
cmake-format:
|
cmake-format:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: cmake-format check 🎛️
|
- name: cmake-format check 🎛️
|
||||||
|
|||||||
2
.github/workflows/locale-check.yml
vendored
2
.github/workflows/locale-check.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Check locale files
|
- name: Check locale files
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
5
.github/workflows/push.yaml
vendored
5
.github/workflows/push.yaml
vendored
@@ -59,7 +59,7 @@ jobs:
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
- name: Download Build Artifacts 📥
|
- name: Download Build Artifacts 📥
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
if: fromJSON(steps.check.outputs.validTag)
|
if: fromJSON(steps.check.outputs.validTag)
|
||||||
id: download
|
id: download
|
||||||
|
|
||||||
@@ -78,6 +78,7 @@ jobs:
|
|||||||
'windows-x64;zip|exe'
|
'windows-x64;zip|exe'
|
||||||
'macos-universal;tar.xz|pkg'
|
'macos-universal;tar.xz|pkg'
|
||||||
'ubuntu-24.04-x86_64;tar.xz|deb|ddeb'
|
'ubuntu-24.04-x86_64;tar.xz|deb|ddeb'
|
||||||
|
'ubuntu-26.04-x86_64;tar.xz|deb|ddeb'
|
||||||
'sources;tar.xz'
|
'sources;tar.xz'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -106,7 +107,7 @@ jobs:
|
|||||||
- name: Create Release 🛫
|
- name: Create Release 🛫
|
||||||
if: fromJSON(steps.check.outputs.validTag)
|
if: fromJSON(steps.check.outputs.validTag)
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: softprops/action-gh-release@d4e8205d7e959a9107da6396278b2f1f07af0f9b
|
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: ${{ fromJSON(steps.check.outputs.prerelease) }}
|
prerelease: ${{ fromJSON(steps.check.outputs.prerelease) }}
|
||||||
|
|||||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@@ -28,12 +28,15 @@
|
|||||||
[submodule "deps/libusb"]
|
[submodule "deps/libusb"]
|
||||||
path = deps/libusb
|
path = deps/libusb
|
||||||
url = https://github.com/libusb/libusb.git
|
url = https://github.com/libusb/libusb.git
|
||||||
[submodule "deps/date"]
|
|
||||||
path = deps/date
|
|
||||||
url = https://github.com/HowardHinnant/date.git
|
|
||||||
[submodule "deps/jsoncons"]
|
[submodule "deps/jsoncons"]
|
||||||
path = deps/jsoncons
|
path = deps/jsoncons
|
||||||
url = https://github.com/danielaparker/jsoncons.git
|
url = https://github.com/danielaparker/jsoncons.git
|
||||||
[submodule "deps/paho.mqtt.cpp"]
|
[submodule "deps/paho.mqtt.cpp"]
|
||||||
path = deps/paho.mqtt.cpp
|
path = deps/paho.mqtt.cpp
|
||||||
url = https://github.com/eclipse-paho/paho.mqtt.cpp.git
|
url = https://github.com/eclipse-paho/paho.mqtt.cpp.git
|
||||||
|
[submodule "deps/opencv_contrib"]
|
||||||
|
path = deps/opencv_contrib
|
||||||
|
url = https://github.com/opencv/opencv_contrib.git
|
||||||
|
[submodule "deps/whisper.cpp"]
|
||||||
|
path = deps/whisper.cpp
|
||||||
|
url = https://github.com/ggml-org/whisper.cpp.git
|
||||||
|
|||||||
@@ -16,6 +16,14 @@ if(BUILD_OUT_OF_TREE)
|
|||||||
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/bootstrap.cmake"
|
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/bootstrap.cmake"
|
||||||
NO_POLICY_SCOPE)
|
NO_POLICY_SCOPE)
|
||||||
include(compilerconfig)
|
include(compilerconfig)
|
||||||
|
# OPENSSL_USE_STATIC_LIBS here ensures FindOpenSSL picks up the static libs
|
||||||
|
# preventing DLL name collisions with other plugins (e.g.NVIDIA AR SDK).
|
||||||
|
if(OS_WINDOWS)
|
||||||
|
set(OPENSSL_USE_STATIC_LIBS
|
||||||
|
ON
|
||||||
|
CACHE BOOL "Use static OpenSSL" FORCE)
|
||||||
|
include(cmake/windows/wingetssl.cmake)
|
||||||
|
endif()
|
||||||
include(defaults)
|
include(defaults)
|
||||||
include(helpers)
|
include(helpers)
|
||||||
endif()
|
endif()
|
||||||
@@ -34,11 +42,6 @@ include(cmake/common/get_git_revision_description.cmake)
|
|||||||
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
|
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
|
||||||
git_describe(GIT_TAG)
|
git_describe(GIT_TAG)
|
||||||
|
|
||||||
# Helper for OpenSSL
|
|
||||||
if(OS_WINDOWS)
|
|
||||||
include(cmake/windows/wingetssl.cmake)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(${GIT_TAG} STREQUAL "GIT-NOTFOUND")
|
if(${GIT_TAG} STREQUAL "GIT-NOTFOUND")
|
||||||
set(GIT_TAG ${PROJECT_VERSION})
|
set(GIT_TAG ${PROJECT_VERSION})
|
||||||
endif()
|
endif()
|
||||||
@@ -104,6 +107,8 @@ target_sources(
|
|||||||
lib/macro/macro-action-edit.hpp
|
lib/macro/macro-action-edit.hpp
|
||||||
lib/macro/macro-action-factory.cpp
|
lib/macro/macro-action-factory.cpp
|
||||||
lib/macro/macro-action-factory.hpp
|
lib/macro/macro-action-factory.hpp
|
||||||
|
lib/macro/macro-action-loop.cpp
|
||||||
|
lib/macro/macro-action-loop.hpp
|
||||||
lib/macro/macro-action-macro.cpp
|
lib/macro/macro-action-macro.cpp
|
||||||
lib/macro/macro-action-macro.hpp
|
lib/macro/macro-action-macro.hpp
|
||||||
lib/macro/macro-action-queue.cpp
|
lib/macro/macro-action-queue.cpp
|
||||||
@@ -134,6 +139,9 @@ target_sources(
|
|||||||
lib/macro/macro-dock-window.hpp
|
lib/macro/macro-dock-window.hpp
|
||||||
lib/macro/macro-edit.cpp
|
lib/macro/macro-edit.cpp
|
||||||
lib/macro/macro-edit.hpp
|
lib/macro/macro-edit.hpp
|
||||||
|
lib/macro/macro-export-builtin-extensions.cpp
|
||||||
|
lib/macro/macro-export-extensions.cpp
|
||||||
|
lib/macro/macro-export-extensions.hpp
|
||||||
lib/macro/macro-export-import-dialog.cpp
|
lib/macro/macro-export-import-dialog.cpp
|
||||||
lib/macro/macro-export-import-dialog.hpp
|
lib/macro/macro-export-import-dialog.hpp
|
||||||
lib/macro/macro-helpers.cpp
|
lib/macro/macro-helpers.cpp
|
||||||
@@ -206,6 +214,13 @@ target_sources(
|
|||||||
lib/utils/filter-combo-box.hpp
|
lib/utils/filter-combo-box.hpp
|
||||||
lib/utils/first-run-wizard.cpp
|
lib/utils/first-run-wizard.cpp
|
||||||
lib/utils/first-run-wizard.hpp
|
lib/utils/first-run-wizard.hpp
|
||||||
|
lib/utils/first-run-wizard-audio.cpp
|
||||||
|
lib/utils/first-run-wizard-audio.hpp
|
||||||
|
lib/utils/first-run-wizard-helpers.hpp
|
||||||
|
lib/utils/first-run-wizard-sequence.cpp
|
||||||
|
lib/utils/first-run-wizard-sequence.hpp
|
||||||
|
lib/utils/first-run-wizard-window.cpp
|
||||||
|
lib/utils/first-run-wizard-window.hpp
|
||||||
lib/utils/help-icon.hpp
|
lib/utils/help-icon.hpp
|
||||||
lib/utils/help-icon.cpp
|
lib/utils/help-icon.cpp
|
||||||
lib/utils/item-selection-helpers.cpp
|
lib/utils/item-selection-helpers.cpp
|
||||||
@@ -288,6 +303,10 @@ target_sources(
|
|||||||
lib/utils/volume-control.hpp
|
lib/utils/volume-control.hpp
|
||||||
lib/utils/websocket-api.cpp
|
lib/utils/websocket-api.cpp
|
||||||
lib/utils/websocket-api.hpp
|
lib/utils/websocket-api.hpp
|
||||||
|
lib/variables/variable-color-button.cpp
|
||||||
|
lib/variables/variable-color-button.hpp
|
||||||
|
lib/variables/variable-color.cpp
|
||||||
|
lib/variables/variable-color.hpp
|
||||||
lib/variables/variable-line-edit.cpp
|
lib/variables/variable-line-edit.cpp
|
||||||
lib/variables/variable-line-edit.hpp
|
lib/variables/variable-line-edit.hpp
|
||||||
lib/variables/variable-number.hpp
|
lib/variables/variable-number.hpp
|
||||||
@@ -360,6 +379,9 @@ set_target_properties(${LIB_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden)
|
|||||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||||
target_compile_features(${LIB_NAME} PUBLIC cxx_std_17)
|
target_compile_features(${LIB_NAME} PUBLIC cxx_std_17)
|
||||||
|
|
||||||
|
target_precompile_headers(${LIB_NAME} PRIVATE
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/pch.hpp")
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
${LIB_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/deps/obs-websocket/lib"
|
${LIB_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/deps/obs-websocket/lib"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/exprtk")
|
"${CMAKE_CURRENT_SOURCE_DIR}/deps/exprtk")
|
||||||
@@ -504,7 +526,11 @@ endif()
|
|||||||
# --- End of section ---
|
# --- End of section ---
|
||||||
|
|
||||||
add_subdirectory(plugins)
|
add_subdirectory(plugins)
|
||||||
add_subdirectory(tests)
|
|
||||||
|
option(ADVSS_ENABLE_TESTS "Build advanced-scene-switcher unit tests" OFF)
|
||||||
|
if(ADVSS_ENABLE_TESTS)
|
||||||
|
add_subdirectory(tests)
|
||||||
|
endif()
|
||||||
|
|
||||||
# --- Install ---
|
# --- Install ---
|
||||||
|
|
||||||
@@ -533,3 +559,59 @@ else()
|
|||||||
set_target_properties_obs(${PROJECT_NAME} PROPERTIES PREFIX "")
|
set_target_properties_obs(${PROJECT_NAME} PROPERTIES PREFIX "")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# --- Install third-party dependency licenses ---
|
||||||
|
#
|
||||||
|
# Each entry is "display-name:path/to/license/file" relative to deps/. Files are
|
||||||
|
# silently skipped when a submodule is not checked out.
|
||||||
|
set(_dep_license_files
|
||||||
|
"asio:asio/asio/LICENSE_1_0.txt"
|
||||||
|
"cpp-httplib:cpp-httplib/LICENSE"
|
||||||
|
"json:json/LICENSE.MIT"
|
||||||
|
"jsoncons:jsoncons/LICENSE"
|
||||||
|
"leptonica:leptonica/leptonica-license.txt"
|
||||||
|
"libremidi:libremidi/LICENSE.md"
|
||||||
|
"libusb:libusb/COPYING"
|
||||||
|
"opencv:opencv/LICENSE"
|
||||||
|
"openvr:openvr/LICENSE"
|
||||||
|
"paho-mqtt:paho.mqtt.cpp/LICENSE"
|
||||||
|
"tesseract:tesseract/LICENSE"
|
||||||
|
"websocketpp:websocketpp/COPYING"
|
||||||
|
"whisper:whisper.cpp/LICENSE")
|
||||||
|
|
||||||
|
if(DEB_INSTALL)
|
||||||
|
if(NOT DATA_OUT_DIR)
|
||||||
|
set(DATA_OUT_DIR "/share/obs/obs-plugins/${PROJECT_NAME}")
|
||||||
|
endif()
|
||||||
|
set(_license_dest "${CMAKE_INSTALL_PREFIX}/${DATA_OUT_DIR}/licenses")
|
||||||
|
elseif(OS_MACOS)
|
||||||
|
# macOS: license files become bundle Resources/licenses/* entries
|
||||||
|
set(_license_dest "")
|
||||||
|
else()
|
||||||
|
# Windows and non-DEB Linux: alongside the plugin's data directory
|
||||||
|
set(_license_dest "${CMAKE_PROJECT_NAME}/data/licenses")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
foreach(_entry IN LISTS _dep_license_files)
|
||||||
|
string(REGEX MATCH "^([^:]+):(.+)$" _m "${_entry}")
|
||||||
|
set(_dep_name "${CMAKE_MATCH_1}")
|
||||||
|
set(_dep_file "${CMAKE_CURRENT_SOURCE_DIR}/deps/${CMAKE_MATCH_2}")
|
||||||
|
|
||||||
|
if(NOT EXISTS "${_dep_file}")
|
||||||
|
continue()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(OS_MACOS)
|
||||||
|
# Copy to binary dir so we can give the file a clean name
|
||||||
|
set(_staged "${CMAKE_CURRENT_BINARY_DIR}/licenses/LICENSE-${_dep_name}")
|
||||||
|
configure_file("${_dep_file}" "${_staged}" COPYONLY)
|
||||||
|
target_sources(${PROJECT_NAME} PRIVATE "${_staged}")
|
||||||
|
set_source_files_properties("${_staged}" PROPERTIES MACOSX_PACKAGE_LOCATION
|
||||||
|
"Resources/licenses")
|
||||||
|
else()
|
||||||
|
install(
|
||||||
|
FILES "${_dep_file}"
|
||||||
|
DESTINATION "${_license_dest}"
|
||||||
|
RENAME "LICENSE-${_dep_name}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|||||||
@@ -41,7 +41,8 @@
|
|||||||
"description": "Build for macOS 11.0+ (Universal binary) for CI",
|
"description": "Build for macOS 11.0+ (Universal binary) for CI",
|
||||||
"generator": "Xcode",
|
"generator": "Xcode",
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true
|
"CMAKE_COMPILE_WARNING_AS_ERROR": true,
|
||||||
|
"ADVSS_ENABLE_TESTS": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -69,7 +70,8 @@
|
|||||||
"displayName": "Windows x64 CI build",
|
"displayName": "Windows x64 CI build",
|
||||||
"description": "Build for Windows x64 on CI",
|
"description": "Build for Windows x64 on CI",
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true
|
"CMAKE_COMPILE_WARNING_AS_ERROR": true,
|
||||||
|
"ADVSS_ENABLE_TESTS": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -97,7 +99,8 @@
|
|||||||
"description": "Build for Linux x86_64 on CI",
|
"description": "Build for Linux x86_64 on CI",
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
||||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true
|
"CMAKE_COMPILE_WARNING_AS_ERROR": true,
|
||||||
|
"ADVSS_ENABLE_TESTS": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -125,7 +128,8 @@
|
|||||||
"description": "Build for Linux aarch64 on CI",
|
"description": "Build for Linux aarch64 on CI",
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
||||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true
|
"CMAKE_COMPILE_WARNING_AS_ERROR": true,
|
||||||
|
"ADVSS_ENABLE_TESTS": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
23
build-aux/CI/windows/README-portable.txt
Normal file
23
build-aux/CI/windows/README-portable.txt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
Advanced Scene Switcher - Windows Installation (Portable)
|
||||||
|
==========================================================
|
||||||
|
|
||||||
|
For full installation instructions visit:
|
||||||
|
https://github.com/WarmUpTill/SceneSwitcher/wiki/Installation
|
||||||
|
|
||||||
|
|
||||||
|
This archive uses the portable plugin layout for older OBS versions,
|
||||||
|
portable OBS installs, and Steam.
|
||||||
|
|
||||||
|
Extract the CONTENTS of this archive into your OBS installation directory,
|
||||||
|
typically:
|
||||||
|
|
||||||
|
C:\Program Files\obs-studio\
|
||||||
|
|
||||||
|
Merge the data\ and obs-plugins\ folders with the existing ones when prompted.
|
||||||
|
|
||||||
|
Steam users: right-click OBS in your library -> Manage -> Browse local files,
|
||||||
|
then extract the contents of this archive into that directory.
|
||||||
|
|
||||||
|
|
||||||
|
For OBS 28 and newer the recommended archive is preferred:
|
||||||
|
advanced-scene-switcher-<version>-windows-x64.zip
|
||||||
21
build-aux/CI/windows/README.txt
Normal file
21
build-aux/CI/windows/README.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
Advanced Scene Switcher - Windows Installation
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
For full installation instructions visit:
|
||||||
|
https://github.com/WarmUpTill/SceneSwitcher/wiki/Installation
|
||||||
|
|
||||||
|
|
||||||
|
This archive uses the recommended plugin layout for OBS 28 and newer.
|
||||||
|
|
||||||
|
Extract the CONTENTS of this archive into:
|
||||||
|
|
||||||
|
C:\ProgramData\obs-studio\plugins\
|
||||||
|
|
||||||
|
Tip: press Win+R and type %ProgramData%\obs-studio\plugins to open that folder.
|
||||||
|
|
||||||
|
After extracting, the path should look like:
|
||||||
|
C:\ProgramData\obs-studio\plugins\advanced-scene-switcher\bin\64bit\advanced-scene-switcher.dll
|
||||||
|
|
||||||
|
|
||||||
|
If the plugin does not appear in OBS, try the portable archive instead:
|
||||||
|
advanced-scene-switcher-<version>-windows-x64-portable.zip
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
if(BUILD_OUT_OF_TREE)
|
if(BUILD_OUT_OF_TREE)
|
||||||
if(OS_WINDOWS)
|
if(OS_WINDOWS)
|
||||||
set(OBS_PLUGIN_DESTINATION "obs-plugins/64bit")
|
set(OBS_PLUGIN_DESTINATION "${CMAKE_PROJECT_NAME}/bin/64bit")
|
||||||
else()
|
else()
|
||||||
set(OBS_PLUGIN_DESTINATION "${CMAKE_INSTALL_LIBDIR}/obs-plugins")
|
set(OBS_PLUGIN_DESTINATION "${CMAKE_INSTALL_LIBDIR}/obs-plugins")
|
||||||
endif()
|
endif()
|
||||||
@@ -157,7 +157,9 @@ else()
|
|||||||
set_target_properties(${target} PROPERTIES INSTALL_RPATH
|
set_target_properties(${target} PROPERTIES INSTALL_RPATH
|
||||||
"$ORIGIN:$ORIGIN/..")
|
"$ORIGIN:$ORIGIN/..")
|
||||||
endif()
|
endif()
|
||||||
message(STATUS "ADVSS: ENABLED PLUGIN ${target}")
|
get_filename_component(_plugin_dir_name "${CMAKE_CURRENT_SOURCE_DIR}" NAME)
|
||||||
|
set_property(GLOBAL APPEND PROPERTY ADVSS_PLUGINS_ENABLED
|
||||||
|
${_plugin_dir_name})
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
function(install_advss_plugin_dependency_target target dep)
|
function(install_advss_plugin_dependency_target target dep)
|
||||||
@@ -331,6 +333,8 @@ function(setup_advss_plugin target)
|
|||||||
PRIVATE "${ADVSS_SOURCE_DIR}/lib" "${ADVSS_SOURCE_DIR}/lib/legacy"
|
PRIVATE "${ADVSS_SOURCE_DIR}/lib" "${ADVSS_SOURCE_DIR}/lib/legacy"
|
||||||
"${ADVSS_SOURCE_DIR}/lib/macro" "${ADVSS_SOURCE_DIR}/lib/utils"
|
"${ADVSS_SOURCE_DIR}/lib/macro" "${ADVSS_SOURCE_DIR}/lib/utils"
|
||||||
"${ADVSS_SOURCE_DIR}/lib/variables" "${ADVSS_SOURCE_DIR}/forms")
|
"${ADVSS_SOURCE_DIR}/lib/variables" "${ADVSS_SOURCE_DIR}/forms")
|
||||||
|
|
||||||
|
target_precompile_headers(${target} PRIVATE "${ADVSS_SOURCE_DIR}/lib/pch.hpp")
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
function(install_advss_plugin_dependency)
|
function(install_advss_plugin_dependency)
|
||||||
|
|||||||
@@ -191,14 +191,21 @@ function(_check_dependencies)
|
|||||||
foreach(i RANGE 1 ${MAX_DOWNLOAD_RETRIES})
|
foreach(i RANGE 1 ${MAX_DOWNLOAD_RETRIES})
|
||||||
message(STATUS "Attempt ${i}/${MAX_DOWNLOAD_RETRIES} for ${url}")
|
message(STATUS "Attempt ${i}/${MAX_DOWNLOAD_RETRIES} for ${url}")
|
||||||
|
|
||||||
file(
|
file(DOWNLOAD "${url}" "${dependencies_dir}/${file}"
|
||||||
DOWNLOAD "${url}" "${dependencies_dir}/${file}"
|
STATUS download_status)
|
||||||
STATUS download_status
|
|
||||||
EXPECTED_HASH SHA256=${hash})
|
|
||||||
|
|
||||||
list(GET download_status 0 error_code)
|
list(GET download_status 0 error_code)
|
||||||
list(GET download_status 1 error_message)
|
list(GET download_status 1 error_message)
|
||||||
|
|
||||||
|
if(error_code EQUAL 0)
|
||||||
|
file(SHA256 "${dependencies_dir}/${file}" actual_hash)
|
||||||
|
if(NOT actual_hash STREQUAL hash)
|
||||||
|
set(error_code 1)
|
||||||
|
set(error_message
|
||||||
|
"hash mismatch (expected ${hash}, got ${actual_hash})")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
if(error_code EQUAL 0)
|
if(error_code EQUAL 0)
|
||||||
message(STATUS "Downloading ${url} - success on attempt ${i}")
|
message(STATUS "Downloading ${url} - success on attempt ${i}")
|
||||||
set(download_success TRUE)
|
set(download_success TRUE)
|
||||||
|
|||||||
@@ -18,8 +18,11 @@ set(CPACK_PACKAGE_FILE_NAME
|
|||||||
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_C_LIBRARY_ARCHITECTURE}"
|
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_C_LIBRARY_ARCHITECTURE}"
|
||||||
)
|
)
|
||||||
|
|
||||||
set(CPACK_GENERATOR "DEB")
|
set(CPACK_GENERATOR
|
||||||
|
"DEB"
|
||||||
|
CACHE STRING "CPack generator to use")
|
||||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
||||||
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "obs-studio")
|
||||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${PLUGIN_EMAIL}")
|
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${PLUGIN_EMAIL}")
|
||||||
set(CPACK_SET_DESTDIR ON)
|
set(CPACK_SET_DESTDIR ON)
|
||||||
|
|
||||||
@@ -46,6 +49,19 @@ set(CPACK_SOURCE_PACKAGE_FILE_NAME
|
|||||||
set(CPACK_ARCHIVE_THREADS 0)
|
set(CPACK_ARCHIVE_THREADS 0)
|
||||||
|
|
||||||
include(CPack)
|
include(CPack)
|
||||||
|
# CPack.cmake has a side effect that leaves binary packaging variables in the
|
||||||
|
# state intended for source packaging when it finishes. Specifically it sets
|
||||||
|
# CPACK_GENERATOR to CPACK_SOURCE_GENERATOR ("TXZ"), sets
|
||||||
|
# CPACK_INSTALLED_DIRECTORIES to the source root (which would cause CPackDeb to
|
||||||
|
# scan the entire source tree, including ccache object files that lack a Build
|
||||||
|
# ID), and clears CPACK_INSTALL_CMAKE_PROJECTS (preventing the cmake install
|
||||||
|
# mechanism from being used). In some cmake versions these overrides happen
|
||||||
|
# before CPackConfig.cmake is written. Reset all three so that binary DEB
|
||||||
|
# packaging works correctly regardless of cmake version.
|
||||||
|
set(CPACK_GENERATOR "DEB")
|
||||||
|
set(CPACK_INSTALLED_DIRECTORIES "")
|
||||||
|
set(CPACK_INSTALL_CMAKE_PROJECTS
|
||||||
|
"${CMAKE_BINARY_DIR};${CMAKE_PROJECT_NAME};ALL;/")
|
||||||
|
|
||||||
find_package(libobs QUIET)
|
find_package(libobs QUIET)
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ include(buildspec_common)
|
|||||||
|
|
||||||
# _check_dependencies_windows: Set up Windows slice for _check_dependencies
|
# _check_dependencies_windows: Set up Windows slice for _check_dependencies
|
||||||
function(_check_dependencies_windows)
|
function(_check_dependencies_windows)
|
||||||
set(arch ${CMAKE_GENERATOR_PLATFORM})
|
if(CMAKE_GENERATOR_PLATFORM)
|
||||||
|
set(arch ${CMAKE_GENERATOR_PLATFORM})
|
||||||
|
else()
|
||||||
|
set(arch x64)
|
||||||
|
endif()
|
||||||
set(platform windows-${arch})
|
set(platform windows-${arch})
|
||||||
|
|
||||||
set(dependencies_dir "${CMAKE_CURRENT_SOURCE_DIR}/.deps")
|
set(dependencies_dir "${CMAKE_CURRENT_SOURCE_DIR}/.deps")
|
||||||
|
|||||||
@@ -6,3 +6,9 @@ include_guard(GLOBAL)
|
|||||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL TRUE)
|
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL TRUE)
|
||||||
|
|
||||||
include(buildspec)
|
include(buildspec)
|
||||||
|
|
||||||
|
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||||
|
set(CMAKE_INSTALL_PREFIX
|
||||||
|
"$ENV{ALLUSERSPROFILE}/obs-studio/plugins"
|
||||||
|
CACHE STRING "Default plugin installation directory" FORCE)
|
||||||
|
endif()
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ function(set_target_properties_plugin target)
|
|||||||
|
|
||||||
install(
|
install(
|
||||||
TARGETS ${target}
|
TARGETS ${target}
|
||||||
RUNTIME DESTINATION bin/64bit
|
RUNTIME DESTINATION "${target}/bin/64bit"
|
||||||
LIBRARY DESTINATION obs-plugins/64bit)
|
LIBRARY DESTINATION "${target}/bin/64bit")
|
||||||
|
|
||||||
install(
|
install(
|
||||||
FILES "$<TARGET_PDB_FILE:${target}>"
|
FILES "$<TARGET_PDB_FILE:${target}>"
|
||||||
CONFIGURATIONS RelWithDebInfo Debug Release
|
CONFIGURATIONS RelWithDebInfo Debug Release
|
||||||
DESTINATION obs-plugins/64bit
|
DESTINATION "${target}/bin/64bit"
|
||||||
OPTIONAL)
|
OPTIONAL)
|
||||||
|
|
||||||
if(OBS_BUILD_DIR)
|
if(OBS_BUILD_DIR)
|
||||||
@@ -44,11 +44,11 @@ function(set_target_properties_plugin target)
|
|||||||
TARGET ${target}
|
TARGET ${target}
|
||||||
POST_BUILD
|
POST_BUILD
|
||||||
COMMAND "${CMAKE_COMMAND}" -E make_directory
|
COMMAND "${CMAKE_COMMAND}" -E make_directory
|
||||||
"${OBS_BUILD_DIR}/obs-plugins/64bit"
|
"${OBS_BUILD_DIR}/plugins/${target}/bin/64bit"
|
||||||
COMMAND
|
COMMAND
|
||||||
"${CMAKE_COMMAND}" -E copy_if_different "$<TARGET_FILE:${target}>"
|
"${CMAKE_COMMAND}" -E copy_if_different "$<TARGET_FILE:${target}>"
|
||||||
"$<$<CONFIG:Debug,RelWithDebInfo,Release>:$<TARGET_PDB_FILE:${target}>>"
|
"$<$<CONFIG:Debug,RelWithDebInfo,Release>:$<TARGET_PDB_FILE:${target}>>"
|
||||||
"${OBS_BUILD_DIR}/obs-plugins/64bit"
|
"${OBS_BUILD_DIR}/plugins/${target}/bin/64bit"
|
||||||
COMMENT "Copy ${target} to obs-studio directory ${OBS_BUILD_DIR}"
|
COMMENT "Copy ${target} to obs-studio directory ${OBS_BUILD_DIR}"
|
||||||
VERBATIM)
|
VERBATIM)
|
||||||
endif()
|
endif()
|
||||||
@@ -105,7 +105,7 @@ function(target_install_resources target)
|
|||||||
|
|
||||||
install(
|
install(
|
||||||
DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/"
|
DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/"
|
||||||
DESTINATION data/obs-plugins/${target}
|
DESTINATION "${target}/data"
|
||||||
USE_SOURCE_PERMISSIONS)
|
USE_SOURCE_PERMISSIONS)
|
||||||
|
|
||||||
if(OBS_BUILD_DIR)
|
if(OBS_BUILD_DIR)
|
||||||
@@ -113,11 +113,11 @@ function(target_install_resources target)
|
|||||||
TARGET ${target}
|
TARGET ${target}
|
||||||
POST_BUILD
|
POST_BUILD
|
||||||
COMMAND "${CMAKE_COMMAND}" -E make_directory
|
COMMAND "${CMAKE_COMMAND}" -E make_directory
|
||||||
"${OBS_BUILD_DIR}/data/obs-plugins/${target}"
|
"${OBS_BUILD_DIR}/plugins/${target}/data"
|
||||||
COMMAND
|
COMMAND
|
||||||
"${CMAKE_COMMAND}" -E copy_directory
|
"${CMAKE_COMMAND}" -E copy_directory
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/data"
|
"${CMAKE_CURRENT_SOURCE_DIR}/data"
|
||||||
"${OBS_BUILD_DIR}/data/obs-plugins/${target}"
|
"${OBS_BUILD_DIR}/plugins/${target}/data"
|
||||||
COMMENT "Copy ${target} resources to data directory"
|
COMMENT "Copy ${target} resources to data directory"
|
||||||
VERBATIM)
|
VERBATIM)
|
||||||
endif()
|
endif()
|
||||||
@@ -133,7 +133,7 @@ function(target_add_resource target resource)
|
|||||||
|
|
||||||
install(
|
install(
|
||||||
FILES "${resource}"
|
FILES "${resource}"
|
||||||
DESTINATION data/obs-plugins/${target}
|
DESTINATION "${target}/data"
|
||||||
COMPONENT Runtime)
|
COMPONENT Runtime)
|
||||||
|
|
||||||
if(OBS_BUILD_DIR)
|
if(OBS_BUILD_DIR)
|
||||||
@@ -141,9 +141,9 @@ function(target_add_resource target resource)
|
|||||||
TARGET ${target}
|
TARGET ${target}
|
||||||
POST_BUILD
|
POST_BUILD
|
||||||
COMMAND "${CMAKE_COMMAND}" -E make_directory
|
COMMAND "${CMAKE_COMMAND}" -E make_directory
|
||||||
"${OBS_BUILD_DIR}/data/obs-plugins/${target}"
|
"${OBS_BUILD_DIR}/plugins/${target}/data"
|
||||||
COMMAND "${CMAKE_COMMAND}" -E copy "${resource}"
|
COMMAND "${CMAKE_COMMAND}" -E copy "${resource}"
|
||||||
"${OBS_BUILD_DIR}/data/obs-plugins/${target}"
|
"${OBS_BUILD_DIR}/plugins/${target}/data"
|
||||||
COMMENT "Copy ${target} resource ${resource} to library directory"
|
COMMENT "Copy ${target} resource ${resource} to library directory"
|
||||||
VERBATIM)
|
VERBATIM)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
#define MyAppVersion "@CMAKE_PROJECT_VERSION@"
|
#define MyAppVersion "@CMAKE_PROJECT_VERSION@"
|
||||||
#define MyAppPublisher "@PLUGIN_AUTHOR@"
|
#define MyAppPublisher "@PLUGIN_AUTHOR@"
|
||||||
#define MyAppURL "@PLUGIN_WEBSITE@"
|
#define MyAppURL "@PLUGIN_WEBSITE@"
|
||||||
|
#define MyAppId "@UUID_APP@"
|
||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
; NOTE: The value of AppId uniquely identifies this application.
|
; NOTE: The value of AppId uniquely identifies this application.
|
||||||
; Do not use the same AppId value in installers for other applications.
|
; Do not use the same AppId value in installers for other applications.
|
||||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||||
AppId={{@UUID_APP@}
|
AppId={{{#MyAppId}}
|
||||||
AppName={#MyAppName}
|
AppName={#MyAppName}
|
||||||
AppVersion={#MyAppVersion}
|
AppVersion={#MyAppVersion}
|
||||||
AppPublisher={#MyAppPublisher}
|
AppPublisher={#MyAppPublisher}
|
||||||
@@ -20,15 +21,19 @@ OutputBaseFilename={#MyAppName}-{#MyAppVersion}-Windows-Installer
|
|||||||
Compression=lzma
|
Compression=lzma
|
||||||
SolidCompression=yes
|
SolidCompression=yes
|
||||||
DirExistsWarning=no
|
DirExistsWarning=no
|
||||||
|
UsePreviousAppDir=no
|
||||||
SetupIconFile=installer.ico
|
SetupIconFile=installer.ico
|
||||||
UninstallDisplayIcon={app}\data\obs-plugins\advanced-scene-switcher\res\images\logo.ico
|
UninstallDisplayIcon={code:GetUninstallIconPath}
|
||||||
DisableDirPage=no
|
DisableDirPage=no
|
||||||
|
|
||||||
[Languages]
|
[Languages]
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: "..\release\Package\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
; Recommended layout - used when installing to %ProgramData%\obs-studio\plugins\
|
||||||
|
Source: "..\release\Package\recommended\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Check: IsRecommendedLayout
|
||||||
|
; Legacy layout - used when installing to the OBS installation directory
|
||||||
|
Source: "..\release\Package\portable\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Check: IsLegacyLayout
|
||||||
Source: "..\LICENSE"; Flags: dontcopy
|
Source: "..\LICENSE"; Flags: dontcopy
|
||||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
@@ -51,17 +56,64 @@ begin
|
|||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// credit where it's due :
|
// Returns true when the selected install directory is under %ProgramData%,
|
||||||
// following function come from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45
|
// meaning the recommended plugin layout should be used.
|
||||||
function GetDirName(Value: string): string;
|
function IsRecommendedLayout(): Boolean;
|
||||||
var
|
|
||||||
InstallPath: string;
|
|
||||||
begin
|
begin
|
||||||
// initialize default path, which will be returned when the following registry
|
Result := Pos(LowerCase(ExpandConstant('{commonappdata}')),
|
||||||
// key queries fail due to missing keys or for some different reason
|
LowerCase(WizardDirValue())) = 1;
|
||||||
Result := '{autopf}\obs-studio';
|
|
||||||
// query the first registry value; if this succeeds, return the obtained value
|
|
||||||
if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then
|
|
||||||
Result := InstallPath
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function IsLegacyLayout(): Boolean;
|
||||||
|
begin
|
||||||
|
Result := not IsRecommendedLayout();
|
||||||
|
end;
|
||||||
|
|
||||||
|
function GetUninstallIconPath(Param: string): string;
|
||||||
|
begin
|
||||||
|
if IsRecommendedLayout() then
|
||||||
|
Result := ExpandConstant('{app}\advanced-scene-switcher\data\res\images\logo.ico')
|
||||||
|
else
|
||||||
|
Result := ExpandConstant('{app}\data\obs-plugins\advanced-scene-switcher\res\images\logo.ico');
|
||||||
|
end;
|
||||||
|
|
||||||
|
function GetDirName(Value: string): string;
|
||||||
|
var
|
||||||
|
PrevPath: string;
|
||||||
|
UninstallKey: string;
|
||||||
|
begin
|
||||||
|
Result := ExpandConstant('{commonappdata}\obs-studio\plugins');
|
||||||
|
UninstallKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + '{' + '{#MyAppId}' + '}_is1';
|
||||||
|
if RegQueryStringValue(HKLM, UninstallKey, 'InstallLocation', PrevPath) and (PrevPath <> '') then
|
||||||
|
Result := PrevPath;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Remove files placed by the portable installer (obs-plugins/64bit and
|
||||||
|
// data/obs-plugins layout) so the plugin is not loaded twice by OBS.
|
||||||
|
// Only runs when upgrading from a portable location to the recommended one.
|
||||||
|
procedure RemoveLegacyFiles();
|
||||||
|
var
|
||||||
|
OldInstallPath: string;
|
||||||
|
UninstallKey: string;
|
||||||
|
begin
|
||||||
|
if not IsRecommendedLayout() then
|
||||||
|
Exit;
|
||||||
|
UninstallKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + '{' + '{#MyAppId}' + '}_is1';
|
||||||
|
if not RegQueryStringValue(HKLM, UninstallKey, 'InstallLocation', OldInstallPath) then
|
||||||
|
Exit;
|
||||||
|
// Skip if the previous install was already in the recommended location
|
||||||
|
if Pos(LowerCase(ExpandConstant('{commonappdata}')), LowerCase(OldInstallPath)) = 1 then
|
||||||
|
Exit;
|
||||||
|
DeleteFile(OldInstallPath + '\obs-plugins\64bit\advanced-scene-switcher.dll');
|
||||||
|
DeleteFile(OldInstallPath + '\obs-plugins\64bit\advanced-scene-switcher.pdb');
|
||||||
|
DeleteFile(OldInstallPath + '\obs-plugins\64bit\advanced-scene-switcher-lib.dll');
|
||||||
|
DeleteFile(OldInstallPath + '\obs-plugins\64bit\advanced-scene-switcher-lib.pdb');
|
||||||
|
DelTree(OldInstallPath + '\obs-plugins\64bit\advanced-scene-switcher-plugins', True, True, True);
|
||||||
|
DelTree(OldInstallPath + '\data\obs-plugins\advanced-scene-switcher', True, True, True);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure CurStepChanged(CurStep: TSetupStep);
|
||||||
|
begin
|
||||||
|
if CurStep = ssInstall then
|
||||||
|
RemoveLegacyFiles();
|
||||||
|
end;
|
||||||
|
|||||||
@@ -40,19 +40,27 @@ if(WIN32 AND (NOT OpenSSL_FOUND))
|
|||||||
message(STATUS "Looking for OpenSSL built with CRT variant: ${_crt_kind}")
|
message(STATUS "Looking for OpenSSL built with CRT variant: ${_crt_kind}")
|
||||||
|
|
||||||
# Try to find the root and corresponding lib path
|
# Try to find the root and corresponding lib path
|
||||||
|
if(OPENSSL_USE_STATIC_LIBS)
|
||||||
|
set(_crypto_lib_name "libcrypto_static.lib")
|
||||||
|
set(_ssl_lib_name "libssl_static.lib")
|
||||||
|
else()
|
||||||
|
set(_crypto_lib_name "libcrypto.lib")
|
||||||
|
set(_ssl_lib_name "libssl.lib")
|
||||||
|
endif()
|
||||||
|
|
||||||
foreach(_root ${_openssl_roots})
|
foreach(_root ${_openssl_roots})
|
||||||
if(EXISTS "${_root}/include/openssl/ssl.h")
|
if(EXISTS "${_root}/include/openssl/ssl.h")
|
||||||
foreach(_suffix ${_openssl_lib_suffixes})
|
foreach(_suffix ${_openssl_lib_suffixes})
|
||||||
if(_suffix MATCHES "${_crt_kind}$"
|
if(_suffix MATCHES "${_crt_kind}$"
|
||||||
AND EXISTS "${_root}/${_suffix}/libcrypto.lib")
|
AND EXISTS "${_root}/${_suffix}/${_crypto_lib_name}")
|
||||||
set(OPENSSL_ROOT_DIR
|
set(OPENSSL_ROOT_DIR
|
||||||
"${_root}"
|
"${_root}"
|
||||||
CACHE PATH "Path to OpenSSL root")
|
CACHE PATH "Path to OpenSSL root")
|
||||||
set(OPENSSL_CRYPTO_LIBRARY
|
set(OPENSSL_CRYPTO_LIBRARY
|
||||||
"${_root}/${_suffix}/libcrypto.lib"
|
"${_root}/${_suffix}/${_crypto_lib_name}"
|
||||||
CACHE FILEPATH "OpenSSL crypto lib")
|
CACHE FILEPATH "OpenSSL crypto lib")
|
||||||
set(OPENSSL_SSL_LIBRARY
|
set(OPENSSL_SSL_LIBRARY
|
||||||
"${_root}/${_suffix}/libssl.lib"
|
"${_root}/${_suffix}/${_ssl_lib_name}"
|
||||||
CACHE FILEPATH "OpenSSL ssl lib")
|
CACHE FILEPATH "OpenSSL ssl lib")
|
||||||
set(OPENSSL_INCLUDE_DIR
|
set(OPENSSL_INCLUDE_DIR
|
||||||
"${_root}/include"
|
"${_root}/include"
|
||||||
|
|||||||
@@ -99,7 +99,6 @@ AdvSceneSwitcher.macroList.deleted="gelöscht"
|
|||||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" ist bereits ausgewählt!"
|
AdvSceneSwitcher.macroList.duplicate="\"%1\" ist bereits ausgewählt!"
|
||||||
|
|
||||||
; Macro Logic
|
; Macro Logic
|
||||||
AdvSceneSwitcher.logic.none="Eintrag ignorieren"
|
|
||||||
AdvSceneSwitcher.logic.and="Und"
|
AdvSceneSwitcher.logic.and="Und"
|
||||||
AdvSceneSwitcher.logic.or="Oder"
|
AdvSceneSwitcher.logic.or="Oder"
|
||||||
AdvSceneSwitcher.logic.andNot="Und nicht"
|
AdvSceneSwitcher.logic.andNot="Und nicht"
|
||||||
@@ -383,8 +382,8 @@ AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Warten, bis der Übergan
|
|||||||
AdvSceneSwitcher.action.wait="Warten"
|
AdvSceneSwitcher.action.wait="Warten"
|
||||||
AdvSceneSwitcher.action.wait.type.fixed="fixe"
|
AdvSceneSwitcher.action.wait.type.fixed="fixe"
|
||||||
AdvSceneSwitcher.action.wait.type.random="zufällig"
|
AdvSceneSwitcher.action.wait.type.random="zufällig"
|
||||||
AdvSceneSwitcher.action.wait.entry.fixed="Warte für {{waitType}} Dauer von {{duration}}"
|
AdvSceneSwitcher.action.wait.layout.fixed="Warte für {{waitType}} Dauer von {{duration}}"
|
||||||
AdvSceneSwitcher.action.wait.entry.random="Warte für {{waitType}} Dauer von {{duration}} bis {{duration2}}"
|
AdvSceneSwitcher.action.wait.layout.random="Warte für {{waitType}} Dauer von {{duration}} bis {{duration2}}"
|
||||||
AdvSceneSwitcher.action.audio="Audio"
|
AdvSceneSwitcher.action.audio="Audio"
|
||||||
AdvSceneSwitcher.action.audio.type.mute="Stummgeschaltet"
|
AdvSceneSwitcher.action.audio.type.mute="Stummgeschaltet"
|
||||||
AdvSceneSwitcher.action.audio.type.unmute="Nicht stummgeschaltet"
|
AdvSceneSwitcher.action.audio.type.unmute="Nicht stummgeschaltet"
|
||||||
|
|||||||
@@ -47,6 +47,13 @@ AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="Hide tabs which can
|
|||||||
AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowMacroSearch="Always show macro search"
|
AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowMacroSearch="Always show macro search"
|
||||||
AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowFeatureTabs="Always show all feature tabs"
|
AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowFeatureTabs="Always show all feature tabs"
|
||||||
AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowFeatureTabs.tooltip="Show tabs that are normally hidden until first used in a macro (e.g. Action Queues, Variables, Twitch, Websockets)."
|
AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowFeatureTabs.tooltip="Show tabs that are normally hidden until first used in a macro (e.g. Action Queues, Variables, Twitch, Websockets)."
|
||||||
|
AdvSceneSwitcher.generalTab.generalBehavior.settingsLock="Lock settings dialog with password"
|
||||||
|
AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title="Settings Locked"
|
||||||
|
AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.passwordLabel="Enter password:"
|
||||||
|
AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.wrongPassword="Wrong password!"
|
||||||
|
AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.newPasswordLabel="New password:"
|
||||||
|
AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.confirmPasswordLabel="Confirm password:"
|
||||||
|
AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.passwordMismatch="Passwords do not match!"
|
||||||
AdvSceneSwitcher.generalTab.matchBehavior="Match behavior"
|
AdvSceneSwitcher.generalTab.matchBehavior="Match behavior"
|
||||||
AdvSceneSwitcher.generalTab.priority="Priority"
|
AdvSceneSwitcher.generalTab.priority="Priority"
|
||||||
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
|
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
|
||||||
@@ -199,9 +206,18 @@ AdvSceneSwitcher.macroTab.expandAllGroups="Expand all Groups"
|
|||||||
AdvSceneSwitcher.macroTab.collapseAllGroups="Collapse all Groups"
|
AdvSceneSwitcher.macroTab.collapseAllGroups="Collapse all Groups"
|
||||||
AdvSceneSwitcher.macroTab.rename="Rename"
|
AdvSceneSwitcher.macroTab.rename="Rename"
|
||||||
AdvSceneSwitcher.macroTab.remove="Remove"
|
AdvSceneSwitcher.macroTab.remove="Remove"
|
||||||
|
AdvSceneSwitcher.macroTab.pause="Pause"
|
||||||
|
AdvSceneSwitcher.macroTab.unpause="Unpause"
|
||||||
AdvSceneSwitcher.macroTab.export="Export"
|
AdvSceneSwitcher.macroTab.export="Export"
|
||||||
AdvSceneSwitcher.macroTab.export.info="Paste the string below into the import dialog to import the selected macros:"
|
AdvSceneSwitcher.macroTab.export.info="Paste the string below into the import dialog to import the selected macros:"
|
||||||
AdvSceneSwitcher.macroTab.export.usePlainText="Use plain text"
|
AdvSceneSwitcher.macroTab.export.usePlainText="Use plain text"
|
||||||
|
AdvSceneSwitcher.macroTab.export.additionalContent="Export Additional Content"
|
||||||
|
AdvSceneSwitcher.macroTab.export.variables="Variables"
|
||||||
|
AdvSceneSwitcher.macroTab.export.actionQueues="Action Queues"
|
||||||
|
AdvSceneSwitcher.macroTab.export.httpServers="HTTP Servers"
|
||||||
|
AdvSceneSwitcher.macroTab.export.macroScheduleEntries="Macro Schedule"
|
||||||
|
AdvSceneSwitcher.macroTab.export.twitchConnections="Twitch Connections"
|
||||||
|
AdvSceneSwitcher.macroTab.export.websocketConnections="Websocket Connections"
|
||||||
AdvSceneSwitcher.macroTab.import="Import"
|
AdvSceneSwitcher.macroTab.import="Import"
|
||||||
AdvSceneSwitcher.macroTab.import.info="Paste the export string into the below text box to import macros:"
|
AdvSceneSwitcher.macroTab.import.info="Paste the export string into the below text box to import macros:"
|
||||||
AdvSceneSwitcher.macroTab.import.invalid="Invalid import data provided!"
|
AdvSceneSwitcher.macroTab.import.invalid="Invalid import data provided!"
|
||||||
@@ -306,7 +322,6 @@ AdvSceneSwitcher.macroList.deleted="deleted"
|
|||||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" is alreay selected!"
|
AdvSceneSwitcher.macroList.duplicate="\"%1\" is alreay selected!"
|
||||||
|
|
||||||
# Macro Logic
|
# Macro Logic
|
||||||
AdvSceneSwitcher.logic.none="Ignore entry"
|
|
||||||
AdvSceneSwitcher.logic.and="And"
|
AdvSceneSwitcher.logic.and="And"
|
||||||
AdvSceneSwitcher.logic.or="Or"
|
AdvSceneSwitcher.logic.or="Or"
|
||||||
AdvSceneSwitcher.logic.andNot="And not"
|
AdvSceneSwitcher.logic.andNot="And not"
|
||||||
@@ -431,6 +446,7 @@ AdvSceneSwitcher.condition.video.ocrConfigReload="Reload configuration file"
|
|||||||
AdvSceneSwitcher.condition.video.ocrConfigHint="Tesseract config files consist of lines with parameter-value pairs (space separated).\nFor example:\n\ntessedit_char_blacklist\t\t\t\t\"abc\"\nlanguage_model_penalty_non_dict_word\t0"
|
AdvSceneSwitcher.condition.video.ocrConfigHint="Tesseract config files consist of lines with parameter-value pairs (space separated).\nFor example:\n\ntessedit_char_blacklist\t\t\t\t\"abc\"\nlanguage_model_penalty_non_dict_word\t0"
|
||||||
AdvSceneSwitcher.condition.video.modelLoadFail="Model data could not be loaded!"
|
AdvSceneSwitcher.condition.video.modelLoadFail="Model data could not be loaded!"
|
||||||
AdvSceneSwitcher.condition.video.selectColor="Select Color"
|
AdvSceneSwitcher.condition.video.selectColor="Select Color"
|
||||||
|
AdvSceneSwitcher.condition.video.colorVariableTooltip="Expected format: #RRGGBB or #AARRGGBB\nInvalid values will default to black."
|
||||||
AdvSceneSwitcher.condition.video.ocrMode.singleColumn="Single column of text of variable sizes"
|
AdvSceneSwitcher.condition.video.ocrMode.singleColumn="Single column of text of variable sizes"
|
||||||
AdvSceneSwitcher.condition.video.ocrMode.singleBlockVertText="Single uniform block of vertically aligned text"
|
AdvSceneSwitcher.condition.video.ocrMode.singleBlockVertText="Single uniform block of vertically aligned text"
|
||||||
AdvSceneSwitcher.condition.video.ocrMode.singleBlock="Single uniform block of text"
|
AdvSceneSwitcher.condition.video.ocrMode.singleBlock="Single uniform block of text"
|
||||||
@@ -454,12 +470,12 @@ AdvSceneSwitcher.condition.video.layout.minNeighbor="Minimum neighbors:{{minNeig
|
|||||||
AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Reduce CPU load by performing check only every{{throttleCount}}milliseconds"
|
AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Reduce CPU load by performing check only every{{throttleCount}}milliseconds"
|
||||||
AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Perform check only in area"
|
AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Perform check only in area"
|
||||||
AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
|
AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrColorPick="Check for text color:{{textColor}}{{selectColor}}"
|
AdvSceneSwitcher.condition.video.layout.ocrColorPick="Check for text color:{{color}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrTextType="Check for text type:{{textType}}"
|
AdvSceneSwitcher.condition.video.layout.ocrTextType="Check for text type:{{textType}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrBaseDir="Tesseract base directory:{{tesseractBaseDir}}"
|
AdvSceneSwitcher.condition.video.layout.ocrBaseDir="Tesseract base directory:{{tesseractBaseDir}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrLanguage="Check for language:{{languageCode}}"
|
AdvSceneSwitcher.condition.video.layout.ocrLanguage="Check for language:{{languageCode}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrConfig="Config file:{{configFile}}{{openConfigFile}}{{reloadConfig}}{{configFileHint}}"
|
AdvSceneSwitcher.condition.video.layout.ocrConfig="Config file:{{configFile}}{{openConfigFile}}{{reloadConfig}}{{configFileHint}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.color="Check for color:{{color}}{{selectColor}}"
|
AdvSceneSwitcher.condition.video.layout.color="Check for color:{{color}}"
|
||||||
AdvSceneSwitcher.condition.video.minSize="Minimum size:"
|
AdvSceneSwitcher.condition.video.minSize="Minimum size:"
|
||||||
AdvSceneSwitcher.condition.video.maxSize="Maximum size:"
|
AdvSceneSwitcher.condition.video.maxSize="Maximum size:"
|
||||||
AdvSceneSwitcher.condition.video.selectArea="Select area"
|
AdvSceneSwitcher.condition.video.selectArea="Select area"
|
||||||
@@ -824,6 +840,38 @@ AdvSceneSwitcher.condition.clipboard.condition.isImage="Clipboard contains an im
|
|||||||
AdvSceneSwitcher.condition.clipboard.condition.isURL="Clipboard contains an URL"
|
AdvSceneSwitcher.condition.clipboard.condition.isURL="Clipboard contains an URL"
|
||||||
AdvSceneSwitcher.condition.clipboard.condition.matches="Clipboard content matches"
|
AdvSceneSwitcher.condition.clipboard.condition.matches="Clipboard content matches"
|
||||||
AdvSceneSwitcher.condition.clipboard.condition.entry="{{conditions}}{{regex}}{{urlInfo}}"
|
AdvSceneSwitcher.condition.clipboard.condition.entry="{{conditions}}{{regex}}{{urlInfo}}"
|
||||||
|
AdvSceneSwitcher.condition.speech="Speech Recognition (beta)"
|
||||||
|
AdvSceneSwitcher.condition.speech.condition.any="Any speech is detected for"
|
||||||
|
AdvSceneSwitcher.condition.speech.condition.contains="contains phrase"
|
||||||
|
AdvSceneSwitcher.condition.speech.condition.matches="matches"
|
||||||
|
AdvSceneSwitcher.condition.speech.layout.any="{{conditions}}{{source}}"
|
||||||
|
AdvSceneSwitcher.condition.speech.layout.contains="Transcript of{{source}}{{conditions}}:"
|
||||||
|
AdvSceneSwitcher.condition.speech.layout.matches="Transcript of{{source}}{{conditions}}:"
|
||||||
|
AdvSceneSwitcher.condition.speech.layout.phrase="{{phrase}}{{regex}}"
|
||||||
|
AdvSceneSwitcher.condition.speech.layout.model="Whisper model:{{modelPath}}{{help}}"
|
||||||
|
AdvSceneSwitcher.condition.speech.model.help="GGML model files can be downloaded from https://huggingface.co/ggerganov/whisper.cpp (e.g. ggml-base.bin).\nLarger models are more accurate but slower."
|
||||||
|
AdvSceneSwitcher.condition.speech.layout.buffer="Audio buffer:{{bufferDuration}}{{help}}"
|
||||||
|
AdvSceneSwitcher.condition.speech.browse="Browse..."
|
||||||
|
AdvSceneSwitcher.condition.speech.browse.title="Select Whisper model file"
|
||||||
|
AdvSceneSwitcher.condition.speech.browse.filter="GGML model files (*.bin);;All files (*)"
|
||||||
|
AdvSceneSwitcher.condition.speech.buffer.help="Longer buffer durations improve accuracy but increase latency."
|
||||||
|
AdvSceneSwitcher.condition.speech.advanced="Advanced"
|
||||||
|
AdvSceneSwitcher.condition.speech.layout.advanced.threads="Threads:{{threads}}"
|
||||||
|
AdvSceneSwitcher.condition.speech.layout.advanced.language="Language:{{language}}{{help}}"
|
||||||
|
AdvSceneSwitcher.condition.speech.advanced.language.help="Whisper language code (e.g. 'en', 'de', 'fr') or 'auto' to detect automatically."
|
||||||
|
AdvSceneSwitcher.condition.speech.layout.advanced.translate="{{translate}}{{help}}"
|
||||||
|
AdvSceneSwitcher.condition.speech.advanced.translate="Translate to English"
|
||||||
|
AdvSceneSwitcher.condition.speech.advanced.translate.help="Translate non-English speech to English before transcribing.\nUseful when the phrase or regex is written in English but the source speaks another language."
|
||||||
|
AdvSceneSwitcher.condition.speech.layout.advanced.vad="VAD energy threshold:{{vad}}{{help}}"
|
||||||
|
AdvSceneSwitcher.condition.speech.advanced.vad.help="Minimum RMS energy a buffer must have before running inference. Buffers below this level are treated as silence and skipped. Lower values are more sensitive; raise it if inference triggers on background noise."
|
||||||
|
AdvSceneSwitcher.condition.speech.layout.advanced.suppress="{{suppress}}{{help}}"
|
||||||
|
AdvSceneSwitcher.condition.speech.advanced.suppress="Suppress non-speech tokens"
|
||||||
|
AdvSceneSwitcher.condition.speech.advanced.suppress.help="Remove filler tokens such as [MUSIC] or (applause) that Whisper tends to insert when it detects non-speech sounds."
|
||||||
|
AdvSceneSwitcher.condition.speech.layout.advanced.noContext="{{noContext}}{{help}}"
|
||||||
|
AdvSceneSwitcher.condition.speech.advanced.noContext="No context"
|
||||||
|
AdvSceneSwitcher.condition.speech.advanced.noContext.help="Do not feed the previous transcription back as a prompt for the next buffer.\nPrevents repetition across buffer boundaries at the cost of slightly reduced coherence."
|
||||||
|
AdvSceneSwitcher.condition.speech.advanced.listenWhenMuted="Listen when source is muted"
|
||||||
|
AdvSceneSwitcher.condition.speech.advanced.useGpu="Use GPU"
|
||||||
AdvSceneSwitcher.condition.folder="Folder watch"
|
AdvSceneSwitcher.condition.folder="Folder watch"
|
||||||
AdvSceneSwitcher.condition.folder.tooltip="This condition type will allow you to monitor the contents of a folder.\nNote that the monitoring will *not* recursively scan for changes in sub directories within directories of the selected folder!\nNote that if there are several changes during a short period of time, some of the changes might not emit this signal.\nHowever, the last change in the sequence of changes always will."
|
AdvSceneSwitcher.condition.folder.tooltip="This condition type will allow you to monitor the contents of a folder.\nNote that the monitoring will *not* recursively scan for changes in sub directories within directories of the selected folder!\nNote that if there are several changes during a short period of time, some of the changes might not emit this signal.\nHowever, the last change in the sequence of changes always will."
|
||||||
AdvSceneSwitcher.condition.folder.condition.any="Any change happened"
|
AdvSceneSwitcher.condition.folder.condition.any="Any change happened"
|
||||||
@@ -856,6 +904,7 @@ AdvSceneSwitcher.condition.streamDeck.stopListen="Stop listening"
|
|||||||
AdvSceneSwitcher.condition.streamDeck.pluginDownload="<html><head/><body><p>The Stream Deck plugin can be found <a href=\"https://github.com/WarmUpTill/advanced-scene-switcher-streamdeck-plugin/releases\"><span style=\" text-decoration: underline; color:#268bd2;\">here on GitHub</span></a>.</p></body></html>"
|
AdvSceneSwitcher.condition.streamDeck.pluginDownload="<html><head/><body><p>The Stream Deck plugin can be found <a href=\"https://github.com/WarmUpTill/advanced-scene-switcher-streamdeck-plugin/releases\"><span style=\" text-decoration: underline; color:#268bd2;\">here on GitHub</span></a>.</p></body></html>"
|
||||||
AdvSceneSwitcher.condition.gameCapture="Game capture"
|
AdvSceneSwitcher.condition.gameCapture="Game capture"
|
||||||
AdvSceneSwitcher.condition.gameCapture.entry="{{sources}}hooked a game."
|
AdvSceneSwitcher.condition.gameCapture.entry="{{sources}}hooked a game."
|
||||||
|
|
||||||
AdvSceneSwitcher.condition.screenshot="Screenshot"
|
AdvSceneSwitcher.condition.screenshot="Screenshot"
|
||||||
AdvSceneSwitcher.condition.screenshot.entry="A screenshot was taken"
|
AdvSceneSwitcher.condition.screenshot.entry="A screenshot was taken"
|
||||||
AdvSceneSwitcher.condition.mqtt="MQTT"
|
AdvSceneSwitcher.condition.mqtt="MQTT"
|
||||||
@@ -884,8 +933,18 @@ AdvSceneSwitcher.action.scene.canvasNotSupported="Not supported by the currently
|
|||||||
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.type.variableCondition="variable"
|
||||||
AdvSceneSwitcher.action.wait.entry.random="Wait for{{waitType}}duration from{{duration}}to{{duration2}}"
|
AdvSceneSwitcher.action.wait.layout.fixed="Wait for{{waitType}}duration of{{duration}}"
|
||||||
|
AdvSceneSwitcher.action.wait.layout.random="Wait for{{waitType}}duration from{{duration}}to{{duration2}}"
|
||||||
|
AdvSceneSwitcher.action.wait.condition.equals="equals"
|
||||||
|
AdvSceneSwitcher.action.wait.condition.doesNotEqual="does not equal"
|
||||||
|
AdvSceneSwitcher.action.wait.condition.isEmpty="is empty"
|
||||||
|
AdvSceneSwitcher.action.wait.condition.lessThan="is less than"
|
||||||
|
AdvSceneSwitcher.action.wait.condition.greaterThan="is greater than"
|
||||||
|
AdvSceneSwitcher.action.wait.layout.variableCondition="Wait until{{waitType}}{{variable}}{{condition}}{{strValue}}{{numValue}}{{regex}}"
|
||||||
|
AdvSceneSwitcher.action.wait.layout.timeout="Timeout:{{useTimeout}}{{conditionTimeout}}"
|
||||||
|
AdvSceneSwitcher.tempVar.wait.timedOut="Timed out"
|
||||||
|
AdvSceneSwitcher.tempVar.wait.timedOut.description="True if the wait action timed out before the condition was met, false if the condition was met in time"
|
||||||
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"
|
||||||
@@ -925,7 +984,8 @@ AdvSceneSwitcher.action.recording.type.changeOutputFileFormat="Change filename f
|
|||||||
AdvSceneSwitcher.action.recording.type.addChapter="Add chapter"
|
AdvSceneSwitcher.action.recording.type.addChapter="Add chapter"
|
||||||
AdvSceneSwitcher.action.recording.pause.hint="Note that depending on your recording settings you might not be able to pause recording"
|
AdvSceneSwitcher.action.recording.pause.hint="Note that depending on your recording settings you might not be able to pause recording"
|
||||||
AdvSceneSwitcher.action.recording.split.hint="Make sure to enable automatic file splitting in the OBS settings first!"
|
AdvSceneSwitcher.action.recording.split.hint="Make sure to enable automatic file splitting in the OBS settings first!"
|
||||||
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{chapterName}}{{pauseHint}}{{splitHint}}"
|
AdvSceneSwitcher.action.recording.outputNotActive.hint="This change will only take effect when the Recording, Replay Buffer, ... output is not active.\nThe output needs to be restarted for the change to apply."
|
||||||
|
AdvSceneSwitcher.action.recording.layout="{{actions}}{{recordFolder}}{{recordFileFormat}}{{outputNotActiveHelp}}{{chapterName}}{{pauseHint}}{{splitHint}}"
|
||||||
AdvSceneSwitcher.action.replay="Replay buffer"
|
AdvSceneSwitcher.action.replay="Replay buffer"
|
||||||
AdvSceneSwitcher.action.replay.saveWarn="Warning: Saving too frequently might result in the replay buffer not actually being saved!"
|
AdvSceneSwitcher.action.replay.saveWarn="Warning: Saving too frequently might result in the replay buffer not actually being saved!"
|
||||||
AdvSceneSwitcher.action.replay.durationWarn="Warning: Changing the maximum replay time will only apply the next time the replay buffer is started!"
|
AdvSceneSwitcher.action.replay.durationWarn="Warning: Changing the maximum replay time will only apply the next time the replay buffer is started!"
|
||||||
@@ -956,6 +1016,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Any"
|
|||||||
AdvSceneSwitcher.action.sceneVisibility.layout="On{{scenes}}{{actions}}{{sources}}"
|
AdvSceneSwitcher.action.sceneVisibility.layout="On{{scenes}}{{actions}}{{sources}}"
|
||||||
AdvSceneSwitcher.action.sceneVisibility.layout.transition="{{updateTransition}}Set transition to{{transitions}}"
|
AdvSceneSwitcher.action.sceneVisibility.layout.transition="{{updateTransition}}Set transition to{{transitions}}"
|
||||||
AdvSceneSwitcher.action.sceneVisibility.layout.duration="{{updateDuration}}Set transition duration to{{duration}}seconds"
|
AdvSceneSwitcher.action.sceneVisibility.layout.duration="{{updateDuration}}Set transition duration to{{duration}}seconds"
|
||||||
|
AdvSceneSwitcher.action.sceneVisibility.blockUntilTransitionDone="Wait until visibility transition is complete"
|
||||||
AdvSceneSwitcher.action.filter="Filter"
|
AdvSceneSwitcher.action.filter="Filter"
|
||||||
AdvSceneSwitcher.action.filter.type.enable="Enable"
|
AdvSceneSwitcher.action.filter.type.enable="Enable"
|
||||||
AdvSceneSwitcher.action.filter.type.disable="Disable"
|
AdvSceneSwitcher.action.filter.type.disable="Disable"
|
||||||
@@ -967,7 +1028,7 @@ AdvSceneSwitcher.action.filter.refresh.tooltip="Repopulate the filter settings s
|
|||||||
AdvSceneSwitcher.action.filter.entry="On{{sources}}{{actions}}{{filters}}{{refresh}}{{settingsButtons}}"
|
AdvSceneSwitcher.action.filter.entry="On{{sources}}{{actions}}{{filters}}{{refresh}}{{settingsButtons}}"
|
||||||
AdvSceneSwitcher.action.filter.entry.settings="{{settings}}{{settingsInputMethod}}{{settingValue}}{{tempVar}}"
|
AdvSceneSwitcher.action.filter.entry.settings="{{settings}}{{settingsInputMethod}}{{settingValue}}{{tempVar}}"
|
||||||
AdvSceneSwitcher.action.filter.getSettings="Get current settings"
|
AdvSceneSwitcher.action.filter.getSettings="Get current settings"
|
||||||
AdvSceneSwitcher.action.filter.inputMethod.individualManual="Set to fixed value"
|
AdvSceneSwitcher.action.filter.inputMethod.individualManual="Set to value"
|
||||||
AdvSceneSwitcher.action.filter.inputMethod.individualListEntryManual="Set to list entry"
|
AdvSceneSwitcher.action.filter.inputMethod.individualListEntryManual="Set to list entry"
|
||||||
AdvSceneSwitcher.action.filter.inputMethod.individualTempvar="Set to macro property"
|
AdvSceneSwitcher.action.filter.inputMethod.individualTempvar="Set to macro property"
|
||||||
AdvSceneSwitcher.action.filter.inputMethod.json="Set setting JSON string"
|
AdvSceneSwitcher.action.filter.inputMethod.json="Set setting JSON string"
|
||||||
@@ -1003,7 +1064,7 @@ AdvSceneSwitcher.action.source.deinterlaceMode.yadif="Yadif"
|
|||||||
AdvSceneSwitcher.action.source.deinterlaceMode.yadif2x="Yadif 2x"
|
AdvSceneSwitcher.action.source.deinterlaceMode.yadif2x="Yadif 2x"
|
||||||
AdvSceneSwitcher.action.source.deinterlaceOrder.topFieldFirst="Top Field First"
|
AdvSceneSwitcher.action.source.deinterlaceOrder.topFieldFirst="Top Field First"
|
||||||
AdvSceneSwitcher.action.source.deinterlaceOrder.bottomFieldFirst="Bottom Field First"
|
AdvSceneSwitcher.action.source.deinterlaceOrder.bottomFieldFirst="Bottom Field First"
|
||||||
AdvSceneSwitcher.action.source.inputMethod.individualManual="Set to fixed value"
|
AdvSceneSwitcher.action.source.inputMethod.individualManual="Set to value"
|
||||||
AdvSceneSwitcher.action.source.inputMethod.individualListEntryManual="Set to list entry"
|
AdvSceneSwitcher.action.source.inputMethod.individualListEntryManual="Set to list entry"
|
||||||
AdvSceneSwitcher.action.source.inputMethod.individualTempvar="Set to macro property"
|
AdvSceneSwitcher.action.source.inputMethod.individualTempvar="Set to macro property"
|
||||||
AdvSceneSwitcher.action.source.inputMethod.json="Set setting JSON string"
|
AdvSceneSwitcher.action.source.inputMethod.json="Set setting JSON string"
|
||||||
@@ -1076,8 +1137,8 @@ AdvSceneSwitcher.action.macro.type.runActions="Run macro actions"
|
|||||||
AdvSceneSwitcher.action.macro.type.run.conditions.ignore="Do not consider condition state"
|
AdvSceneSwitcher.action.macro.type.run.conditions.ignore="Do not consider condition state"
|
||||||
AdvSceneSwitcher.action.macro.type.run.conditions.true="Only if conditions evaluate to true"
|
AdvSceneSwitcher.action.macro.type.run.conditions.true="Only if conditions evaluate to true"
|
||||||
AdvSceneSwitcher.action.macro.type.run.conditions.false="Only if conditions evaluate to false"
|
AdvSceneSwitcher.action.macro.type.run.conditions.false="Only if conditions evaluate to false"
|
||||||
AdvSceneSwitcher.action.macro.type.run.updateConditionMatchState="Reevaluate the condition state before executing this action"
|
AdvSceneSwitcher.action.macro.type.run.updateConditionMatchState="Check conditions now, even if macro is paused"
|
||||||
AdvSceneSwitcher.action.macro.type.run.updateConditionMatchState.help="The plugin operates in phases:\n * The phase evaluating the macro conditions\n * The phase running macro actions\nMacros executed before this particular action might have side effects on the condition state of macros.\nCheck this option if you want those side effects to be taken into account when evaluating the condition state."
|
AdvSceneSwitcher.action.macro.type.run.updateConditionMatchState.help="The plugin operates in phases:\n * The phase evaluating the macro conditions\n * The phase running macro actions\nMacros executed before this particular action might have side effects on the condition state of macros.\nAdditionally, paused macros are assumed to not match their conditions.\nCheck this option to force a fresh condition evaluation before running this action, regardless of whether the macro is paused."
|
||||||
AdvSceneSwitcher.action.macro.type.run.actionType.regular="actions"
|
AdvSceneSwitcher.action.macro.type.run.actionType.regular="actions"
|
||||||
AdvSceneSwitcher.action.macro.type.run.actionType.else="else-actions"
|
AdvSceneSwitcher.action.macro.type.run.actionType.else="else-actions"
|
||||||
AdvSceneSwitcher.action.macro.type.run.skipWhenPaused="Skip execution when macro is paused"
|
AdvSceneSwitcher.action.macro.type.run.skipWhenPaused="Skip execution when macro is paused"
|
||||||
@@ -1095,6 +1156,12 @@ AdvSceneSwitcher.action.macro.type.nestedMacro="Nested macro"
|
|||||||
AdvSceneSwitcher.action.macro.actionSelectionType.index="at index"
|
AdvSceneSwitcher.action.macro.actionSelectionType.index="at index"
|
||||||
AdvSceneSwitcher.action.macro.actionSelectionType.label="with label"
|
AdvSceneSwitcher.action.macro.actionSelectionType.label="with label"
|
||||||
AdvSceneSwitcher.action.macro.actionSelectionType.id="of action type"
|
AdvSceneSwitcher.action.macro.actionSelectionType.id="of action type"
|
||||||
|
AdvSceneSwitcher.action.loop="Loop"
|
||||||
|
AdvSceneSwitcher.action.loop.maxIterations="Max iterations"
|
||||||
|
AdvSceneSwitcher.action.loop.conditionHelp="Define the loop condition. The actions below will be repeated while these conditions are met.\n\nClick the plus button below to add a new condition."
|
||||||
|
AdvSceneSwitcher.action.loop.actionHelp="Define the actions to perform each iteration.\n\nClick the plus button below to add a new action."
|
||||||
|
AdvSceneSwitcher.tempVar.loop.count="Iteration count"
|
||||||
|
AdvSceneSwitcher.tempVar.loop.count.description="The total number of iterations completed."
|
||||||
AdvSceneSwitcher.action.macro.type.nestedMacro.conditionHelp="This section allows you to define macro conditions.\n\nClick the plus button below to add a new condition."
|
AdvSceneSwitcher.action.macro.type.nestedMacro.conditionHelp="This section allows you to define macro conditions.\n\nClick the plus button below to add a new condition."
|
||||||
AdvSceneSwitcher.action.macro.type.nestedMacro.actionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are met.\n\nClick the plus button below to add a new action."
|
AdvSceneSwitcher.action.macro.type.nestedMacro.actionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are met.\n\nClick the plus button below to add a new action."
|
||||||
AdvSceneSwitcher.action.macro.type.nestedMacro.elseActionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are *not* met.\n\nClick the plus button below to add a new action."
|
AdvSceneSwitcher.action.macro.type.nestedMacro.elseActionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are *not* met.\n\nClick the plus button below to add a new action."
|
||||||
@@ -1274,6 +1341,8 @@ AdvSceneSwitcher.action.variable.type.stringLength="Set to length of string"
|
|||||||
AdvSceneSwitcher.action.variable.type.extractJsonField="Extract JSON field with name"
|
AdvSceneSwitcher.action.variable.type.extractJsonField="Extract JSON field with name"
|
||||||
AdvSceneSwitcher.action.variable.type.queryJson="Query JSON"
|
AdvSceneSwitcher.action.variable.type.queryJson="Query JSON"
|
||||||
AdvSceneSwitcher.action.variable.type.queryJson.info="You can use \"JSONPath\" (RFC 9535) syntax here.\nSo, for example:\n\n • $['books'][0]['category']\n • $.books[0].category\n\nIf the query or the JSON should be invalid, the variable value will not be changed.\nIf the input is valid, the result of the query will always be a JSON array."
|
AdvSceneSwitcher.action.variable.type.queryJson.info="You can use \"JSONPath\" (RFC 9535) syntax here.\nSo, for example:\n\n • $['books'][0]['category']\n • $.books[0].category\n\nIf the query or the JSON should be invalid, the variable value will not be changed.\nIf the input is valid, the result of the query will always be a JSON array."
|
||||||
|
AdvSceneSwitcher.action.variable.type.queryJson.extractSingle="Extract value if single result"
|
||||||
|
AdvSceneSwitcher.action.variable.type.queryJson.extractSingle.info="When enabled and the query returns exactly one result, the value is extracted from the result array and stored directly.\nIf the query matches zero or more than one element, the full JSON array is stored as usual."
|
||||||
AdvSceneSwitcher.action.variable.type.accessJsonArray="Access JSON array at index"
|
AdvSceneSwitcher.action.variable.type.accessJsonArray="Access JSON array at index"
|
||||||
AdvSceneSwitcher.action.variable.type.setToTempvar="Set to macro property"
|
AdvSceneSwitcher.action.variable.type.setToTempvar="Set to macro property"
|
||||||
AdvSceneSwitcher.action.variable.type.setToTempvar.help="This action type will allow you to extract values out of segments of the current macro and assign those values to the selected variable.\nFor example, you can get the current scene name from a scene condition and assign this name to a variable."
|
AdvSceneSwitcher.action.variable.type.setToTempvar.help="This action type will allow you to extract values out of segments of the current macro and assign those values to the selected variable.\nFor example, you can get the current scene name from a scene condition and assign this name to a variable."
|
||||||
@@ -1306,6 +1375,7 @@ AdvSceneSwitcher.action.variable.actionNoVariableSupport="Getting variable value
|
|||||||
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.layout.other="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}{{tempVarsHelp}}{{sceneItemIndex}}{{direction}}{{stringLength}}{{paddingCharSelection}}{{caseType}}{{jsonQuery}}{{jsonQueryHelp}}{{jsonIndex}}"
|
AdvSceneSwitcher.action.variable.layout.other="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}{{tempVarsHelp}}{{sceneItemIndex}}{{direction}}{{stringLength}}{{paddingCharSelection}}{{caseType}}{{jsonQuery}}{{jsonQueryHelp}}{{jsonIndex}}"
|
||||||
|
AdvSceneSwitcher.action.variable.layout.copy="{{actions}}{{variables2}}to{{variables}}"
|
||||||
AdvSceneSwitcher.action.variable.layout.pad="{{actions}}of{{variables}}to length{{stringLength}}by adding{{paddingCharSelection}}to the{{direction}}"
|
AdvSceneSwitcher.action.variable.layout.pad="{{actions}}of{{variables}}to length{{stringLength}}by adding{{paddingCharSelection}}to the{{direction}}"
|
||||||
AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}of{{variables}}to length{{stringLength}}by removing characters from the{{direction}}"
|
AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}of{{variables}}to length{{stringLength}}by removing characters from the{{direction}}"
|
||||||
AdvSceneSwitcher.action.variable.layout.substringIndex="Substring start:{{subStringStart}}Substring size:{{subStringSize}}{{subStringRegex}}"
|
AdvSceneSwitcher.action.variable.layout.substringIndex="Substring start:{{subStringStart}}Substring size:{{subStringSize}}{{subStringRegex}}"
|
||||||
@@ -1345,6 +1415,8 @@ AdvSceneSwitcher.action.twitch.type.channel.info.language.set="Set stream langua
|
|||||||
AdvSceneSwitcher.action.twitch.type.raid.start="Start raid"
|
AdvSceneSwitcher.action.twitch.type.raid.start="Start raid"
|
||||||
AdvSceneSwitcher.action.twitch.type.raid.end="Cancel raid"
|
AdvSceneSwitcher.action.twitch.type.raid.end="Cancel raid"
|
||||||
AdvSceneSwitcher.action.twitch.type.shoutout.send="Send shoutout"
|
AdvSceneSwitcher.action.twitch.type.shoutout.send="Send shoutout"
|
||||||
|
AdvSceneSwitcher.action.twitch.type.poll.start="Start poll"
|
||||||
|
AdvSceneSwitcher.action.twitch.type.poll.end="End poll"
|
||||||
AdvSceneSwitcher.action.twitch.type.shieldMode.start="Enable shield mode"
|
AdvSceneSwitcher.action.twitch.type.shieldMode.start="Enable shield mode"
|
||||||
AdvSceneSwitcher.action.twitch.type.shieldMode.end="Disable shield mode"
|
AdvSceneSwitcher.action.twitch.type.shieldMode.end="Disable shield mode"
|
||||||
AdvSceneSwitcher.action.twitch.type.commercial.start="Start commercial"
|
AdvSceneSwitcher.action.twitch.type.commercial.start="Start commercial"
|
||||||
@@ -1376,6 +1448,7 @@ AdvSceneSwitcher.action.twitch.type.user.vip.add="Add user as VIP"
|
|||||||
AdvSceneSwitcher.action.twitch.type.user.vip.delete="Remove user as VIP"
|
AdvSceneSwitcher.action.twitch.type.user.vip.delete="Remove user as VIP"
|
||||||
AdvSceneSwitcher.action.twitch.type.reward.getInfo="Get channel points reward information"
|
AdvSceneSwitcher.action.twitch.type.reward.getInfo="Get channel points reward information"
|
||||||
AdvSceneSwitcher.action.twitch.type.channel.getInfo="Get channel information"
|
AdvSceneSwitcher.action.twitch.type.channel.getInfo="Get channel information"
|
||||||
|
AdvSceneSwitcher.action.twitch.type.channel.ads.getInfo="Get channel ad schedule"
|
||||||
AdvSceneSwitcher.action.twitch.reward.toggleControl="Toggle reward name / variable selection control"
|
AdvSceneSwitcher.action.twitch.reward.toggleControl="Toggle reward name / variable selection control"
|
||||||
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!"
|
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!"
|
||||||
AdvSceneSwitcher.action.twitch.layout.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{nonModDelayDuration}}{{channel}}{{pointsReward}}"
|
AdvSceneSwitcher.action.twitch.layout.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{nonModDelayDuration}}{{channel}}{{pointsReward}}"
|
||||||
@@ -1389,6 +1462,10 @@ AdvSceneSwitcher.action.twitch.layout.user.ban.row1="Using account{{account}}{{a
|
|||||||
AdvSceneSwitcher.action.twitch.layout.user.ban.row2="on channel{{channel}}for{{userInfoQueryType}}{{userLogin}}{{userId}}reason{{banReason}}"
|
AdvSceneSwitcher.action.twitch.layout.user.ban.row2="on channel{{channel}}for{{userInfoQueryType}}{{userLogin}}{{userId}}reason{{banReason}}"
|
||||||
AdvSceneSwitcher.action.twitch.layout.reward.getInfo.row1="Using account{{account}}{{actions}}for channel{{channel}}"
|
AdvSceneSwitcher.action.twitch.layout.reward.getInfo.row1="Using account{{account}}{{actions}}for channel{{channel}}"
|
||||||
AdvSceneSwitcher.action.twitch.layout.reward.getInfo.row2="{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
|
AdvSceneSwitcher.action.twitch.layout.reward.getInfo.row2="{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
|
||||||
|
AdvSceneSwitcher.action.twitch.layout.poll.start.row1="Using account{{account}}{{actions}}"
|
||||||
|
AdvSceneSwitcher.action.twitch.layout.poll.start.row2="on{{channel}}for{{duration}}"
|
||||||
|
AdvSceneSwitcher.action.twitch.layout.poll.end.row1="Using account{{account}}{{actions}}"
|
||||||
|
AdvSceneSwitcher.action.twitch.layout.poll.end.row2="on{{channel}}{{pollEndStatus}}"
|
||||||
AdvSceneSwitcher.action.twitch.title.title="Enter title"
|
AdvSceneSwitcher.action.twitch.title.title="Enter title"
|
||||||
AdvSceneSwitcher.action.twitch.marker.description="Describe marker"
|
AdvSceneSwitcher.action.twitch.marker.description="Describe marker"
|
||||||
AdvSceneSwitcher.action.twitch.clip.hasDelay="Add a slight delay before capturing the clip"
|
AdvSceneSwitcher.action.twitch.clip.hasDelay="Add a slight delay before capturing the clip"
|
||||||
@@ -1398,6 +1475,9 @@ AdvSceneSwitcher.action.twitch.announcement.blue="Blue"
|
|||||||
AdvSceneSwitcher.action.twitch.announcement.green="Green"
|
AdvSceneSwitcher.action.twitch.announcement.green="Green"
|
||||||
AdvSceneSwitcher.action.twitch.announcement.orange="Orange"
|
AdvSceneSwitcher.action.twitch.announcement.orange="Orange"
|
||||||
AdvSceneSwitcher.action.twitch.announcement.purple="Purple"
|
AdvSceneSwitcher.action.twitch.announcement.purple="Purple"
|
||||||
|
AdvSceneSwitcher.action.twitch.poll.choices="Enter one choice per line (2-10 choices, max 25 characters each)"
|
||||||
|
AdvSceneSwitcher.action.twitch.poll.end.terminated="Terminate (show results)"
|
||||||
|
AdvSceneSwitcher.action.twitch.poll.end.archived="Archive (hide results)"
|
||||||
AdvSceneSwitcher.action.twitch.user.getInfo.queryType.id="User Id"
|
AdvSceneSwitcher.action.twitch.user.getInfo.queryType.id="User Id"
|
||||||
AdvSceneSwitcher.action.twitch.user.getInfo.queryType.login="User login"
|
AdvSceneSwitcher.action.twitch.user.getInfo.queryType.login="User login"
|
||||||
AdvSceneSwitcher.action.twitch.tags.add="Add Channel Tag"
|
AdvSceneSwitcher.action.twitch.tags.add="Add Channel Tag"
|
||||||
@@ -1582,6 +1662,8 @@ AdvSceneSwitcher.actionQueues.invalid="Invalid action queue selection"
|
|||||||
AdvSceneSwitcher.actionQueues.name="Name:"
|
AdvSceneSwitcher.actionQueues.name="Name:"
|
||||||
AdvSceneSwitcher.actionQueues.runOnStartup="Run action queue when starting the plugin"
|
AdvSceneSwitcher.actionQueues.runOnStartup="Run action queue when starting the plugin"
|
||||||
AdvSceneSwitcher.actionQueues.resolveVariablesOnAdd="Resolve variables when action is inserted into the queue"
|
AdvSceneSwitcher.actionQueues.resolveVariablesOnAdd="Resolve variables when action is inserted into the queue"
|
||||||
|
AdvSceneSwitcher.actionQueues.cloneVariableContext="Clone variable context when action is inserted into the queue"
|
||||||
|
AdvSceneSwitcher.actionQueues.cloneVariableContext.tooltip="Captures the current values of all variables when the action is added to the queue.\nDuring execution, actions read from and write to this snapshot instead of the global variables.\nUnlike \"Resolve variables\", changes made by one action in the queue are visible to subsequent actions."
|
||||||
AdvSceneSwitcher.actionQueues.running="Queue is running"
|
AdvSceneSwitcher.actionQueues.running="Queue is running"
|
||||||
AdvSceneSwitcher.actionQueues.stopped="Queue is stopped"
|
AdvSceneSwitcher.actionQueues.stopped="Queue is stopped"
|
||||||
AdvSceneSwitcher.actionQueues.start="Start action queue"
|
AdvSceneSwitcher.actionQueues.start="Start action queue"
|
||||||
@@ -1869,8 +1951,10 @@ AdvSceneSwitcher.tempVar.twitch.user_login.subscribe.message="Subscriber Twitch
|
|||||||
AdvSceneSwitcher.tempVar.twitch.user_login.subscribe.message.description="The user login of the user who sent a resubscription chat message."
|
AdvSceneSwitcher.tempVar.twitch.user_login.subscribe.message.description="The user login of the user who sent a resubscription chat message."
|
||||||
AdvSceneSwitcher.tempVar.twitch.user_name.subscribe.message="Subscriber Twitch user name"
|
AdvSceneSwitcher.tempVar.twitch.user_name.subscribe.message="Subscriber Twitch user name"
|
||||||
AdvSceneSwitcher.tempVar.twitch.user_name.subscribe.message.description="The user display name of the user who a resubscription chat message."
|
AdvSceneSwitcher.tempVar.twitch.user_name.subscribe.message.description="The user display name of the user who a resubscription chat message."
|
||||||
AdvSceneSwitcher.tempVar.twitch.message.subscribe="Subscription message"
|
AdvSceneSwitcher.tempVar.twitch.message.subscribe="Subscription message (JSON)"
|
||||||
AdvSceneSwitcher.tempVar.twitch.message.subscribe.description="An object that contains the resubscription message and emote information needed to recreate the message."
|
AdvSceneSwitcher.tempVar.twitch.message.subscribe.description="A JSON object containing the resubscription message text and emote information. Use 'Subscription message text' to get the plain text."
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.message_text.subscribe="Subscription message text"
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.message_text.subscribe.description="The plain text of the resubscription message, with emotes replaced by their names."
|
||||||
AdvSceneSwitcher.tempVar.twitch.cumulative_months.subscribe="Number of months subscribed"
|
AdvSceneSwitcher.tempVar.twitch.cumulative_months.subscribe="Number of months subscribed"
|
||||||
AdvSceneSwitcher.tempVar.twitch.cumulative_months.subscribe.description="The total number of months the user has been subscribed to the channel."
|
AdvSceneSwitcher.tempVar.twitch.cumulative_months.subscribe.description="The total number of months the user has been subscribed to the channel."
|
||||||
AdvSceneSwitcher.tempVar.twitch.streak_months.subscribe="Subscription streak"
|
AdvSceneSwitcher.tempVar.twitch.streak_months.subscribe="Subscription streak"
|
||||||
@@ -2259,6 +2343,19 @@ AdvSceneSwitcher.tempVar.twitch.requester_user_login.commercial.description="The
|
|||||||
AdvSceneSwitcher.tempVar.twitch.requester_user_name.commercial="Requester user name"
|
AdvSceneSwitcher.tempVar.twitch.requester_user_name.commercial="Requester user name"
|
||||||
AdvSceneSwitcher.tempVar.twitch.requester_user_name.commercial.description="The display name of the user that requested the ad."
|
AdvSceneSwitcher.tempVar.twitch.requester_user_name.commercial.description="The display name of the user that requested the ad."
|
||||||
|
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.snooze_count.ads="Snooze count"
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.snooze_count.ads.description="The number of snoozes available for the broadcaster."
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.snooze_refresh_at.ads="Snooze refresh time"
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.snooze_refresh_at.ads.description="The UTC timestamp of when the broadcaster will have an additional snooze available, in RFC3339 format."
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.next_ad_at.ads="Next ad time"
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.next_ad_at.ads.description="The UTC timestamp of when the channel's next scheduled ad break will begin, in RFC3339 format."
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.duration.ads="Ad duration"
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.duration.ads.description="The length in seconds of the scheduled ad break."
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.last_ad_at.ads="Last ad time"
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.last_ad_at.ads.description="The UTC timestamp of the channel's last ad break, in RFC3339 format."
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.preroll_free_time.ads="Pre-roll free time"
|
||||||
|
AdvSceneSwitcher.tempVar.twitch.preroll_free_time.ads.description="The amount of time in seconds the broadcaster must wait before running another commercial."
|
||||||
|
|
||||||
AdvSceneSwitcher.tempVar.audio.output_volume="Output volume"
|
AdvSceneSwitcher.tempVar.audio.output_volume="Output volume"
|
||||||
AdvSceneSwitcher.tempVar.audio.output_volume.description="The volume the audio source is outputting."
|
AdvSceneSwitcher.tempVar.audio.output_volume.description="The volume the audio source is outputting."
|
||||||
AdvSceneSwitcher.tempVar.audio.configured_volume="Configured volume"
|
AdvSceneSwitcher.tempVar.audio.configured_volume="Configured volume"
|
||||||
@@ -2280,6 +2377,14 @@ AdvSceneSwitcher.tempVar.scene.position="Position in scenes list"
|
|||||||
|
|
||||||
AdvSceneSwitcher.tempVar.window.window="Window title"
|
AdvSceneSwitcher.tempVar.window.window="Window title"
|
||||||
AdvSceneSwitcher.tempVar.window.window.description="The window title of the matching window."
|
AdvSceneSwitcher.tempVar.window.window.description="The window title of the matching window."
|
||||||
|
AdvSceneSwitcher.tempVar.window.windowX="Window X position"
|
||||||
|
AdvSceneSwitcher.tempVar.window.windowX.description="The X position (left edge) of the matching window in screen coordinates."
|
||||||
|
AdvSceneSwitcher.tempVar.window.windowY="Window Y position"
|
||||||
|
AdvSceneSwitcher.tempVar.window.windowY.description="The Y position (top edge) of the matching window in screen coordinates."
|
||||||
|
AdvSceneSwitcher.tempVar.window.windowWidth="Window width"
|
||||||
|
AdvSceneSwitcher.tempVar.window.windowWidth.description="The width of the matching window in pixels."
|
||||||
|
AdvSceneSwitcher.tempVar.window.windowHeight="Window height"
|
||||||
|
AdvSceneSwitcher.tempVar.window.windowHeight.description="The height of the matching window in pixels."
|
||||||
AdvSceneSwitcher.tempVar.window.windowClass="Window class"
|
AdvSceneSwitcher.tempVar.window.windowClass="Window class"
|
||||||
AdvSceneSwitcher.tempVar.window.windowClass.description="The window class of the matching window."
|
AdvSceneSwitcher.tempVar.window.windowClass.description="The window class of the matching window."
|
||||||
AdvSceneSwitcher.tempVar.window.windowText="Window text"
|
AdvSceneSwitcher.tempVar.window.windowText="Window text"
|
||||||
@@ -2345,6 +2450,14 @@ AdvSceneSwitcher.tempVar.video.similarity="Similarity Rating"
|
|||||||
AdvSceneSwitcher.tempVar.video.similarity.description="Values range from 0 to 1, where 0 means dissimilar and 1 means highly similar."
|
AdvSceneSwitcher.tempVar.video.similarity.description="Values range from 0 to 1, where 0 means dissimilar and 1 means highly similar."
|
||||||
AdvSceneSwitcher.tempVar.video.patternCount="Pattern count"
|
AdvSceneSwitcher.tempVar.video.patternCount="Pattern count"
|
||||||
AdvSceneSwitcher.tempVar.video.patternCount.description="The number of times the given pattern has been found in a given video input frame."
|
AdvSceneSwitcher.tempVar.video.patternCount.description="The number of times the given pattern has been found in a given video input frame."
|
||||||
|
AdvSceneSwitcher.tempVar.video.matchX="Match X coordinate"
|
||||||
|
AdvSceneSwitcher.tempVar.video.matchX.description="The X coordinate of the top-left corner of the best pattern match.\nIf the 'check area' option is enabled, the coordinate is relative to the video source, not the area.\nSet to -1 if no match was found."
|
||||||
|
AdvSceneSwitcher.tempVar.video.matchY="Match Y coordinate"
|
||||||
|
AdvSceneSwitcher.tempVar.video.matchY.description="The Y coordinate of the top-left corner of the best pattern match.\nIf the 'check area' option is enabled, the coordinate is relative to the video source, not the area.\nSet to -1 if no match was found."
|
||||||
|
AdvSceneSwitcher.tempVar.video.matchWidth="Match width"
|
||||||
|
AdvSceneSwitcher.tempVar.video.matchWidth.description="The width of the matched pattern in pixels."
|
||||||
|
AdvSceneSwitcher.tempVar.video.matchHeight="Match height"
|
||||||
|
AdvSceneSwitcher.tempVar.video.matchHeight.description="The height of the matched pattern in pixels."
|
||||||
AdvSceneSwitcher.tempVar.video.objectCount="Object count"
|
AdvSceneSwitcher.tempVar.video.objectCount="Object count"
|
||||||
AdvSceneSwitcher.tempVar.video.objectCount.description="The number of objects the given model has identified in a given video input frame."
|
AdvSceneSwitcher.tempVar.video.objectCount.description="The number of objects the given model has identified in a given video input frame."
|
||||||
AdvSceneSwitcher.tempVar.video.brightness="Average brightness"
|
AdvSceneSwitcher.tempVar.video.brightness="Average brightness"
|
||||||
@@ -2379,6 +2492,9 @@ AdvSceneSwitcher.tempVar.streaming.serviceName.description="The name of the stre
|
|||||||
AdvSceneSwitcher.tempVar.clipboard.text="Clipboard text"
|
AdvSceneSwitcher.tempVar.clipboard.text="Clipboard text"
|
||||||
AdvSceneSwitcher.tempVar.clipboard.text.description="The text contained in the clipboard.\nWill be empty if the clipboard does not contain text."
|
AdvSceneSwitcher.tempVar.clipboard.text.description="The text contained in the clipboard.\nWill be empty if the clipboard does not contain text."
|
||||||
|
|
||||||
|
AdvSceneSwitcher.tempVar.speech.speech="Transcribed speech"
|
||||||
|
AdvSceneSwitcher.tempVar.speech.speech.description="The text transcribed from the last audio buffer. Only populated when the condition matched."
|
||||||
|
|
||||||
AdvSceneSwitcher.tempVar.file.content="File content"
|
AdvSceneSwitcher.tempVar.file.content="File content"
|
||||||
AdvSceneSwitcher.tempVar.file.date="File modification date"
|
AdvSceneSwitcher.tempVar.file.date="File modification date"
|
||||||
AdvSceneSwitcher.tempVar.file.basename="File basename"
|
AdvSceneSwitcher.tempVar.file.basename="File basename"
|
||||||
@@ -2538,6 +2654,9 @@ 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"
|
||||||
|
|
||||||
|
AdvSceneSwitcher.keepSourceActive="Keep source active"
|
||||||
|
AdvSceneSwitcher.keepSourceActive.help="When a source is not visible in the active scene it becomes inactive, which can cause it to stop producing output (e.g. video frames or game capture hooks).\nEnabling this option forces the source to remain active at all times, ensuring it continues to produce output even when not displayed.\n\nNote: Keeping a source permanently active may increase CPU and GPU usage, as the source continues to run even when not shown."
|
||||||
|
|
||||||
AdvSceneSwitcher.settings.suffix.type.invalid=" (Invalid)"
|
AdvSceneSwitcher.settings.suffix.type.invalid=" (Invalid)"
|
||||||
AdvSceneSwitcher.settings.suffix.type.bool=" (Bool)"
|
AdvSceneSwitcher.settings.suffix.type.bool=" (Bool)"
|
||||||
AdvSceneSwitcher.settings.suffix.type.int=" (Int)"
|
AdvSceneSwitcher.settings.suffix.type.int=" (Int)"
|
||||||
@@ -2642,7 +2761,28 @@ FirstRunWizard.windowTitle="Advanced Scene Switcher - Setup Wizard"
|
|||||||
|
|
||||||
FirstRunWizard.welcome.title="Welcome to Advanced Scene Switcher"
|
FirstRunWizard.welcome.title="Welcome to Advanced Scene Switcher"
|
||||||
FirstRunWizard.welcome.subtitle="Let's create your first automation in a few easy steps."
|
FirstRunWizard.welcome.subtitle="Let's create your first automation in a few easy steps."
|
||||||
FirstRunWizard.welcome.body="<p>Advanced Scene Switcher lets you build <b>Macros</b> - rules of the form:</p><blockquote><i>When [condition] -> perform [action]</i></blockquote><p>This wizard creates a macro that <b>switches to a chosen OBS scene whenever a specific window comes into focus</b>.</p><p>You can skip at any time. Re-open this wizard later from the <b>General</b> tab inside the Advanced Scene Switcher dialog.</p>"
|
FirstRunWizard.welcome.body="<p>Advanced Scene Switcher lets you build <b>Macros</b> - rules of the form:</p><blockquote><i>When [condition] -> perform [action]</i></blockquote><p>This wizard will guide you through creating your first automation. On the next page, choose the type of macro you want to set up.</p><p>You can skip at any time. Re-open this wizard later from the <b>General</b> tab inside the Advanced Scene Switcher dialog.</p>"
|
||||||
|
|
||||||
|
FirstRunWizard.template.title="Choose an Automation Type"
|
||||||
|
FirstRunWizard.template.subtitle="Select the kind of macro you would like to create."
|
||||||
|
FirstRunWizard.template.window="Switch to a scene when a window comes into focus"
|
||||||
|
FirstRunWizard.template.sequence="Run a timed sequence of scene switches"
|
||||||
|
FirstRunWizard.template.audio="Show or hide a source based on audio activity"
|
||||||
|
|
||||||
|
FirstRunWizard.audio.source.title="Choose an Audio Source"
|
||||||
|
FirstRunWizard.audio.source.subtitle="Select the audio source to monitor and configure when it should be considered active."
|
||||||
|
FirstRunWizard.audio.source.sourceRow="Audio source:{{source}}"
|
||||||
|
FirstRunWizard.audio.source.thresholdRow="Show when volume is above{{threshold}}for{{duration}}"
|
||||||
|
|
||||||
|
FirstRunWizard.audio.target.title="Choose a Target Source"
|
||||||
|
FirstRunWizard.audio.target.subtitle="Select the source to show when audio is active and hide when it is not."
|
||||||
|
FirstRunWizard.audio.target.sourceRow="Source to show/hide:{{source}}"
|
||||||
|
|
||||||
|
FirstRunWizard.audio.review.title="Review Your Macro"
|
||||||
|
FirstRunWizard.audio.review.subtitle="Click Back to make changes, or Finish to create the macro."
|
||||||
|
FirstRunWizard.audio.review.summary="<b>When</b> <i>%1</i> output volume is above <b>%2 dB</b> for <b>%3</b>:<br><br> <b>Show</b> <i>%4</i> on the current scene<br><br><b>Otherwise:</b><br><br> <b>Hide</b> <i>%4</i> on the current scene"
|
||||||
|
FirstRunWizard.audio.review.errorTitle="Macro Creation Failed"
|
||||||
|
FirstRunWizard.audio.review.errorBody="The audio macro could not be created. Please check the OBS log for details."
|
||||||
|
|
||||||
FirstRunWizard.scene.title="Choose a Target Scene"
|
FirstRunWizard.scene.title="Choose a Target Scene"
|
||||||
FirstRunWizard.scene.subtitle="Which OBS scene should become active when your chosen window comes into focus?"
|
FirstRunWizard.scene.subtitle="Which OBS scene should become active when your chosen window comes into focus?"
|
||||||
@@ -2663,6 +2803,26 @@ FirstRunWizard.review.summary="<b>Macro name:</b> Window -> %1<br><br><b>Conditi
|
|||||||
FirstRunWizard.review.errorTitle="Macro Creation Failed"
|
FirstRunWizard.review.errorTitle="Macro Creation Failed"
|
||||||
FirstRunWizard.review.errorBody="The macro could not be created automatically.\n\nYou can create it manually on the Macros tab:\n Condition: Window -> contains \"%1\"\n Action: Switch scene -> \"%2\""
|
FirstRunWizard.review.errorBody="The macro could not be created automatically.\n\nYou can create it manually on the Macros tab:\n Condition: Window -> contains \"%1\"\n Action: Switch scene -> \"%2\""
|
||||||
|
|
||||||
|
FirstRunWizard.seqTrigger.title="Choose a Trigger Scene"
|
||||||
|
FirstRunWizard.seqTrigger.subtitle="The sequence will start when you have been on this scene for the specified duration."
|
||||||
|
FirstRunWizard.seqTrigger.scene="Trigger scene:{{scene}}"
|
||||||
|
FirstRunWizard.seqTrigger.delay="Start sequence after{{duration}}"
|
||||||
|
|
||||||
|
FirstRunWizard.seqScenes.title="Build Your Sequence"
|
||||||
|
FirstRunWizard.seqScenes.subtitle="Add at least two scenes. The plugin will switch through them in order, waiting the configured time between each switch."
|
||||||
|
FirstRunWizard.seqScenes.triggerInfo="When on \"%1\" for %2, switch to:"
|
||||||
|
FirstRunWizard.seqScenes.addScene="Add scene"
|
||||||
|
FirstRunWizard.seqScenes.removeTooltip="Remove this scene from the sequence"
|
||||||
|
FirstRunWizard.seqScenes.switchTo="Switch to{{scene}}"
|
||||||
|
FirstRunWizard.seqScenes.thenWait="then wait{{duration}}"
|
||||||
|
|
||||||
|
FirstRunWizard.seqReview.title="Review Your Sequence"
|
||||||
|
FirstRunWizard.seqReview.subtitle="Click Back to make changes, or Finish to create the macro."
|
||||||
|
FirstRunWizard.seqReview.trigger="When on <b>%1</b> for at least <b>%2</b>:"
|
||||||
|
FirstRunWizard.seqReview.step="After %1, switch to %2"
|
||||||
|
FirstRunWizard.seqReview.errorTitle="Macro Creation Failed"
|
||||||
|
FirstRunWizard.seqReview.errorBody="The sequence macro could not be created. Please check the OBS log for details."
|
||||||
|
|
||||||
FirstRunWizard.done.title="You're all set!"
|
FirstRunWizard.done.title="You're all set!"
|
||||||
FirstRunWizard.done.subtitle="Your first macro has been created and is now active."
|
FirstRunWizard.done.subtitle="Your first macro has been created and is now active."
|
||||||
FirstRunWizard.done.body="<p>You can view and edit it on the <b>Macros</b> tab of the Advanced Scene Switcher dialog.</p><p>To learn more:</p><ul><li><a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\">Plugin wiki</a></li><li><a href=\"https://obsproject.com/forum/resources/automatic-scene-switching.395/\">OBS forum thread</a></li></ul>"
|
FirstRunWizard.done.body="<p>You can view and edit it on the <b>Macros</b> tab of the Advanced Scene Switcher dialog.</p><p>To learn more:</p><ul><li><a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\">Plugin wiki</a></li><li><a href=\"https://obsproject.com/forum/resources/automatic-scene-switching.395/\">OBS forum thread</a></li></ul>"
|
||||||
@@ -2705,6 +2865,64 @@ Basic.Settings.Video.FPSCommon="Common FPS Values"
|
|||||||
Basic.Settings.Video.FPSInteger="Integer FPS Value"
|
Basic.Settings.Video.FPSInteger="Integer FPS Value"
|
||||||
Basic.Settings.Video.FPSFraction="Fractional FPS Value"
|
Basic.Settings.Video.FPSFraction="Fractional FPS Value"
|
||||||
|
|
||||||
|
# Calendar widget
|
||||||
|
AdvSceneSwitcher.calendar.today="Today"
|
||||||
|
AdvSceneSwitcher.calendar.month="Month"
|
||||||
|
AdvSceneSwitcher.calendar.week="Week"
|
||||||
|
AdvSceneSwitcher.calendar.day="Day"
|
||||||
|
AdvSceneSwitcher.calendar.moreEvents="+%1 more"
|
||||||
|
|
||||||
|
# Macro Schedule tab
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.title="Macro Schedule"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.help="No scheduled entries.\nClick '+' to schedule a macro."
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.add="Add"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.add.tooltip="Add schedule entry"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.edit="Edit"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.remove="Remove"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.remove.tooltip="Remove schedule entry"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.enable="Enable"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.disable="Disable"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.showAll="Show All"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.column.name="Name"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.column.macro="Macro"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.column.schedule="Schedule"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.column.nextTrigger="Next Trigger"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.column.status="Status"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.remove.confirm="Are you sure you want to remove \"%1\"?"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.remove.confirmMultiple="Are you sure you want to remove %1 entries?"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.status.active="Active"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.status.disabled="Disabled"
|
||||||
|
AdvSceneSwitcher.macroScheduleTab.status.expired="Expired"
|
||||||
|
|
||||||
|
# Macro Schedule entry dialog
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.dialog.title.add="Add Schedule Entry"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.dialog.title.edit="Edit Schedule Entry"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.name="Name (optional):"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.name.placeholder="e.g. Daily stream start"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.macro="Macro:"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.startDateTime="Start:"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.endDate="End date:"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.endDate.help="Controls how far the entry spans visually in the calendar and optionally triggers an action when the date is reached."
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.endDate.action.none="No action"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.endDate.action.disableEntry="Disable schedule entry"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.endDate.action.pauseMacro="Pause macro"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.endDate.action.stopMacro="Stop macro"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.repeat="Repeat"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.repeat.once="Once"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.repeat.every="Every %1"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.repeat.interval="Repeat interval:"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.noMacro="(no macro)"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.repeatEnd="End repeat:"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.repeatEnd.never="Never"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.repeatEnd.afterNTimes="After"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.repeatEnd.afterNTimes.suffix="occurrence(s)"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.repeatEnd.untilDate="Until"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.color="Color:"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.color.dialog="Pick entry color"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.checkConditions="Check macro conditions before running"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.runElseActionsOnConditionFailure="Run else actions if conditions are not met"
|
||||||
|
AdvSceneSwitcher.macroScheduleEntry.enabled="Enabled"
|
||||||
|
|
||||||
# Legacy tabs below - please don't waste your time adding translations for these :)
|
# Legacy tabs below - please don't waste your time adding translations for these :)
|
||||||
# Transition Tab
|
# Transition Tab
|
||||||
AdvSceneSwitcher.transitionTab.title="Transition"
|
AdvSceneSwitcher.transitionTab.title="Transition"
|
||||||
@@ -2889,3 +3107,4 @@ AdvSceneSwitcher.sceneGroupTab.defaultname="Scene Group %1"
|
|||||||
AdvSceneSwitcher.sceneGroupTab.exists="Scene Group or Scene name exists already"
|
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.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."
|
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."
|
||||||
|
AdvSceneSwitcher.kwin.flatpakPermissionWarning="Window detection via KWin is unavailable.\nThe OBS Studio Flatpak requires additional permissions to enable this feature.\nSee the Advanced Scene Switcher wiki for details."
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ AdvSceneSwitcher.macroTab.highlightTrueConditions="Resaltar condiciones de la ma
|
|||||||
AdvSceneSwitcher.macroTab.highlightPerformedActions="Resaltar acciones realizadas recientemente de la macro seleccionada actualmente"
|
AdvSceneSwitcher.macroTab.highlightPerformedActions="Resaltar acciones realizadas recientemente de la macro seleccionada actualmente"
|
||||||
|
|
||||||
; Lógica de macros
|
; Lógica de macros
|
||||||
AdvSceneSwitcher.logic.none="Omitir entrada"
|
|
||||||
AdvSceneSwitcher.logic.and="Y"
|
AdvSceneSwitcher.logic.and="Y"
|
||||||
AdvSceneSwitcher.logic.or="O"
|
AdvSceneSwitcher.logic.or="O"
|
||||||
AdvSceneSwitcher.logic.andNot="Y no"
|
AdvSceneSwitcher.logic.andNot="Y no"
|
||||||
@@ -313,8 +312,8 @@ AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Espere hasta que se comp
|
|||||||
AdvSceneSwitcher.action.wait="Esperar"
|
AdvSceneSwitcher.action.wait="Esperar"
|
||||||
AdvSceneSwitcher.action.wait.type.fixed="fijo"
|
AdvSceneSwitcher.action.wait.type.fixed="fijo"
|
||||||
AdvSceneSwitcher.action.wait.type.random="aleatorio"
|
AdvSceneSwitcher.action.wait.type.random="aleatorio"
|
||||||
AdvSceneSwitcher.action.wait.entry.fixed="Espere {{waitType}} de {{duration}}"
|
AdvSceneSwitcher.action.wait.layout.fixed="Espere {{waitType}} de {{duration}}"
|
||||||
AdvSceneSwitcher.action.wait.entry.random="Espere {{waitType}} de {{duration}} a {{duration2}}"
|
AdvSceneSwitcher.action.wait.layout.random="Espere {{waitType}} de {{duration}} a {{duration2}}"
|
||||||
AdvSceneSwitcher.action.audio="Audio"
|
AdvSceneSwitcher.action.audio="Audio"
|
||||||
AdvSceneSwitcher.action.audio.type.mute="Silencio"
|
AdvSceneSwitcher.action.audio.type.mute="Silencio"
|
||||||
AdvSceneSwitcher.action.audio.type.unmute="Activar silencio"
|
AdvSceneSwitcher.action.audio.type.unmute="Activar silencio"
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ AdvSceneSwitcher.macroList.deleted="supprimé"
|
|||||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" est déjà sélectionné !"
|
AdvSceneSwitcher.macroList.duplicate="\"%1\" est déjà sélectionné !"
|
||||||
|
|
||||||
; Macro Logic
|
; Macro Logic
|
||||||
AdvSceneSwitcher.logic.none="Ignorer l'entrée"
|
|
||||||
AdvSceneSwitcher.logic.and="Et"
|
AdvSceneSwitcher.logic.and="Et"
|
||||||
AdvSceneSwitcher.logic.or="Ou"
|
AdvSceneSwitcher.logic.or="Ou"
|
||||||
AdvSceneSwitcher.logic.andNot="Et pas"
|
AdvSceneSwitcher.logic.andNot="Et pas"
|
||||||
@@ -258,10 +257,10 @@ AdvSceneSwitcher.condition.video.layout.modelPath="Données du modèle (classifi
|
|||||||
AdvSceneSwitcher.condition.video.layout.minNeighbor="Nombre minimum de voisins :{{minNeighbors}}"
|
AdvSceneSwitcher.condition.video.layout.minNeighbor="Nombre minimum de voisins :{{minNeighbors}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Réduire la charge CPU en effectuant la vérification uniquement toutes les{{throttleCount}}millisecondes"
|
AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Réduire la charge CPU en effectuant la vérification uniquement toutes les{{throttleCount}}millisecondes"
|
||||||
AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Effectuer la vérification uniquement dans la zone"
|
AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Effectuer la vérification uniquement dans la zone"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrColorPick="Vérifier la couleur du texte :{{textColor}}{{selectColor}}"
|
AdvSceneSwitcher.condition.video.layout.ocrColorPick="Vérifier la couleur du texte :{{color}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrTextType="Vérifier le type de texte :{{textType}}"
|
AdvSceneSwitcher.condition.video.layout.ocrTextType="Vérifier le type de texte :{{textType}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrLanguage="Vérifier la langue :{{languageCode}}"
|
AdvSceneSwitcher.condition.video.layout.ocrLanguage="Vérifier la langue :{{languageCode}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.color="Vérifier la couleur :{{color}}{{selectColor}}"
|
AdvSceneSwitcher.condition.video.layout.color="Vérifier la couleur :{{color}}"
|
||||||
AdvSceneSwitcher.condition.video.minSize="Taille minimale :"
|
AdvSceneSwitcher.condition.video.minSize="Taille minimale :"
|
||||||
AdvSceneSwitcher.condition.video.maxSize="Taille maximale :"
|
AdvSceneSwitcher.condition.video.maxSize="Taille maximale :"
|
||||||
AdvSceneSwitcher.condition.video.selectArea="Sélectionner la zone"
|
AdvSceneSwitcher.condition.video.selectArea="Sélectionner la zone"
|
||||||
@@ -461,8 +460,8 @@ AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Attendre que la transiti
|
|||||||
AdvSceneSwitcher.action.wait="Attendre"
|
AdvSceneSwitcher.action.wait="Attendre"
|
||||||
AdvSceneSwitcher.action.wait.type.fixed="fixe"
|
AdvSceneSwitcher.action.wait.type.fixed="fixe"
|
||||||
AdvSceneSwitcher.action.wait.type.random="aléatoire"
|
AdvSceneSwitcher.action.wait.type.random="aléatoire"
|
||||||
AdvSceneSwitcher.action.wait.entry.fixed="Attendre pour{{waitType}}une durée de{{duration}}"
|
AdvSceneSwitcher.action.wait.layout.fixed="Attendre pour{{waitType}}une durée de{{duration}}"
|
||||||
AdvSceneSwitcher.action.wait.entry.random="Attendre pour{{waitType}}une durée de{{duration}}à{{duration2}}"
|
AdvSceneSwitcher.action.wait.layout.random="Attendre pour{{waitType}}une durée de{{duration}}à{{duration2}}"
|
||||||
AdvSceneSwitcher.action.audio="Audio"
|
AdvSceneSwitcher.action.audio="Audio"
|
||||||
AdvSceneSwitcher.action.audio.type.mute="Muet"
|
AdvSceneSwitcher.action.audio.type.mute="Muet"
|
||||||
AdvSceneSwitcher.action.audio.type.unmute="Activer le son"
|
AdvSceneSwitcher.action.audio.type.unmute="Activer le son"
|
||||||
|
|||||||
@@ -275,7 +275,6 @@ AdvSceneSwitcher.macroList.deleted="削除"
|
|||||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" はすでに選択されています!"
|
AdvSceneSwitcher.macroList.duplicate="\"%1\" はすでに選択されています!"
|
||||||
|
|
||||||
# Macro Logic
|
# Macro Logic
|
||||||
AdvSceneSwitcher.logic.none="入力無視"
|
|
||||||
; AdvSceneSwitcher.logic.and="And"
|
; AdvSceneSwitcher.logic.and="And"
|
||||||
; AdvSceneSwitcher.logic.or="Or"
|
; AdvSceneSwitcher.logic.or="Or"
|
||||||
; AdvSceneSwitcher.logic.andNot="And not"
|
; AdvSceneSwitcher.logic.andNot="And not"
|
||||||
@@ -415,12 +414,12 @@ AdvSceneSwitcher.condition.video.layout.minNeighbor="最小近傍数:{{minNeighb
|
|||||||
AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}:{{throttleCount}}ミリ秒ごとにのみパフォーマンスチェックを実行することでCPU負荷を軽減します"
|
AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}:{{throttleCount}}ミリ秒ごとにのみパフォーマンスチェックを実行することでCPU負荷を軽減します"
|
||||||
AdvSceneSwitcher.condition.video.layout.checkAreaEnable="エリア内のみチェックを実施"
|
AdvSceneSwitcher.condition.video.layout.checkAreaEnable="エリア内のみチェックを実施"
|
||||||
; AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
|
; AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrColorPick="テキストカラーの確認:{{textColor}}{{selectColor}}"
|
AdvSceneSwitcher.condition.video.layout.ocrColorPick="テキストカラーの確認:{{color}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrTextType="テキストタイプの確認:{{textType}}"
|
AdvSceneSwitcher.condition.video.layout.ocrTextType="テキストタイプの確認:{{textType}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrBaseDir="Tesseractベースディレクトリ:{{tesseractBaseDir}}"
|
AdvSceneSwitcher.condition.video.layout.ocrBaseDir="Tesseractベースディレクトリ:{{tesseractBaseDir}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrLanguage="言語の確認:{{languageCode}}"
|
AdvSceneSwitcher.condition.video.layout.ocrLanguage="言語の確認:{{languageCode}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrConfig="設定ファイル:{{configFile}}{{openConfigFile}}{{reloadConfig}}{{configFileHint}}"
|
AdvSceneSwitcher.condition.video.layout.ocrConfig="設定ファイル:{{configFile}}{{openConfigFile}}{{reloadConfig}}{{configFileHint}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.color="カラーを確認してください:{{color}}{{selectColor}}"
|
AdvSceneSwitcher.condition.video.layout.color="カラーを確認してください:{{color}}"
|
||||||
AdvSceneSwitcher.condition.video.minSize="最小サイズ:"
|
AdvSceneSwitcher.condition.video.minSize="最小サイズ:"
|
||||||
AdvSceneSwitcher.condition.video.maxSize="最大サイズ:"
|
AdvSceneSwitcher.condition.video.maxSize="最大サイズ:"
|
||||||
AdvSceneSwitcher.condition.video.selectArea="範囲指定"
|
AdvSceneSwitcher.condition.video.selectArea="範囲指定"
|
||||||
@@ -801,8 +800,8 @@ AdvSceneSwitcher.action.scene.blockUntilTransitionDone="目的のシーンへの
|
|||||||
; AdvSceneSwitcher.action.wait="Wait"
|
; AdvSceneSwitcher.action.wait="Wait"
|
||||||
AdvSceneSwitcher.action.wait.type.fixed="指定"
|
AdvSceneSwitcher.action.wait.type.fixed="指定"
|
||||||
AdvSceneSwitcher.action.wait.type.random="ランダム"
|
AdvSceneSwitcher.action.wait.type.random="ランダム"
|
||||||
AdvSceneSwitcher.action.wait.entry.fixed="{{waitType}}期間{{duration}}待機します"
|
AdvSceneSwitcher.action.wait.layout.fixed="{{waitType}}期間{{duration}}待機します"
|
||||||
AdvSceneSwitcher.action.wait.entry.random="{{duration}}から{{duration2}}までの{{waitType}}期間待ちます"
|
AdvSceneSwitcher.action.wait.layout.random="{{duration}}から{{duration2}}までの{{waitType}}期間待ちます"
|
||||||
AdvSceneSwitcher.action.audio="音声"
|
AdvSceneSwitcher.action.audio="音声"
|
||||||
AdvSceneSwitcher.action.audio.type.mute="ミュート"
|
AdvSceneSwitcher.action.audio.type.mute="ミュート"
|
||||||
AdvSceneSwitcher.action.audio.type.unmute="ミュート解除"
|
AdvSceneSwitcher.action.audio.type.unmute="ミュート解除"
|
||||||
@@ -831,7 +830,6 @@ AdvSceneSwitcher.action.recording.type.changeOutputFolder="出力フォルダの
|
|||||||
AdvSceneSwitcher.action.recording.type.changeOutputFileFormat="ファイル名の書式変更"
|
AdvSceneSwitcher.action.recording.type.changeOutputFileFormat="ファイル名の書式変更"
|
||||||
AdvSceneSwitcher.action.recording.pause.hint="録画設定によっては録画を一時停止できない場合がありますのでご注意ください"
|
AdvSceneSwitcher.action.recording.pause.hint="録画設定によっては録画を一時停止できない場合がありますのでご注意ください"
|
||||||
AdvSceneSwitcher.action.recording.split.hint="まずOBS設定で自動ファイル分割を有効にしてください!"
|
AdvSceneSwitcher.action.recording.split.hint="まずOBS設定で自動ファイル分割を有効にしてください!"
|
||||||
; AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
|
|
||||||
AdvSceneSwitcher.action.replay="リプレイバッファー"
|
AdvSceneSwitcher.action.replay="リプレイバッファー"
|
||||||
AdvSceneSwitcher.action.replay.saveWarn="警告: 頻繁に保存しすぎると、リプレイ バッファーが実際には保存されなくなる可能性があります。"
|
AdvSceneSwitcher.action.replay.saveWarn="警告: 頻繁に保存しすぎると、リプレイ バッファーが実際には保存されなくなる可能性があります。"
|
||||||
AdvSceneSwitcher.action.replay.durationWarn="注意: 最大リプレイ時間の変更は、次回再生バッファを開始したときから適用されます!"
|
AdvSceneSwitcher.action.replay.durationWarn="注意: 最大リプレイ時間の変更は、次回再生バッファを開始したときから適用されます!"
|
||||||
|
|||||||
@@ -239,7 +239,6 @@ AdvSceneSwitcher.macroList.deleted="excluída"
|
|||||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" já está selecionada!"
|
AdvSceneSwitcher.macroList.duplicate="\"%1\" já está selecionada!"
|
||||||
|
|
||||||
; Macro Logic
|
; Macro Logic
|
||||||
AdvSceneSwitcher.logic.none="Ignorar entrada"
|
|
||||||
AdvSceneSwitcher.logic.and="E"
|
AdvSceneSwitcher.logic.and="E"
|
||||||
AdvSceneSwitcher.logic.or="Ou"
|
AdvSceneSwitcher.logic.or="Ou"
|
||||||
AdvSceneSwitcher.logic.andNot="E não"
|
AdvSceneSwitcher.logic.andNot="E não"
|
||||||
@@ -372,10 +371,10 @@ AdvSceneSwitcher.condition.video.layout.minNeighbor="Vizinhos mínimos:{{minNeig
|
|||||||
AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Reduzir carga de CPU realizando a verificação apenas a cada {{throttleCount}} milissegundos"
|
AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Reduzir carga de CPU realizando a verificação apenas a cada {{throttleCount}} milissegundos"
|
||||||
AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Realizar verificação apenas na área"
|
AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Realizar verificação apenas na área"
|
||||||
AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
|
AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrColorPick="Verificar cor do texto:{{textColor}}{{selectColor}}"
|
AdvSceneSwitcher.condition.video.layout.ocrColorPick="Verificar cor do texto:{{color}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrTextType="Verificar tipo de texto:{{textType}}"
|
AdvSceneSwitcher.condition.video.layout.ocrTextType="Verificar tipo de texto:{{textType}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.ocrLanguage="Verificar idioma:{{languageCode}}"
|
AdvSceneSwitcher.condition.video.layout.ocrLanguage="Verificar idioma:{{languageCode}}"
|
||||||
AdvSceneSwitcher.condition.video.layout.color="Verificar cor:{{color}}{{selectColor}}"
|
AdvSceneSwitcher.condition.video.layout.color="Verificar cor:{{color}}"
|
||||||
AdvSceneSwitcher.condition.video.minSize="Tamanho mínimo:"
|
AdvSceneSwitcher.condition.video.minSize="Tamanho mínimo:"
|
||||||
AdvSceneSwitcher.condition.video.maxSize="Tamanho máximo:"
|
AdvSceneSwitcher.condition.video.maxSize="Tamanho máximo:"
|
||||||
AdvSceneSwitcher.condition.video.selectArea="Selecionar área"
|
AdvSceneSwitcher.condition.video.selectArea="Selecionar área"
|
||||||
@@ -719,8 +718,8 @@ AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Aguardar até que a tran
|
|||||||
AdvSceneSwitcher.action.wait="Aguardar"
|
AdvSceneSwitcher.action.wait="Aguardar"
|
||||||
AdvSceneSwitcher.action.wait.type.fixed="fixo"
|
AdvSceneSwitcher.action.wait.type.fixed="fixo"
|
||||||
AdvSceneSwitcher.action.wait.type.random="aleatório"
|
AdvSceneSwitcher.action.wait.type.random="aleatório"
|
||||||
AdvSceneSwitcher.action.wait.entry.fixed="Aguardando por{{waitType}}duração de{{duration}}"
|
AdvSceneSwitcher.action.wait.layout.fixed="Aguardando por{{waitType}}duração de{{duration}}"
|
||||||
AdvSceneSwitcher.action.wait.entry.random="Aguardando por{{waitType}}duração de{{duration}}para{{duration2}}"
|
AdvSceneSwitcher.action.wait.layout.random="Aguardando por{{waitType}}duração de{{duration}}para{{duration2}}"
|
||||||
AdvSceneSwitcher.action.audio="Áudio"
|
AdvSceneSwitcher.action.audio="Áudio"
|
||||||
AdvSceneSwitcher.action.audio.type.mute="Silenciar"
|
AdvSceneSwitcher.action.audio.type.mute="Silenciar"
|
||||||
AdvSceneSwitcher.action.audio.type.unmute="Ativar som"
|
AdvSceneSwitcher.action.audio.type.unmute="Ativar som"
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ AdvSceneSwitcher.macroTab.name="Имя:"
|
|||||||
AdvSceneSwitcher.macroTab.defaultname="Макрос %1"
|
AdvSceneSwitcher.macroTab.defaultname="Макрос %1"
|
||||||
AdvSceneSwitcher.macroTab.copy="Создать копию"
|
AdvSceneSwitcher.macroTab.copy="Создать копию"
|
||||||
; Macro Logic
|
; Macro Logic
|
||||||
AdvSceneSwitcher.logic.none="Игнорировать вход"
|
|
||||||
AdvSceneSwitcher.logic.and="И"
|
AdvSceneSwitcher.logic.and="И"
|
||||||
AdvSceneSwitcher.logic.or="Или"
|
AdvSceneSwitcher.logic.or="Или"
|
||||||
AdvSceneSwitcher.logic.andNot="И не"
|
AdvSceneSwitcher.logic.andNot="И не"
|
||||||
@@ -115,8 +114,8 @@ AdvSceneSwitcher.action.scene="Переключить сцену"
|
|||||||
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="случайный"
|
||||||
AdvSceneSwitcher.action.wait.entry.fixed="Ожидать {{waitType}} в течении {{duration}}"
|
AdvSceneSwitcher.action.wait.layout.fixed="Ожидать {{waitType}} в течении {{duration}}"
|
||||||
AdvSceneSwitcher.action.wait.entry.random="Ожидание {{waitType}} длительностью от {{duration}} до {{duration2}}"
|
AdvSceneSwitcher.action.wait.layout.random="Ожидание {{waitType}} длительностью от {{duration}} до {{duration2}}"
|
||||||
AdvSceneSwitcher.action.audio="Аудио"
|
AdvSceneSwitcher.action.audio="Аудио"
|
||||||
AdvSceneSwitcher.action.audio.type.mute="Замьютить"
|
AdvSceneSwitcher.action.audio.type.mute="Замьютить"
|
||||||
AdvSceneSwitcher.action.audio.type.unmute="Размьютить"
|
AdvSceneSwitcher.action.audio.type.unmute="Размьютить"
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ AdvSceneSwitcher.macroTab.expandAll="Hepsini Genişlet"
|
|||||||
AdvSceneSwitcher.macroTab.collapseAll="Hepsini Küçült"
|
AdvSceneSwitcher.macroTab.collapseAll="Hepsini Küçült"
|
||||||
|
|
||||||
; Macro Logic
|
; Macro Logic
|
||||||
AdvSceneSwitcher.logic.none="Girişi yoksay"
|
|
||||||
AdvSceneSwitcher.logic.and="Ve"
|
AdvSceneSwitcher.logic.and="Ve"
|
||||||
AdvSceneSwitcher.logic.or="Ya da"
|
AdvSceneSwitcher.logic.or="Ya da"
|
||||||
AdvSceneSwitcher.logic.andNot="ve değil"
|
AdvSceneSwitcher.logic.andNot="ve değil"
|
||||||
@@ -250,8 +249,8 @@ AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Hedef sahneye geçiş ta
|
|||||||
AdvSceneSwitcher.action.wait="Bekle"
|
AdvSceneSwitcher.action.wait="Bekle"
|
||||||
AdvSceneSwitcher.action.wait.type.fixed="sabit"
|
AdvSceneSwitcher.action.wait.type.fixed="sabit"
|
||||||
AdvSceneSwitcher.action.wait.type.random="rastgele"
|
AdvSceneSwitcher.action.wait.type.random="rastgele"
|
||||||
AdvSceneSwitcher.action.wait.entry.fixed="Şunu bekle {{waitType}} süre {{duration}}"
|
AdvSceneSwitcher.action.wait.layout.fixed="Şunu bekle {{waitType}} süre {{duration}}"
|
||||||
AdvSceneSwitcher.action.wait.entry.random="Şunu bekle {{waitType}} bu aralıktan {{duration}} buna {{duration2}}"
|
AdvSceneSwitcher.action.wait.layout.random="Şunu bekle {{waitType}} bu aralıktan {{duration}} buna {{duration2}}"
|
||||||
AdvSceneSwitcher.action.audio="Ses"
|
AdvSceneSwitcher.action.audio="Ses"
|
||||||
AdvSceneSwitcher.action.audio.type.mute="Sessiz"
|
AdvSceneSwitcher.action.audio.type.mute="Sessiz"
|
||||||
AdvSceneSwitcher.action.audio.type.unmute="Ses açmak"
|
AdvSceneSwitcher.action.audio.type.unmute="Ses açmak"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
BIN
data/res/speech/ggml-tiny-q8_0.bin
Normal file
BIN
data/res/speech/ggml-tiny-q8_0.bin
Normal file
Binary file not shown.
2
deps/cpp-httplib
vendored
2
deps/cpp-httplib
vendored
Submodule deps/cpp-httplib updated: c7ed1796a7...008e107d0f
1
deps/date
vendored
1
deps/date
vendored
Submodule deps/date deleted from 5bdb7e6f31
2
deps/leptonica
vendored
2
deps/leptonica
vendored
Submodule deps/leptonica updated: b667978e86...13275a278e
2
deps/libusb
vendored
2
deps/libusb
vendored
Submodule deps/libusb updated: 15a7ebb4d4...bc368fb133
2
deps/opencv
vendored
2
deps/opencv
vendored
Submodule deps/opencv updated: 71d3237a09...40738fb16c
1
deps/opencv_contrib
vendored
Submodule
1
deps/opencv_contrib
vendored
Submodule
Submodule deps/opencv_contrib added at 755e50675d
1
deps/whisper.cpp
vendored
Submodule
1
deps/whisper.cpp
vendored
Submodule
Submodule deps/whisper.cpp added at 306c88f4d1
@@ -258,6 +258,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="settingsLock">
|
||||||
|
<property name="text">
|
||||||
|
<string>AdvSceneSwitcher.generalTab.generalBehavior.settingsLock</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -19,8 +19,10 @@
|
|||||||
#include <obs-frontend-api.h>
|
#include <obs-frontend-api.h>
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
|
#include <QInputDialog>
|
||||||
#include <QLibrary>
|
#include <QLibrary>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
@@ -41,9 +43,12 @@ AdvSceneSwitcher::AdvSceneSwitcher(QWidget *parent)
|
|||||||
{
|
{
|
||||||
switcher->settingsWindowOpened = true;
|
switcher->settingsWindowOpened = true;
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
std::lock_guard<std::mutex> lock(switcher->m);
|
{
|
||||||
switcher->Prune();
|
std::lock_guard<std::mutex> lock(switcher->m);
|
||||||
LoadUI();
|
switcher->Prune();
|
||||||
|
LoadUI();
|
||||||
|
}
|
||||||
|
CheckFirstTimeSetup();
|
||||||
}
|
}
|
||||||
|
|
||||||
AdvSceneSwitcher::~AdvSceneSwitcher()
|
AdvSceneSwitcher::~AdvSceneSwitcher()
|
||||||
@@ -139,7 +144,6 @@ void AdvSceneSwitcher::LoadUI()
|
|||||||
SetTabOrder(ui->tabWidget);
|
SetTabOrder(ui->tabWidget);
|
||||||
SetCurrentTab(ui->tabWidget);
|
SetCurrentTab(ui->tabWidget);
|
||||||
RestoreWindowGeo();
|
RestoreWindowGeo();
|
||||||
CheckFirstTimeSetup();
|
|
||||||
|
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
@@ -457,12 +461,13 @@ void SwitcherData::Stop()
|
|||||||
SetMacroAbortWait(true);
|
SetMacroAbortWait(true);
|
||||||
GetMacroWaitCV().notify_all();
|
GetMacroWaitCV().notify_all();
|
||||||
GetMacroTransitionCV().notify_all();
|
GetMacroTransitionCV().notify_all();
|
||||||
StopAllMacros();
|
SignalStopAllMacros();
|
||||||
StopAndClearAllActionQueues();
|
StopAndClearAllActionQueues();
|
||||||
CloseAllInputDialogs();
|
CloseAllInputDialogs();
|
||||||
th->wait();
|
th->wait();
|
||||||
delete th;
|
delete th;
|
||||||
th = nullptr;
|
th = nullptr;
|
||||||
|
WaitForAllMacros();
|
||||||
RunStopSteps();
|
RunStopSteps();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,6 +511,8 @@ bool SwitcherData::AnySceneTransitionStarted()
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
extern "C" EXPORT void FreeSceneSwitcher()
|
extern "C" EXPORT void FreeSceneSwitcher()
|
||||||
{
|
{
|
||||||
|
switcher->Stop();
|
||||||
|
|
||||||
PlatformCleanup();
|
PlatformCleanup();
|
||||||
RunPluginCleanupSteps();
|
RunPluginCleanupSteps();
|
||||||
|
|
||||||
@@ -627,13 +634,20 @@ static void handleSceneCollectionCleanup()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP is also called on
|
||||||
|
// shutdown.
|
||||||
|
// Here we also don't want to clear the settings.
|
||||||
|
if (switcher->obsIsShuttingDown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SaveSceneSwitcher(nullptr, false, nullptr);
|
SaveSceneSwitcher(nullptr, false, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note to future self:
|
// Note to future self:
|
||||||
// be careful using switcher->m here as there is potential for deadlocks when using
|
// be careful using switcher->m here as there is potential for deadlocks when using
|
||||||
// frontend functions such as obs_frontend_set_current_scene()
|
// frontend functions such as obs_frontend_set_current_scene()
|
||||||
static void OBSEvent(enum obs_frontend_event event, void *switcher)
|
static void OBSEvent(enum obs_frontend_event event, void *)
|
||||||
{
|
{
|
||||||
if (!switcher) {
|
if (!switcher) {
|
||||||
return;
|
return;
|
||||||
@@ -707,19 +721,54 @@ static void LoadPlugins()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool settingsPasswordIsCorrect(QWidget *parent)
|
||||||
|
{
|
||||||
|
bool ok;
|
||||||
|
QString input = QInputDialog::getText(
|
||||||
|
parent,
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title"),
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.passwordLabel"),
|
||||||
|
QLineEdit::Password, QString(), &ok);
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.toStdString() == switcher->settingsLockPassword) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox::warning(
|
||||||
|
parent,
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title"),
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.wrongPassword"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void OpenSettingsWindow()
|
void OpenSettingsWindow()
|
||||||
{
|
{
|
||||||
if (switcher->settingsWindowOpened) {
|
if (switcher->settingsWindowOpened) {
|
||||||
AdvSceneSwitcher::window->show();
|
AdvSceneSwitcher::window->show();
|
||||||
AdvSceneSwitcher::window->raise();
|
AdvSceneSwitcher::window->raise();
|
||||||
AdvSceneSwitcher::window->activateWindow();
|
AdvSceneSwitcher::window->activateWindow();
|
||||||
} else {
|
return;
|
||||||
AdvSceneSwitcher::window =
|
|
||||||
new AdvSceneSwitcher(static_cast<QMainWindow *>(
|
|
||||||
obs_frontend_get_main_window()));
|
|
||||||
AdvSceneSwitcher::window->setAttribute(Qt::WA_DeleteOnClose);
|
|
||||||
AdvSceneSwitcher::window->show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto parent =
|
||||||
|
static_cast<QMainWindow *>(obs_frontend_get_main_window());
|
||||||
|
if (switcher->settingsLockEnabled &&
|
||||||
|
!switcher->settingsLockPassword.empty() &&
|
||||||
|
!settingsPasswordIsCorrect(parent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AdvSceneSwitcher::window = new AdvSceneSwitcher(parent);
|
||||||
|
AdvSceneSwitcher::window->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
AdvSceneSwitcher::window->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvSceneSwitcher::HighlightMacroSettingsButton(bool enable)
|
void AdvSceneSwitcher::HighlightMacroSettingsButton(bool enable)
|
||||||
@@ -767,8 +816,6 @@ void HighlightMacroSettingsButton(bool enable)
|
|||||||
enable);
|
enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetupActionQueues();
|
|
||||||
|
|
||||||
extern "C" EXPORT void InitSceneSwitcher(obs_module_t *module,
|
extern "C" EXPORT void InitSceneSwitcher(obs_module_t *module,
|
||||||
translateFunc translate)
|
translateFunc translate)
|
||||||
{
|
{
|
||||||
@@ -780,12 +827,11 @@ extern "C" EXPORT void InitSceneSwitcher(obs_module_t *module,
|
|||||||
PlatformInit();
|
PlatformInit();
|
||||||
LoadPlugins();
|
LoadPlugins();
|
||||||
SetupDock();
|
SetupDock();
|
||||||
SetupActionQueues();
|
|
||||||
|
|
||||||
RunPluginInitSteps();
|
RunPluginInitSteps();
|
||||||
|
|
||||||
obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr);
|
obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr);
|
||||||
obs_frontend_add_event_callback(OBSEvent, switcher);
|
obs_frontend_add_event_callback(OBSEvent, nullptr);
|
||||||
|
|
||||||
QAction *action = (QAction *)obs_frontend_add_tools_menu_qaction(
|
QAction *action = (QAction *)obs_frontend_add_tools_menu_qaction(
|
||||||
obs_module_text("AdvSceneSwitcher.pluginName"));
|
obs_module_text("AdvSceneSwitcher.pluginName"));
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "macro-segment-list.hpp"
|
|
||||||
#include "condition-logic.hpp"
|
|
||||||
#include "log-helper.hpp"
|
|
||||||
|
|
||||||
#include <ui_advanced-scene-switcher.h>
|
#include <ui_advanced-scene-switcher.h>
|
||||||
|
|
||||||
@@ -68,6 +65,7 @@ public slots:
|
|||||||
void on_warnPluginLoadFailure_stateChanged(int state);
|
void on_warnPluginLoadFailure_stateChanged(int state);
|
||||||
void on_suppressCrashRecoveryDialog_stateChanged(int state);
|
void on_suppressCrashRecoveryDialog_stateChanged(int state);
|
||||||
void on_hideLegacyTabs_stateChanged(int state);
|
void on_hideLegacyTabs_stateChanged(int state);
|
||||||
|
void on_settingsLock_stateChanged(int state);
|
||||||
void on_priorityUp_clicked();
|
void on_priorityUp_clicked();
|
||||||
void on_priorityDown_clicked();
|
void on_priorityDown_clicked();
|
||||||
void on_threadPriority_currentTextChanged(const QString &text);
|
void on_threadPriority_currentTextChanged(const QString &text);
|
||||||
@@ -116,6 +114,8 @@ public slots:
|
|||||||
void RenameSelectedMacro();
|
void RenameSelectedMacro();
|
||||||
void ExportMacros() const;
|
void ExportMacros() const;
|
||||||
void ImportMacros();
|
void ImportMacros();
|
||||||
|
void PauseSelectedMacros();
|
||||||
|
void UnpauseSelectedMacros();
|
||||||
void HighlightOnChange() const;
|
void HighlightOnChange() const;
|
||||||
void on_macroSettings_clicked();
|
void on_macroSettings_clicked();
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
#include "filter-combo-box.hpp"
|
#include "filter-combo-box.hpp"
|
||||||
#include "first-run-wizard.hpp"
|
#include "first-run-wizard.hpp"
|
||||||
#include "layout-helpers.hpp"
|
#include "layout-helpers.hpp"
|
||||||
#include "macro.hpp"
|
#include "macro-helpers.hpp"
|
||||||
#include "macro-search.hpp"
|
#include "macro-search.hpp"
|
||||||
#include "macro-settings.hpp"
|
#include "macro-settings.hpp"
|
||||||
|
#include "macro.hpp"
|
||||||
#include "path-helpers.hpp"
|
#include "path-helpers.hpp"
|
||||||
#include "source-helpers.hpp"
|
#include "source-helpers.hpp"
|
||||||
#include "splitter-helpers.hpp"
|
#include "splitter-helpers.hpp"
|
||||||
@@ -18,7 +19,10 @@
|
|||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
#include <obs-frontend-api.h>
|
#include <obs-frontend-api.h>
|
||||||
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
|
#include <QInputDialog>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
@@ -243,6 +247,64 @@ void AdvSceneSwitcher::on_hideLegacyTabs_stateChanged(int state)
|
|||||||
ui->priorityBox->setVisible(!switcher->hideLegacyTabs);
|
ui->priorityBox->setVisible(!switcher->hideLegacyTabs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AdvSceneSwitcher::on_settingsLock_stateChanged(int state)
|
||||||
|
{
|
||||||
|
if (loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state) {
|
||||||
|
switcher->settingsLockEnabled = false;
|
||||||
|
switcher->settingsLockPassword = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
QString newPassword = QInputDialog::getText(
|
||||||
|
this,
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title"),
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.newPasswordLabel"),
|
||||||
|
QLineEdit::Password, QString(), &ok);
|
||||||
|
|
||||||
|
const auto disablePassword = [this]() {
|
||||||
|
QSignalBlocker blocker(ui->settingsLock);
|
||||||
|
ui->settingsLock->setChecked(false);
|
||||||
|
ui->settingsLock->setCheckState(Qt::Unchecked);
|
||||||
|
switcher->settingsLockEnabled = false;
|
||||||
|
switcher->settingsLockPassword = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!ok || newPassword.isEmpty()) {
|
||||||
|
disablePassword();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString confirm = QInputDialog::getText(
|
||||||
|
this,
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title"),
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.confirmPasswordLabel"),
|
||||||
|
QLineEdit::Password, QString(), &ok);
|
||||||
|
if (!ok || newPassword != confirm) {
|
||||||
|
if (ok) {
|
||||||
|
QMessageBox::warning(
|
||||||
|
this,
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title"),
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.passwordMismatch"));
|
||||||
|
}
|
||||||
|
disablePassword();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switcher->settingsLockEnabled = true;
|
||||||
|
switcher->settingsLockPassword = newPassword.toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
void AdvSceneSwitcher::SetDeprecationWarnings()
|
void AdvSceneSwitcher::SetDeprecationWarnings()
|
||||||
{
|
{
|
||||||
QString toolTip =
|
QString toolTip =
|
||||||
@@ -391,18 +453,22 @@ void AdvSceneSwitcher::CheckFirstTimeSetup()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto macro = FirstRunWizard::ShowWizard(this);
|
bool wasSkipped = false;
|
||||||
|
auto macro = wiz::FirstRunWizard::ShowWizard(this, &wasSkipped);
|
||||||
if (macro) {
|
if (macro) {
|
||||||
renameMacroIfNecessary(macro);
|
renameMacroIfNecessary(macro);
|
||||||
QTimer::singleShot(0, this,
|
QTimer::singleShot(0, this,
|
||||||
[this, macro]() { ui->macros->Add(macro); });
|
[this, macro]() { ui->macros->Add(macro); });
|
||||||
}
|
}
|
||||||
|
if (wasSkipped) {
|
||||||
|
ui->alwaysShowFeatureTabs->setChecked(true);
|
||||||
|
}
|
||||||
switcher->Start();
|
switcher->Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdvSceneSwitcher::on_openSetupWizard_clicked()
|
void AdvSceneSwitcher::on_openSetupWizard_clicked()
|
||||||
{
|
{
|
||||||
auto macro = FirstRunWizard::ShowWizard(this);
|
auto macro = wiz::FirstRunWizard::ShowWizard(this);
|
||||||
if (!macro) {
|
if (!macro) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -468,7 +534,6 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
|
|||||||
// Needs to be loaded before any entries which might rely on scene group
|
// Needs to be loaded before any entries which might rely on scene group
|
||||||
// selections to be available.
|
// selections to be available.
|
||||||
loadSceneGroups(obj);
|
loadSceneGroups(obj);
|
||||||
LoadVariables(obj);
|
|
||||||
|
|
||||||
RunLoadSteps(obj);
|
RunLoadSteps(obj);
|
||||||
|
|
||||||
@@ -507,7 +572,6 @@ void SwitcherData::SaveSettings(obs_data_t *obj)
|
|||||||
saveSceneGroups(obj);
|
saveSceneGroups(obj);
|
||||||
SaveMacros(obj);
|
SaveMacros(obj);
|
||||||
SaveGlobalMacroSettings(obj);
|
SaveGlobalMacroSettings(obj);
|
||||||
SaveVariables(obj);
|
|
||||||
saveWindowTitleSwitches(obj);
|
saveWindowTitleSwitches(obj);
|
||||||
saveScreenRegionSwitches(obj);
|
saveScreenRegionSwitches(obj);
|
||||||
savePauseSwitches(obj);
|
savePauseSwitches(obj);
|
||||||
@@ -565,6 +629,9 @@ void SwitcherData::SaveGeneralSettings(obs_data_t *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);
|
||||||
|
obs_data_set_bool(obj, "settingsLockEnabled", settingsLockEnabled);
|
||||||
|
obs_data_set_string(obj, "settingsLockPassword",
|
||||||
|
settingsLockPassword.c_str());
|
||||||
|
|
||||||
SaveFunctionPriorities(obj, functionNamesByPriority);
|
SaveFunctionPriorities(obj, functionNamesByPriority);
|
||||||
|
|
||||||
@@ -636,6 +703,8 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
|
|||||||
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);
|
||||||
hideLegacyTabs = obs_data_get_bool(obj, "hideLegacyTabs");
|
hideLegacyTabs = obs_data_get_bool(obj, "hideLegacyTabs");
|
||||||
|
settingsLockEnabled = obs_data_get_bool(obj, "settingsLockEnabled");
|
||||||
|
settingsLockPassword = obs_data_get_string(obj, "settingsLockPassword");
|
||||||
|
|
||||||
SetDefaultFunctionPriorities(obj);
|
SetDefaultFunctionPriorities(obj);
|
||||||
LoadFunctionPriorities(obj, functionNamesByPriority);
|
LoadFunctionPriorities(obj, functionNamesByPriority);
|
||||||
@@ -958,6 +1027,7 @@ void AdvSceneSwitcher::SetupGeneralTab()
|
|||||||
ui->warnPluginLoadFailure->setChecked(switcher->warnPluginLoadFailure);
|
ui->warnPluginLoadFailure->setChecked(switcher->warnPluginLoadFailure);
|
||||||
ui->suppressCrashRecoveryDialog->setChecked(GetSuppressCrashDialog());
|
ui->suppressCrashRecoveryDialog->setChecked(GetSuppressCrashDialog());
|
||||||
ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs);
|
ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs);
|
||||||
|
ui->settingsLock->setChecked(switcher->settingsLockEnabled);
|
||||||
|
|
||||||
populatePriorityFunctionList(ui->priorityList);
|
populatePriorityFunctionList(ui->priorityList);
|
||||||
populateThreadPriorityList(ui->threadPriority);
|
populateThreadPriorityList(ui->threadPriority);
|
||||||
|
|||||||
@@ -178,47 +178,19 @@ void AdvSceneSwitcher::on_ignoreWindows_currentRowChanged(int idx)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkWindowTitleSwitchDirect(WindowSwitch &s,
|
static bool windowInfoMatchesSwitch(const WindowInfo &info,
|
||||||
std::string ¤tWindowTitle, bool &match,
|
const WindowSwitch &s)
|
||||||
OBSWeakSource &scene,
|
|
||||||
OBSWeakSource &transition)
|
|
||||||
{
|
{
|
||||||
bool focus = (!s.focus || s.window == currentWindowTitle);
|
if (s.focus && !info.focused) {
|
||||||
bool fullscreen = (!s.fullscreen || IsFullscreen(s.window));
|
return false;
|
||||||
bool max = (!s.maximized || IsMaximized(s.window));
|
|
||||||
|
|
||||||
if (focus && fullscreen && max) {
|
|
||||||
match = true;
|
|
||||||
scene = s.getScene();
|
|
||||||
transition = s.transition;
|
|
||||||
}
|
}
|
||||||
}
|
if (s.fullscreen && !info.fullscreen) {
|
||||||
|
return false;
|
||||||
void checkWindowTitleSwitchRegex(WindowSwitch &s,
|
|
||||||
std::string ¤tWindowTitle,
|
|
||||||
std::vector<std::string> windowList,
|
|
||||||
bool &match, OBSWeakSource &scene,
|
|
||||||
OBSWeakSource &transition)
|
|
||||||
{
|
|
||||||
for (auto &window : windowList) {
|
|
||||||
try {
|
|
||||||
std::regex expr(s.window);
|
|
||||||
if (!std::regex_match(window, expr)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} catch (const std::regex_error &) {
|
|
||||||
}
|
|
||||||
|
|
||||||
bool focus = (!s.focus || window == currentWindowTitle);
|
|
||||||
bool fullscreen = (!s.fullscreen || IsFullscreen(window));
|
|
||||||
bool max = (!s.maximized || IsMaximized(window));
|
|
||||||
|
|
||||||
if (focus && fullscreen && max) {
|
|
||||||
match = true;
|
|
||||||
scene = s.getScene();
|
|
||||||
transition = s.transition;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (s.maximized && !info.maximized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SwitcherData::checkWindowTitleSwitch(OBSWeakSource &scene,
|
bool SwitcherData::checkWindowTitleSwitch(OBSWeakSource &scene,
|
||||||
@@ -228,23 +200,37 @@ bool SwitcherData::checkWindowTitleSwitch(OBSWeakSource &scene,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string currentWindowTitle = switcher->currentTitle;
|
|
||||||
bool match = false;
|
bool match = false;
|
||||||
const auto windowList = GetWindowList();
|
|
||||||
|
WindowQueryOptions options;
|
||||||
|
options.focus = true;
|
||||||
|
options.fullscreen = true;
|
||||||
|
options.maximized = true;
|
||||||
|
const auto windows = GetWindows(options);
|
||||||
|
|
||||||
for (WindowSwitch &s : windowSwitches) {
|
for (WindowSwitch &s : windowSwitches) {
|
||||||
if (!s.initialized()) {
|
if (!s.initialized()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std::find(windowList.begin(), windowList.end(), s.window) !=
|
for (const auto &info : windows) {
|
||||||
windowList.end()) {
|
bool titleMatch = false;
|
||||||
checkWindowTitleSwitchDirect(s, currentWindowTitle,
|
try {
|
||||||
match, scene, transition);
|
std::regex expr(s.window);
|
||||||
} else {
|
titleMatch = info.title == s.window ||
|
||||||
checkWindowTitleSwitchRegex(s, currentWindowTitle,
|
std::regex_match(info.title, expr);
|
||||||
windowList, match, scene,
|
} catch (const std::regex_error &) {
|
||||||
transition);
|
titleMatch = info.title == s.window;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!titleMatch || !windowInfoMatchesSwitch(info, s)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match = true;
|
||||||
|
scene = s.getScene();
|
||||||
|
transition = s.transition;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
#include "platform-funcs.hpp"
|
#include "platform-funcs.hpp"
|
||||||
|
|
||||||
#include "log-helper.hpp"
|
#include "log-helper.hpp"
|
||||||
|
#include "obs-module-helper.hpp"
|
||||||
|
#include "plugin-state-helpers.hpp"
|
||||||
|
#include "ui-helpers.hpp"
|
||||||
|
|
||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
#include <X11/Xatom.h>
|
#include <X11/Xatom.h>
|
||||||
@@ -236,17 +240,89 @@ std::string getWindowName(Window window)
|
|||||||
return windowTitle;
|
return windowTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> GetWindowList()
|
std::vector<WindowInfo> GetWindows(const WindowQueryOptions &options)
|
||||||
{
|
{
|
||||||
std::vector<std::string> windows;
|
const std::string foregroundTitle = GetCurrentWindowTitle();
|
||||||
for (auto window : getTopLevelWindows()) {
|
const auto topLevel = getTopLevelWindows();
|
||||||
|
auto display = disp();
|
||||||
|
|
||||||
|
std::vector<WindowInfo> result;
|
||||||
|
result.reserve(topLevel.size());
|
||||||
|
|
||||||
|
for (auto window : topLevel) {
|
||||||
auto name = getWindowName(window);
|
auto name = getWindowName(window);
|
||||||
if (name.empty()) {
|
if (name.empty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
windows.emplace_back(name);
|
|
||||||
|
WindowInfo info;
|
||||||
|
info.title = name;
|
||||||
|
|
||||||
|
if (options.focus) {
|
||||||
|
info.focused = (name == foregroundTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.fullscreen || options.maximized) {
|
||||||
|
QStringList states = getStates(window);
|
||||||
|
if (options.fullscreen) {
|
||||||
|
info.fullscreen = states.contains(
|
||||||
|
"_NET_WM_STATE_FULLSCREEN");
|
||||||
|
}
|
||||||
|
if (options.maximized) {
|
||||||
|
info.maximized =
|
||||||
|
states.contains(
|
||||||
|
"_NET_WM_STATE_MAXIMIZED_VERT") &&
|
||||||
|
states.contains(
|
||||||
|
"_NET_WM_STATE_MAXIMIZED_HORZ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.geometry && display) {
|
||||||
|
XWindowAttributes attrs;
|
||||||
|
if (XGetWindowAttributes(display, window, &attrs)) {
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
Window child;
|
||||||
|
XTranslateCoordinates(
|
||||||
|
display, window,
|
||||||
|
DefaultRootWindow(display), 0, 0, &x,
|
||||||
|
&y, &child);
|
||||||
|
info.x = x;
|
||||||
|
info.y = y;
|
||||||
|
info.width = attrs.width;
|
||||||
|
info.height = attrs.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// windowClass and text not implemented on Linux
|
||||||
|
result.emplace_back(std::move(info));
|
||||||
}
|
}
|
||||||
return windows;
|
|
||||||
|
// When KWin compat is active, native Wayland windows are not tracked by
|
||||||
|
// X11 and therefore do not appear in the list above. Merge in any window
|
||||||
|
// reported by KWin that is not already present (matched by title) so that
|
||||||
|
// title and focus conditions work for native Wayland windows.
|
||||||
|
if (KWin) {
|
||||||
|
for (const auto &[pid, title] :
|
||||||
|
FocusNotifier::getWindowList()) {
|
||||||
|
bool found = false;
|
||||||
|
for (const auto &info : result) {
|
||||||
|
if (info.title == title) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
WindowInfo waylandWindow;
|
||||||
|
waylandWindow.title = title;
|
||||||
|
waylandWindow.focused =
|
||||||
|
(title == foregroundTitle);
|
||||||
|
result.emplace_back(std::move(waylandWindow));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getActiveWindow(Window *&window)
|
int getActiveWindow(Window *&window)
|
||||||
@@ -290,68 +366,6 @@ std::string GetCurrentWindowTitle()
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool windowStatesAreSet(const std::string &windowTitle,
|
|
||||||
std::vector<QString> &expectedStates)
|
|
||||||
{
|
|
||||||
if (!ewmhIsSupported()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Window> windows = getTopLevelWindows();
|
|
||||||
for (auto &window : windows) {
|
|
||||||
auto name = getWindowName(window);
|
|
||||||
if (name.empty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool equals = windowTitle == name;
|
|
||||||
bool matches = QString::fromStdString(name).contains(
|
|
||||||
QRegularExpression(
|
|
||||||
QString::fromStdString(windowTitle)));
|
|
||||||
|
|
||||||
if (!(equals || matches)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList states = getStates(window);
|
|
||||||
if (states.isEmpty()) {
|
|
||||||
if (expectedStates.empty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &state : expectedStates) {
|
|
||||||
if (!states.contains(state)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsMaximized(const std::string &title)
|
|
||||||
{
|
|
||||||
std::vector<QString> states;
|
|
||||||
states.emplace_back("_NET_WM_STATE_MAXIMIZED_VERT");
|
|
||||||
states.emplace_back("_NET_WM_STATE_MAXIMIZED_HORZ");
|
|
||||||
return windowStatesAreSet(title, states);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsFullscreen(const std::string &title)
|
|
||||||
{
|
|
||||||
std::vector<QString> states;
|
|
||||||
states.emplace_back("_NET_WM_STATE_FULLSCREEN");
|
|
||||||
return windowStatesAreSet(title, states);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::string> GetTextInWindow(const std::string &)
|
|
||||||
{
|
|
||||||
// Not implemented
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
static void getProcessListProcps(QStringList &processes)
|
static void getProcessListProcps(QStringList &processes)
|
||||||
{
|
{
|
||||||
#ifdef PROCPS_AVAILABLE
|
#ifdef PROCPS_AVAILABLE
|
||||||
@@ -618,27 +632,39 @@ int ignoreXerror(Display *d, XErrorEvent *e)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void handleKWinInitFailure(bool kwinAvailable)
|
||||||
|
{
|
||||||
|
blog(LOG_INFO, "not using KWin compat");
|
||||||
|
const bool onKDE = qEnvironmentVariable("XDG_CURRENT_DESKTOP")
|
||||||
|
.contains("KDE", Qt::CaseInsensitive);
|
||||||
|
const bool inFlatpak = qEnvironmentVariableIsSet("FLATPAK_ID");
|
||||||
|
if (!kwinAvailable && onKDE && inFlatpak)
|
||||||
|
AddFinishedLoadingStep([]() {
|
||||||
|
DisplayTrayMessage(
|
||||||
|
obs_module_text("AdvSceneSwitcher.pluginName"),
|
||||||
|
obs_module_text("AdvSceneSwitcher.kwin.flatpakPermissionWarning"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void PlatformInit()
|
void PlatformInit()
|
||||||
{
|
{
|
||||||
|
const bool kwinAvailable = isKWinAvailable();
|
||||||
|
KWin = kwinAvailable && registerKWinDBusListener(¬ifier) &&
|
||||||
|
startKWinScript(KWinScriptObjectPath);
|
||||||
|
if (KWin)
|
||||||
|
blog(LOG_INFO, "using KWin compat");
|
||||||
|
else
|
||||||
|
handleKWinInitFailure(kwinAvailable);
|
||||||
|
|
||||||
|
initProcps();
|
||||||
|
initProc2();
|
||||||
|
|
||||||
auto display = disp();
|
auto display = disp();
|
||||||
if (!display) {
|
if (!display) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
KWin = isKWinAvailable();
|
|
||||||
if (!(KWin && startKWinScript(KWinScriptObjectPath) &&
|
|
||||||
registerKWinDBusListener(¬ifier))) {
|
|
||||||
// something bad happened while trying to initialize
|
|
||||||
// the KWin script/dbus so disable it
|
|
||||||
KWin = false;
|
|
||||||
blog(LOG_INFO, "not using KWin compat");
|
|
||||||
} else {
|
|
||||||
blog(LOG_INFO, "using KWin compat");
|
|
||||||
}
|
|
||||||
|
|
||||||
initXss();
|
initXss();
|
||||||
initProcps();
|
|
||||||
initProc2();
|
|
||||||
XSetErrorHandler(ignoreXerror);
|
XSetErrorHandler(ignoreXerror);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileDevice>
|
#include <QFileDevice>
|
||||||
|
#include <QStandardPaths>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
#include <QtDBus/QDBusConnectionInterface>
|
#include <QtDBus/QDBusConnectionInterface>
|
||||||
#include <QtDBus/QDBusInterface>
|
#include <QtDBus/QDBusInterface>
|
||||||
@@ -12,35 +13,82 @@
|
|||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
|
std::mutex FocusNotifier::_mutex;
|
||||||
int FocusNotifier::activePID = -1;
|
int FocusNotifier::activePID = -1;
|
||||||
std::string FocusNotifier::activeTitle = {};
|
std::string FocusNotifier::activeTitle = {};
|
||||||
|
std::map<std::string, std::pair<int, std::string>> FocusNotifier::_windows;
|
||||||
|
|
||||||
int FocusNotifier::getActiveWindowPID()
|
int FocusNotifier::getActiveWindowPID()
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
return activePID;
|
return activePID;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string FocusNotifier::getActiveWindowTitle()
|
std::string FocusNotifier::getActiveWindowTitle()
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
return activeTitle;
|
return activeTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<int, std::string>> FocusNotifier::getWindowList()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
std::vector<std::pair<int, std::string>> result;
|
||||||
|
result.reserve(_windows.size());
|
||||||
|
for (const auto &entry : _windows) {
|
||||||
|
result.emplace_back(entry.second);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void FocusNotifier::focusChanged(const int pid)
|
void FocusNotifier::focusChanged(const int pid)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
activePID = pid;
|
activePID = pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FocusNotifier::focusTitle(const QString &title)
|
void FocusNotifier::focusTitle(const QString &title)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
activeTitle = title.toStdString();
|
activeTitle = title.toStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FocusNotifier::focusUpdate(const int pid, const QString &title)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
activePID = pid;
|
||||||
|
activeTitle = title.toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FocusNotifier::windowAdded(const QString &id, const int pid,
|
||||||
|
const QString &title)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
_windows[id.toStdString()] = {pid, title.toStdString()};
|
||||||
|
}
|
||||||
|
|
||||||
|
void FocusNotifier::windowRemoved(const QString &id)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
_windows.erase(id.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void FocusNotifier::windowTitleChanged(const QString &id, const QString &title)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
auto it = _windows.find(id.toStdString());
|
||||||
|
if (it != _windows.end()) {
|
||||||
|
it->second.second = title.toStdString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool isKWinAvailable()
|
bool isKWinAvailable()
|
||||||
{
|
{
|
||||||
const QDBusConnectionInterface *interface =
|
const QDBusConnectionInterface *interface =
|
||||||
QDBusConnection::sessionBus().interface();
|
QDBusConnection::sessionBus().interface();
|
||||||
if (!interface)
|
if (!interface) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const QStringList services =
|
const QStringList services =
|
||||||
interface->registeredServiceNames().value();
|
interface->registeredServiceNames().value();
|
||||||
@@ -50,32 +98,42 @@ bool isKWinAvailable()
|
|||||||
bool startKWinScript(QString &scriptObjectPath)
|
bool startKWinScript(QString &scriptObjectPath)
|
||||||
{
|
{
|
||||||
const QString scriptPath =
|
const QString scriptPath =
|
||||||
"/tmp/AdvancedSceneSwitcher/KWinFocusNotifier.js";
|
QStandardPaths::writableLocation(
|
||||||
|
QStandardPaths::RuntimeLocation) +
|
||||||
|
"/AdvancedSceneSwitcher/KWinFocusNotifier.js";
|
||||||
|
|
||||||
const QString script =
|
const QString script =
|
||||||
R"(workspace.windowActivated.connect(function(client) {
|
R"(var adss = "com.obsproject.Studio.Plugin.SceneSwitcher";
|
||||||
if (!client) return;
|
var adssPath = "/com/obsproject/Studio/Plugin/SceneSwitcher";
|
||||||
if (!client.pid) return;
|
|
||||||
if (!client.caption) return;
|
|
||||||
|
|
||||||
callDBus(
|
function trackWindow(window) {
|
||||||
"com.github.AdvancedSceneSwitcher",
|
var id = window.internalId.toString();
|
||||||
"/com/github/AdvancedSceneSwitcher",
|
callDBus(adss, adssPath, adss, "windowAdded", id, window.pid, window.caption);
|
||||||
"com.github.AdvancedSceneSwitcher",
|
window.captionChanged.connect(function() {
|
||||||
"focusChanged",
|
callDBus(adss, adssPath, adss, "windowTitleChanged", id, window.caption);
|
||||||
client.pid
|
});
|
||||||
);
|
}
|
||||||
callDBus(
|
|
||||||
"com.github.AdvancedSceneSwitcher",
|
var windows = workspace.windowList();
|
||||||
"/com/github/AdvancedSceneSwitcher",
|
for (var i = 0; i < windows.length; i++) {
|
||||||
"com.github.AdvancedSceneSwitcher",
|
trackWindow(windows[i]);
|
||||||
"focusTitle",
|
}
|
||||||
client.caption
|
|
||||||
);
|
workspace.windowAdded.connect(trackWindow);
|
||||||
|
|
||||||
|
workspace.windowRemoved.connect(function(window) {
|
||||||
|
callDBus(adss, adssPath, adss, "windowRemoved", window.internalId.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
workspace.windowActivated.connect(function(client) {
|
||||||
|
callDBus(adss, adssPath, adss, "focusUpdate",
|
||||||
|
client ? client.pid : 0,
|
||||||
|
client ? client.caption : "");
|
||||||
}))";
|
}))";
|
||||||
|
|
||||||
if (const QDir dir; !dir.mkpath(QFileInfo(scriptPath).absolutePath())) {
|
if (const QDir dir; !dir.mkpath(QFileInfo(scriptPath).absolutePath())) {
|
||||||
blog(LOG_ERROR, "error creating /tmp/AdvancedSceneSwitcher");
|
blog(LOG_ERROR,
|
||||||
|
"error creating KWinFocusNotifier script directory");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,8 +202,8 @@ bool stopKWinScript(const QString &scriptObjectPath)
|
|||||||
|
|
||||||
bool registerKWinDBusListener(FocusNotifier *notifier)
|
bool registerKWinDBusListener(FocusNotifier *notifier)
|
||||||
{
|
{
|
||||||
static const QString serviceName = "com.github.AdvancedSceneSwitcher";
|
static const QString serviceName = "com.obsproject.Studio.Plugin.SceneSwitcher";
|
||||||
static const QString objectPath = "/com/github/AdvancedSceneSwitcher";
|
static const QString objectPath = "/com/obsproject/Studio/Plugin/SceneSwitcher";
|
||||||
auto bus = QDBusConnection::sessionBus();
|
auto bus = QDBusConnection::sessionBus();
|
||||||
|
|
||||||
if (bus.objectRegisteredAt(objectPath)) {
|
if (bus.objectRegisteredAt(objectPath)) {
|
||||||
|
|||||||
@@ -2,26 +2,39 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
class FocusNotifier final : public QObject {
|
class FocusNotifier final : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_CLASSINFO("D-Bus Interface", "com.github.AdvancedSceneSwitcher")
|
Q_CLASSINFO("D-Bus Interface", "com.obsproject.Studio.Plugin.SceneSwitcher")
|
||||||
|
|
||||||
|
static std::mutex _mutex;
|
||||||
static int activePID;
|
static int activePID;
|
||||||
static std::string activeTitle;
|
static std::string activeTitle;
|
||||||
|
// Maps KWin internal window ID to {pid, title}
|
||||||
|
static std::map<std::string, std::pair<int, std::string>> _windows;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using QObject::QObject;
|
using QObject::QObject;
|
||||||
|
|
||||||
static int getActiveWindowPID();
|
static int getActiveWindowPID();
|
||||||
static std::string getActiveWindowTitle();
|
static std::string getActiveWindowTitle();
|
||||||
|
static std::vector<std::pair<int, std::string>> getWindowList();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void focusChanged(const int pid);
|
void focusChanged(const int pid);
|
||||||
void focusTitle(const QString &title);
|
void focusTitle(const QString &title);
|
||||||
|
void focusUpdate(const int pid, const QString &title);
|
||||||
|
void windowAdded(const QString &id, const int pid,
|
||||||
|
const QString &title);
|
||||||
|
void windowRemoved(const QString &id);
|
||||||
|
void windowTitleChanged(const QString &id, const QString &title);
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isKWinAvailable();
|
bool isKWinAvailable();
|
||||||
|
|||||||
@@ -164,4 +164,9 @@ std::shared_ptr<MacroSegment> MacroActionEdit::Data() const
|
|||||||
return *_entryData;
|
return *_entryData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroActionEdit::FocusTypeSelection()
|
||||||
|
{
|
||||||
|
_actionSelection->setFocus();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace advss
|
} // namespace advss
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public:
|
|||||||
std::shared_ptr<MacroAction> * = nullptr);
|
std::shared_ptr<MacroAction> * = nullptr);
|
||||||
void SetupWidgets(bool basicSetup = false);
|
void SetupWidgets(bool basicSetup = false);
|
||||||
void SetEntryData(std::shared_ptr<MacroAction> *);
|
void SetEntryData(std::shared_ptr<MacroAction> *);
|
||||||
|
void FocusTypeSelection() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void ActionSelectionChanged(const QString &text);
|
void ActionSelectionChanged(const QString &text);
|
||||||
|
|||||||
196
lib/macro/macro-action-loop.cpp
Normal file
196
lib/macro/macro-action-loop.cpp
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
#include "macro-action-loop.hpp"
|
||||||
|
|
||||||
|
#include "layout-helpers.hpp"
|
||||||
|
#include "macro-action-factory.hpp"
|
||||||
|
#include "macro.hpp"
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
const std::string MacroActionLoop::id = "loop";
|
||||||
|
|
||||||
|
bool MacroActionLoop::_registered = MacroActionFactory::Register(
|
||||||
|
MacroActionLoop::id,
|
||||||
|
{MacroActionLoop::Create, MacroActionLoopEdit::Create,
|
||||||
|
"AdvSceneSwitcher.action.loop"});
|
||||||
|
|
||||||
|
bool MacroActionLoop::PerformAction()
|
||||||
|
{
|
||||||
|
int iterations = 0;
|
||||||
|
const int maxIterations = _maxIterations;
|
||||||
|
Macro *parentMacro = GetMacro();
|
||||||
|
|
||||||
|
if (maxIterations <= 0) {
|
||||||
|
blog(LOG_WARNING,
|
||||||
|
"loop action has invalid max iterations value of %d",
|
||||||
|
maxIterations);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (_loopMacro->CheckConditions()) {
|
||||||
|
if (iterations >= maxIterations) {
|
||||||
|
blog(LOG_WARNING,
|
||||||
|
"loop action reached iteration limit of %d",
|
||||||
|
maxIterations);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ablog(LOG_INFO, "loop iteration %d", iterations);
|
||||||
|
if (!_loopMacro->PerformActions(true, false, true)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++iterations;
|
||||||
|
if (parentMacro && parentMacro->GetStop()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ablog(LOG_INFO, "loop completed after %d iteration(s)", iterations);
|
||||||
|
SetTempVarValue("count", std::to_string(iterations));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroActionLoop::LogAction() const
|
||||||
|
{
|
||||||
|
ablog(LOG_INFO, "running loop (max %d iterations)",
|
||||||
|
_maxIterations.GetValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MacroActionLoop::Save(obs_data_t *obj) const
|
||||||
|
{
|
||||||
|
MacroAction::Save(obj);
|
||||||
|
_maxIterations.Save(obj, "maxIterations");
|
||||||
|
OBSDataAutoRelease loopMacroData = obs_data_create();
|
||||||
|
_loopMacro->Save(loopMacroData);
|
||||||
|
obs_data_set_obj(obj, "loopMacro", loopMacroData);
|
||||||
|
obs_data_set_int(obj, "customWidgetHeight", _customWidgetHeight);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MacroActionLoop::Load(obs_data_t *obj)
|
||||||
|
{
|
||||||
|
MacroAction::Load(obj);
|
||||||
|
_maxIterations.Load(obj, "maxIterations");
|
||||||
|
if (obs_data_has_user_value(obj, "loopMacro")) {
|
||||||
|
OBSDataAutoRelease loopMacroData =
|
||||||
|
obs_data_get_obj(obj, "loopMacro");
|
||||||
|
_loopMacro = std::make_shared<Macro>();
|
||||||
|
_loopMacro->Load(loopMacroData);
|
||||||
|
}
|
||||||
|
_customWidgetHeight = obs_data_get_int(obj, "customWidgetHeight");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MacroActionLoop::PostLoad()
|
||||||
|
{
|
||||||
|
MacroAction::PostLoad();
|
||||||
|
_loopMacro->PostLoad();
|
||||||
|
_loopMacro->SetActionTriggerMode(Macro::ActionTriggerMode::ALWAYS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<MacroAction> MacroActionLoop::Create(Macro *m)
|
||||||
|
{
|
||||||
|
return std::make_shared<MacroActionLoop>(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<MacroAction> MacroActionLoop::Copy() const
|
||||||
|
{
|
||||||
|
auto copy = std::make_shared<MacroActionLoop>(*this);
|
||||||
|
|
||||||
|
OBSDataAutoRelease data = obs_data_create();
|
||||||
|
_loopMacro->Save(data);
|
||||||
|
|
||||||
|
copy->_loopMacro = std::make_shared<Macro>();
|
||||||
|
copy->_loopMacro->Load(data);
|
||||||
|
copy->_loopMacro->PostLoad();
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroActionLoop::ResolveVariablesToFixedValues()
|
||||||
|
{
|
||||||
|
_maxIterations.ResolveVariables();
|
||||||
|
for (auto &action : _loopMacro->Actions()) {
|
||||||
|
action->ResolveVariablesToFixedValues();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroActionLoop::SetupTempVars()
|
||||||
|
{
|
||||||
|
MacroAction::SetupTempVars();
|
||||||
|
AddTempvar("count",
|
||||||
|
obs_module_text("AdvSceneSwitcher.tempVar.loop.count"),
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.tempVar.loop.count.description"));
|
||||||
|
}
|
||||||
|
|
||||||
|
MacroActionLoopEdit::MacroActionLoopEdit(
|
||||||
|
QWidget *parent, std::shared_ptr<MacroActionLoop> entryData)
|
||||||
|
: ResizableWidget(parent),
|
||||||
|
_macroEdit(new MacroEdit(
|
||||||
|
this, QStringList()
|
||||||
|
<< "AdvSceneSwitcher.action.loop.conditionHelp"
|
||||||
|
<< "AdvSceneSwitcher.action.loop.actionHelp"
|
||||||
|
<< "")),
|
||||||
|
_maxIterations(new VariableSpinBox())
|
||||||
|
{
|
||||||
|
_maxIterations->setMinimum(1);
|
||||||
|
_maxIterations->setMaximum(10000000);
|
||||||
|
|
||||||
|
QWidget::connect(
|
||||||
|
_maxIterations,
|
||||||
|
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||||
|
this, SLOT(MaxIterationsChanged(const NumberVariable<int> &)));
|
||||||
|
|
||||||
|
auto controlsLayout = new QHBoxLayout();
|
||||||
|
controlsLayout->addWidget(new QLabel(
|
||||||
|
obs_module_text("AdvSceneSwitcher.action.loop.maxIterations")));
|
||||||
|
controlsLayout->addWidget(_maxIterations);
|
||||||
|
controlsLayout->addStretch();
|
||||||
|
|
||||||
|
auto layout = new QVBoxLayout();
|
||||||
|
layout->addLayout(controlsLayout);
|
||||||
|
layout->addWidget(_macroEdit);
|
||||||
|
setLayout(layout);
|
||||||
|
|
||||||
|
_macroEdit->HideElseSection();
|
||||||
|
|
||||||
|
_entryData = entryData;
|
||||||
|
UpdateEntryData();
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MacroActionLoopEdit::~MacroActionLoopEdit()
|
||||||
|
{
|
||||||
|
if (!_entryData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_entryData->_customWidgetHeight = GetCustomHeight();
|
||||||
|
_macroEdit->SetMacro({});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroActionLoopEdit::UpdateEntryData()
|
||||||
|
{
|
||||||
|
if (!_entryData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_maxIterations->SetValue(_entryData->_maxIterations);
|
||||||
|
_macroEdit->SetMacro(_entryData->_loopMacro);
|
||||||
|
|
||||||
|
if (_macroEdit->IsEmpty()) {
|
||||||
|
_macroEdit->ShowAllMacroSections();
|
||||||
|
_entryData->_customWidgetHeight = 600;
|
||||||
|
}
|
||||||
|
SetResizingEnabled(true);
|
||||||
|
SetCustomHeight(_entryData->_customWidgetHeight);
|
||||||
|
|
||||||
|
adjustSize();
|
||||||
|
updateGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroActionLoopEdit::MaxIterationsChanged(const NumberVariable<int> &value)
|
||||||
|
{
|
||||||
|
GUARD_LOADING_AND_LOCK();
|
||||||
|
_entryData->_maxIterations = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
64
lib/macro/macro-action-loop.hpp
Normal file
64
lib/macro/macro-action-loop.hpp
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "macro-action-edit.hpp"
|
||||||
|
#include "macro-edit.hpp"
|
||||||
|
#include "resizable-widget.hpp"
|
||||||
|
#include "variable-spinbox.hpp"
|
||||||
|
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
class MacroActionLoop : public MacroAction {
|
||||||
|
public:
|
||||||
|
MacroActionLoop(Macro *m) : MacroAction(m) {}
|
||||||
|
bool PerformAction();
|
||||||
|
void LogAction() const;
|
||||||
|
bool Save(obs_data_t *obj) const;
|
||||||
|
bool Load(obs_data_t *obj);
|
||||||
|
bool PostLoad();
|
||||||
|
std::string GetId() const { return id; }
|
||||||
|
static std::shared_ptr<MacroAction> Create(Macro *m);
|
||||||
|
std::shared_ptr<MacroAction> Copy() const;
|
||||||
|
void ResolveVariablesToFixedValues();
|
||||||
|
|
||||||
|
std::shared_ptr<Macro> _loopMacro = std::make_shared<Macro>();
|
||||||
|
IntVariable _maxIterations = 100;
|
||||||
|
int _customWidgetHeight = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SetupTempVars();
|
||||||
|
|
||||||
|
static bool _registered;
|
||||||
|
static const std::string id;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MacroActionLoopEdit : public ResizableWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
MacroActionLoopEdit(
|
||||||
|
QWidget *parent,
|
||||||
|
std::shared_ptr<MacroActionLoop> entryData = nullptr);
|
||||||
|
~MacroActionLoopEdit();
|
||||||
|
void UpdateEntryData();
|
||||||
|
static QWidget *Create(QWidget *parent,
|
||||||
|
std::shared_ptr<MacroAction> action)
|
||||||
|
{
|
||||||
|
return new MacroActionLoopEdit(
|
||||||
|
parent,
|
||||||
|
std::dynamic_pointer_cast<MacroActionLoop>(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void MaxIterationsChanged(const NumberVariable<int> &value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
MacroEdit *_macroEdit;
|
||||||
|
VariableSpinBox *_maxIterations;
|
||||||
|
|
||||||
|
std::shared_ptr<MacroActionLoop> _entryData;
|
||||||
|
bool _loading = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
#include "macro-action-macro.hpp"
|
#include "macro-action-macro.hpp"
|
||||||
|
|
||||||
|
#include "condition-logic.hpp"
|
||||||
#include "help-icon.hpp"
|
#include "help-icon.hpp"
|
||||||
#include "layout-helpers.hpp"
|
#include "layout-helpers.hpp"
|
||||||
#include "macro.hpp"
|
|
||||||
#include "macro-action-factory.hpp"
|
#include "macro-action-factory.hpp"
|
||||||
|
#include "macro-helpers.hpp"
|
||||||
|
#include "macro.hpp"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
@@ -263,6 +266,7 @@ bool MacroActionMacro::Load(obs_data_t *obj)
|
|||||||
if (obs_data_has_user_value(obj, "nestedMacro")) {
|
if (obs_data_has_user_value(obj, "nestedMacro")) {
|
||||||
OBSDataAutoRelease nestedMacroData =
|
OBSDataAutoRelease nestedMacroData =
|
||||||
obs_data_get_obj(obj, "nestedMacro");
|
obs_data_get_obj(obj, "nestedMacro");
|
||||||
|
_nestedMacro = std::make_shared<Macro>();
|
||||||
_nestedMacro->Load(nestedMacroData);
|
_nestedMacro->Load(nestedMacroData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,6 +313,15 @@ void MacroActionMacro::ResolveVariablesToFixedValues()
|
|||||||
{
|
{
|
||||||
_actionIndex.ResolveVariables();
|
_actionIndex.ResolveVariables();
|
||||||
_label.ResolveVariables();
|
_label.ResolveVariables();
|
||||||
|
|
||||||
|
if (_action == Action::NESTED_MACRO) {
|
||||||
|
for (auto &action : _nestedMacro->Actions()) {
|
||||||
|
action->ResolveVariablesToFixedValues();
|
||||||
|
}
|
||||||
|
for (auto &action : _nestedMacro->ElseActions()) {
|
||||||
|
action->ResolveVariablesToFixedValues();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void runActionsHelper(Macro *macro, bool runElseActions, bool setInputs,
|
static void runActionsHelper(Macro *macro, bool runElseActions, bool setInputs,
|
||||||
@@ -871,6 +884,8 @@ void MacroActionMacroEdit::SetWidgetVisibility()
|
|||||||
} else {
|
} else {
|
||||||
_noConditionsWarning->setVisible(false);
|
_noConditionsWarning->setVisible(false);
|
||||||
}
|
}
|
||||||
|
_runMacroHelp->setVisible(action ==
|
||||||
|
MacroActionMacro::Action::RUN_MACRO);
|
||||||
|
|
||||||
_nestedMacro->setVisible(action ==
|
_nestedMacro->setVisible(action ==
|
||||||
MacroActionMacro::Action::NESTED_MACRO);
|
MacroActionMacro::Action::NESTED_MACRO);
|
||||||
|
|||||||
@@ -447,7 +447,19 @@ bool MacroActionVariable::PerformAction()
|
|||||||
if (!value.has_value()) {
|
if (!value.has_value()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
var->SetValue(*value);
|
|
||||||
|
if (!_jsonQueryExtractSingle) {
|
||||||
|
var->SetValue(*value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto extracted = ExtractSingleJsonArrayElement(*value);
|
||||||
|
if (extracted.has_value()) {
|
||||||
|
var->SetValue(*extracted);
|
||||||
|
} else {
|
||||||
|
var->SetValue(*value);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case Action::ARRAY_JSON: {
|
case Action::ARRAY_JSON: {
|
||||||
@@ -551,6 +563,8 @@ bool MacroActionVariable::Save(obs_data_t *obj) const
|
|||||||
obs_data_set_bool(obj, "allowRepeatValues", _allowRepeatValues);
|
obs_data_set_bool(obj, "allowRepeatValues", _allowRepeatValues);
|
||||||
_jsonQuery.Save(obj, "jsonQuery");
|
_jsonQuery.Save(obj, "jsonQuery");
|
||||||
_jsonIndex.Save(obj, "jsonIndex");
|
_jsonIndex.Save(obj, "jsonIndex");
|
||||||
|
obs_data_set_bool(obj, "jsonQueryExtractSingle",
|
||||||
|
_jsonQueryExtractSingle);
|
||||||
|
|
||||||
obs_data_set_int(obj, "version", 1);
|
obs_data_set_int(obj, "version", 1);
|
||||||
|
|
||||||
@@ -611,6 +625,8 @@ bool MacroActionVariable::Load(obs_data_t *obj)
|
|||||||
_allowRepeatValues = obs_data_get_bool(obj, "allowRepeatValues");
|
_allowRepeatValues = obs_data_get_bool(obj, "allowRepeatValues");
|
||||||
_jsonQuery.Load(obj, "jsonQuery");
|
_jsonQuery.Load(obj, "jsonQuery");
|
||||||
_jsonIndex.Load(obj, "jsonIndex");
|
_jsonIndex.Load(obj, "jsonIndex");
|
||||||
|
_jsonQueryExtractSingle =
|
||||||
|
obs_data_get_bool(obj, "jsonQueryExtractSingle");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -904,6 +920,15 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
|||||||
"AdvSceneSwitcher.action.variable.type.queryJson.info"),
|
"AdvSceneSwitcher.action.variable.type.queryJson.info"),
|
||||||
this)),
|
this)),
|
||||||
_jsonIndex(new VariableSpinBox(this)),
|
_jsonIndex(new VariableSpinBox(this)),
|
||||||
|
_jsonExtractSingle(new QCheckBox(
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.action.variable.type.queryJson.extractSingle"),
|
||||||
|
this)),
|
||||||
|
_jsonExtractSingleHelp(new HelpIcon(
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.action.variable.type.queryJson.extractSingle.info"),
|
||||||
|
this)),
|
||||||
|
_jsonQueryLayout(new QVBoxLayout()),
|
||||||
_entryLayout(new QHBoxLayout())
|
_entryLayout(new QHBoxLayout())
|
||||||
{
|
{
|
||||||
_numValue->setMinimum(-9999999999);
|
_numValue->setMinimum(-9999999999);
|
||||||
@@ -935,6 +960,11 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
|||||||
_randomNumberEnd->setMaximum(9999999999);
|
_randomNumberEnd->setMaximum(9999999999);
|
||||||
_randomValues->SetMaxStringSize(99999999);
|
_randomValues->SetMaxStringSize(99999999);
|
||||||
_jsonIndex->setMaximum(999);
|
_jsonIndex->setMaximum(999);
|
||||||
|
auto jsonExtractSingleLayout = new QHBoxLayout();
|
||||||
|
jsonExtractSingleLayout->addWidget(_jsonExtractSingle);
|
||||||
|
jsonExtractSingleLayout->addWidget(_jsonExtractSingleHelp);
|
||||||
|
jsonExtractSingleLayout->addStretch();
|
||||||
|
_jsonQueryLayout->addLayout(jsonExtractSingleLayout);
|
||||||
|
|
||||||
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
|
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
|
||||||
this, SLOT(VariableChanged(const QString &)));
|
this, SLOT(VariableChanged(const QString &)));
|
||||||
@@ -1033,6 +1063,8 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
|||||||
_jsonIndex,
|
_jsonIndex,
|
||||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||||
this, SLOT(JsonIndexChanged(const NumberVariable<int> &)));
|
this, SLOT(JsonIndexChanged(const NumberVariable<int> &)));
|
||||||
|
QWidget::connect(_jsonExtractSingle, SIGNAL(stateChanged(int)), this,
|
||||||
|
SLOT(JsonQueryExtractSingleChanged(int)));
|
||||||
|
|
||||||
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||||
{"{{variables}}", _variables},
|
{"{{variables}}", _variables},
|
||||||
@@ -1109,6 +1141,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
|||||||
|
|
||||||
auto layout = new QVBoxLayout;
|
auto layout = new QVBoxLayout;
|
||||||
layout->addLayout(_entryLayout);
|
layout->addLayout(_entryLayout);
|
||||||
|
layout->addLayout(_jsonQueryLayout);
|
||||||
layout->addLayout(_substringLayout);
|
layout->addLayout(_substringLayout);
|
||||||
layout->addWidget(_segmentValueStatus);
|
layout->addWidget(_segmentValueStatus);
|
||||||
layout->addWidget(_segmentValue);
|
layout->addWidget(_segmentValue);
|
||||||
@@ -1180,6 +1213,7 @@ void MacroActionVariableEdit::UpdateEntryData()
|
|||||||
_randomValues->SetStringList(_entryData->_randomValues);
|
_randomValues->SetStringList(_entryData->_randomValues);
|
||||||
_jsonQuery->setText(_entryData->_jsonQuery);
|
_jsonQuery->setText(_entryData->_jsonQuery);
|
||||||
_jsonIndex->SetValue(_entryData->_jsonIndex);
|
_jsonIndex->SetValue(_entryData->_jsonIndex);
|
||||||
|
_jsonExtractSingle->setChecked(_entryData->_jsonQueryExtractSingle);
|
||||||
|
|
||||||
SetWidgetVisibility();
|
SetWidgetVisibility();
|
||||||
}
|
}
|
||||||
@@ -1545,6 +1579,12 @@ void MacroActionVariableEdit::JsonIndexChanged(const NumberVariable<int> &value)
|
|||||||
_entryData->_jsonIndex = value;
|
_entryData->_jsonIndex = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroActionVariableEdit::JsonQueryExtractSingleChanged(int value)
|
||||||
|
{
|
||||||
|
GUARD_LOADING_AND_LOCK();
|
||||||
|
_entryData->_jsonQueryExtractSingle = value;
|
||||||
|
}
|
||||||
|
|
||||||
void MacroActionVariableEdit::SetWidgetVisibility()
|
void MacroActionVariableEdit::SetWidgetVisibility()
|
||||||
{
|
{
|
||||||
if (!_entryData) {
|
if (!_entryData) {
|
||||||
@@ -1593,6 +1633,10 @@ void MacroActionVariableEdit::SetWidgetVisibility()
|
|||||||
MacroActionVariable::Action::TRUNCATE) {
|
MacroActionVariable::Action::TRUNCATE) {
|
||||||
layoutString = obs_module_text(
|
layoutString = obs_module_text(
|
||||||
"AdvSceneSwitcher.action.variable.layout.truncate");
|
"AdvSceneSwitcher.action.variable.layout.truncate");
|
||||||
|
} else if (_entryData->_action ==
|
||||||
|
MacroActionVariable::Action::COPY_VAR) {
|
||||||
|
layoutString = obs_module_text(
|
||||||
|
"AdvSceneSwitcher.action.variable.layout.copy");
|
||||||
} else {
|
} else {
|
||||||
layoutString = obs_module_text(
|
layoutString = obs_module_text(
|
||||||
"AdvSceneSwitcher.action.variable.layout.other");
|
"AdvSceneSwitcher.action.variable.layout.other");
|
||||||
@@ -1740,6 +1784,9 @@ void MacroActionVariableEdit::SetWidgetVisibility()
|
|||||||
MacroActionVariable::Action::QUERY_JSON);
|
MacroActionVariable::Action::QUERY_JSON);
|
||||||
_jsonIndex->setVisible(_entryData->_action ==
|
_jsonIndex->setVisible(_entryData->_action ==
|
||||||
MacroActionVariable::Action::ARRAY_JSON);
|
MacroActionVariable::Action::ARRAY_JSON);
|
||||||
|
SetLayoutVisible(_jsonQueryLayout,
|
||||||
|
_entryData->_action ==
|
||||||
|
MacroActionVariable::Action::QUERY_JSON);
|
||||||
|
|
||||||
adjustSize();
|
adjustSize();
|
||||||
updateGeometry();
|
updateGeometry();
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ public:
|
|||||||
|
|
||||||
StringVariable _jsonQuery = "$.some.nested.value";
|
StringVariable _jsonQuery = "$.some.nested.value";
|
||||||
IntVariable _jsonIndex = 0;
|
IntVariable _jsonIndex = 0;
|
||||||
|
bool _jsonQueryExtractSingle = true;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void DecrementCurrentSegmentVariableRef();
|
void DecrementCurrentSegmentVariableRef();
|
||||||
@@ -187,6 +188,7 @@ private slots:
|
|||||||
void AllowRepeatValuesChanged(int);
|
void AllowRepeatValuesChanged(int);
|
||||||
void JsonQueryChanged();
|
void JsonQueryChanged();
|
||||||
void JsonIndexChanged(const NumberVariable<int> &);
|
void JsonIndexChanged(const NumberVariable<int> &);
|
||||||
|
void JsonQueryExtractSingleChanged(int);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void HeaderInfoChanged(const QString &);
|
void HeaderInfoChanged(const QString &);
|
||||||
@@ -239,8 +241,11 @@ private:
|
|||||||
QCheckBox *_allowRepeatValues;
|
QCheckBox *_allowRepeatValues;
|
||||||
QVBoxLayout *_randomValueLayout;
|
QVBoxLayout *_randomValueLayout;
|
||||||
VariableLineEdit *_jsonQuery;
|
VariableLineEdit *_jsonQuery;
|
||||||
QLabel *_jsonQueryHelp;
|
HelpIcon *_jsonQueryHelp;
|
||||||
VariableSpinBox *_jsonIndex;
|
VariableSpinBox *_jsonIndex;
|
||||||
|
QCheckBox *_jsonExtractSingle;
|
||||||
|
HelpIcon *_jsonExtractSingleHelp;
|
||||||
|
QVBoxLayout *_jsonQueryLayout;
|
||||||
QHBoxLayout *_entryLayout;
|
QHBoxLayout *_entryLayout;
|
||||||
|
|
||||||
std::shared_ptr<MacroActionVariable> _entryData;
|
std::shared_ptr<MacroActionVariable> _entryData;
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
#include "macro-condition-edit.hpp"
|
#include "macro-condition-edit.hpp"
|
||||||
|
|
||||||
#include "advanced-scene-switcher.hpp"
|
#include "advanced-scene-switcher.hpp"
|
||||||
|
#include "condition-logic.hpp"
|
||||||
#include "macro-settings.hpp"
|
#include "macro-settings.hpp"
|
||||||
|
#include "macro-signals.hpp"
|
||||||
#include "macro.hpp"
|
#include "macro.hpp"
|
||||||
#include "path-helpers.hpp"
|
#include "path-helpers.hpp"
|
||||||
#include "plugin-state-helpers.hpp"
|
#include "plugin-state-helpers.hpp"
|
||||||
#include "section.hpp"
|
#include "section.hpp"
|
||||||
|
#include "switch-button.hpp"
|
||||||
#include "ui-helpers.hpp"
|
#include "ui-helpers.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
|
||||||
@@ -102,12 +106,15 @@ MacroConditionEdit::MacroConditionEdit(
|
|||||||
QWidget *parent, std::shared_ptr<MacroCondition> *entryData,
|
QWidget *parent, std::shared_ptr<MacroCondition> *entryData,
|
||||||
bool isRootCondition)
|
bool isRootCondition)
|
||||||
: MacroSegmentEdit(parent),
|
: MacroSegmentEdit(parent),
|
||||||
|
_enable(new SwitchButton()),
|
||||||
_logicSelection(new QComboBox()),
|
_logicSelection(new QComboBox()),
|
||||||
_conditionSelection(new FilterComboBox()),
|
_conditionSelection(new FilterComboBox()),
|
||||||
_dur(new DurationModifierEdit()),
|
_dur(new DurationModifierEdit()),
|
||||||
_entryData(entryData),
|
_entryData(entryData),
|
||||||
_isRoot(isRootCondition)
|
_isRoot(isRootCondition)
|
||||||
{
|
{
|
||||||
|
QWidget::connect(_enable, SIGNAL(checked(bool)), this,
|
||||||
|
SLOT(ConditionEnableChanged(bool)));
|
||||||
QWidget::connect(_logicSelection, SIGNAL(currentIndexChanged(int)),
|
QWidget::connect(_logicSelection, SIGNAL(currentIndexChanged(int)),
|
||||||
this, SLOT(LogicSelectionChanged(int)));
|
this, SLOT(LogicSelectionChanged(int)));
|
||||||
QWidget::connect(_conditionSelection,
|
QWidget::connect(_conditionSelection,
|
||||||
@@ -122,6 +129,7 @@ MacroConditionEdit::MacroConditionEdit(
|
|||||||
Logic::PopulateLogicTypeSelection(_logicSelection, isRootCondition);
|
Logic::PopulateLogicTypeSelection(_logicSelection, isRootCondition);
|
||||||
populateConditionSelection(_conditionSelection);
|
populateConditionSelection(_conditionSelection);
|
||||||
|
|
||||||
|
_section->AddHeaderWidget(_enable);
|
||||||
_section->AddHeaderWidget(_logicSelection);
|
_section->AddHeaderWidget(_logicSelection);
|
||||||
_section->AddHeaderWidget(_conditionSelection);
|
_section->AddHeaderWidget(_conditionSelection);
|
||||||
_section->AddHeaderWidget(_headerInfo);
|
_section->AddHeaderWidget(_headerInfo);
|
||||||
@@ -154,8 +162,17 @@ void MacroConditionEdit::LogicSelectionChanged(int idx)
|
|||||||
const auto logic = static_cast<Logic::Type>(
|
const auto logic = static_cast<Logic::Type>(
|
||||||
_logicSelection->itemData(idx).toInt());
|
_logicSelection->itemData(idx).toInt());
|
||||||
(*_entryData)->SetLogicType(logic);
|
(*_entryData)->SetLogicType(logic);
|
||||||
|
}
|
||||||
|
|
||||||
SetEnableAppearance(logic != Logic::Type::NONE);
|
void MacroConditionEdit::ConditionEnableChanged(bool value)
|
||||||
|
{
|
||||||
|
if (_loading || !_entryData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lock = LockContext();
|
||||||
|
(*_entryData)->SetEnabled(value);
|
||||||
|
SetDisableEffect(!value);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroConditionEdit::IsRootNode() const
|
bool MacroConditionEdit::IsRootNode() const
|
||||||
@@ -168,7 +185,9 @@ void MacroConditionEdit::SetLogicSelection()
|
|||||||
const auto logic = (*_entryData)->GetLogicType();
|
const auto logic = (*_entryData)->GetLogicType();
|
||||||
_logicSelection->setCurrentIndex(
|
_logicSelection->setCurrentIndex(
|
||||||
_logicSelection->findData(static_cast<int>(logic)));
|
_logicSelection->findData(static_cast<int>(logic)));
|
||||||
SetEnableAppearance(logic != Logic::Type::NONE);
|
const bool enabled = (*_entryData)->Enabled();
|
||||||
|
_enable->setChecked(enabled);
|
||||||
|
SetEnableAppearance(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroConditionEdit::SetRootNode(bool root)
|
void MacroConditionEdit::SetRootNode(bool root)
|
||||||
@@ -239,10 +258,12 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
|
|||||||
{
|
{
|
||||||
auto lock = LockContext();
|
auto lock = LockContext();
|
||||||
auto logic = (*_entryData)->GetLogicType();
|
auto logic = (*_entryData)->GetLogicType();
|
||||||
|
const bool enabled = (*_entryData)->Enabled();
|
||||||
_entryData->reset();
|
_entryData->reset();
|
||||||
*_entryData = MacroConditionFactory::Create(id, macro);
|
*_entryData = MacroConditionFactory::Create(id, macro);
|
||||||
(*_entryData)->SetIndex(idx);
|
(*_entryData)->SetIndex(idx);
|
||||||
(*_entryData)->SetLogicType(logic);
|
(*_entryData)->SetLogicType(logic);
|
||||||
|
(*_entryData)->SetEnabled(enabled);
|
||||||
(*_entryData)->PostLoad();
|
(*_entryData)->PostLoad();
|
||||||
RunAndClearPostLoadSteps();
|
RunAndClearPostLoadSteps();
|
||||||
}
|
}
|
||||||
@@ -256,6 +277,9 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
|
|||||||
SetupVarMappings((*_entryData).get());
|
SetupVarMappings((*_entryData).get());
|
||||||
_dur->setVisible(MacroConditionFactory::UsesDurationModifier(id));
|
_dur->setVisible(MacroConditionFactory::UsesDurationModifier(id));
|
||||||
SetFocusPolicyOfWidgets();
|
SetFocusPolicyOfWidgets();
|
||||||
|
|
||||||
|
emit MacroSignalManager::Instance()->ConditionTypeCreated(
|
||||||
|
QString::fromStdString(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroConditionEdit::DurationChanged(const Duration &seconds)
|
void MacroConditionEdit::DurationChanged(const Duration &seconds)
|
||||||
@@ -283,4 +307,9 @@ std::shared_ptr<MacroSegment> MacroConditionEdit::Data() const
|
|||||||
return *_entryData;
|
return *_entryData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroConditionEdit::FocusTypeSelection()
|
||||||
|
{
|
||||||
|
_conditionSelection->setFocus();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace advss
|
} // namespace advss
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
|
class SwitchButton;
|
||||||
|
|
||||||
class DurationModifierEdit : public QWidget {
|
class DurationModifierEdit : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
@@ -41,17 +43,20 @@ public:
|
|||||||
void SetRootNode(bool);
|
void SetRootNode(bool);
|
||||||
void SetupWidgets(bool basicSetup = false);
|
void SetupWidgets(bool basicSetup = false);
|
||||||
void SetEntryData(std::shared_ptr<MacroCondition> *);
|
void SetEntryData(std::shared_ptr<MacroCondition> *);
|
||||||
|
void FocusTypeSelection() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void LogicSelectionChanged(int idx);
|
void LogicSelectionChanged(int idx);
|
||||||
void ConditionSelectionChanged(const QString &text);
|
void ConditionSelectionChanged(const QString &text);
|
||||||
void DurationChanged(const Duration &value);
|
void DurationChanged(const Duration &value);
|
||||||
void DurationModifierChanged(DurationModifier::Type m);
|
void DurationModifierChanged(DurationModifier::Type m);
|
||||||
|
void ConditionEnableChanged(bool);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SetLogicSelection();
|
void SetLogicSelection();
|
||||||
std::shared_ptr<MacroSegment> Data() const;
|
std::shared_ptr<MacroSegment> Data() const;
|
||||||
|
|
||||||
|
SwitchButton *_enable;
|
||||||
QComboBox *_logicSelection;
|
QComboBox *_logicSelection;
|
||||||
FilterComboBox *_conditionSelection;
|
FilterComboBox *_conditionSelection;
|
||||||
DurationModifierEdit *_dur;
|
DurationModifierEdit *_dur;
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#include "macro-condition-macro.hpp"
|
#include "macro-condition-macro.hpp"
|
||||||
|
|
||||||
#include "layout-helpers.hpp"
|
#include "layout-helpers.hpp"
|
||||||
#include "macro-action-edit.hpp"
|
#include "macro-action-edit.hpp"
|
||||||
|
#include "macro-helpers.hpp"
|
||||||
|
#include "macro-ref.hpp"
|
||||||
#include "macro-signals.hpp"
|
#include "macro-signals.hpp"
|
||||||
#include "macro.hpp"
|
#include "macro.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "macro-condition.hpp"
|
#include "macro-condition.hpp"
|
||||||
|
|
||||||
|
#include "condition-logic.hpp"
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
MacroCondition::MacroCondition(Macro *m, bool supportsVariableValue)
|
MacroCondition::MacroCondition(Macro *m, bool supportsVariableValue)
|
||||||
@@ -31,6 +33,10 @@ bool MacroCondition::Load(obs_data_t *obj)
|
|||||||
{
|
{
|
||||||
MacroSegment::Load(obj);
|
MacroSegment::Load(obj);
|
||||||
_logic.Load(obj, "logic");
|
_logic.Load(obj, "logic");
|
||||||
|
if (_logic.GetType() == Logic::Type::NONE) {
|
||||||
|
SetEnabled(false);
|
||||||
|
_logic.SetType(Logic::Type::AND);
|
||||||
|
}
|
||||||
_durationModifier.Load(obj);
|
_durationModifier.Load(obj);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -50,7 +56,8 @@ void MacroCondition::ValidateLogicSelection(bool isRootCondition,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logic.SetType(Logic::Type::NONE);
|
_logic.SetType(Logic::Type::AND);
|
||||||
|
SetEnabled(false);
|
||||||
blog(LOG_WARNING,
|
blog(LOG_WARNING,
|
||||||
"setting invalid logic selection to 'ignore' for macro %s",
|
"setting invalid logic selection to 'ignore' for macro %s",
|
||||||
context);
|
context);
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
#include "macro-dock-settings.hpp"
|
#include "macro-dock-settings.hpp"
|
||||||
#include "macro-dock.hpp"
|
|
||||||
#include "macro-dock-window.hpp"
|
#include "macro-dock-window.hpp"
|
||||||
|
#include "macro-dock.hpp"
|
||||||
|
#include "macro-helpers.hpp"
|
||||||
#include "macro.hpp"
|
#include "macro.hpp"
|
||||||
#include "plugin-state-helpers.hpp"
|
#include "plugin-state-helpers.hpp"
|
||||||
|
#include "variable-string.hpp"
|
||||||
|
|
||||||
#include <obs-frontend-api.h>
|
#include <obs-frontend-api.h>
|
||||||
#include <util/platform.h>
|
#include <util/platform.h>
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
#include "macro-edit.hpp"
|
#include "macro-edit.hpp"
|
||||||
|
|
||||||
|
#include "condition-logic.hpp"
|
||||||
#include "cursor-shape-changer.hpp"
|
#include "cursor-shape-changer.hpp"
|
||||||
#include "macro.hpp"
|
|
||||||
#include "macro-action-edit.hpp"
|
#include "macro-action-edit.hpp"
|
||||||
#include "macro-action-macro.hpp"
|
#include "macro-action-macro.hpp"
|
||||||
#include "macro-condition-edit.hpp"
|
#include "macro-condition-edit.hpp"
|
||||||
|
#include "macro-helpers.hpp"
|
||||||
#include "macro-segment-copy-paste.hpp"
|
#include "macro-segment-copy-paste.hpp"
|
||||||
#include "macro-segment-list.hpp"
|
#include "macro-segment-list.hpp"
|
||||||
#include "macro-settings.hpp"
|
#include "macro-settings.hpp"
|
||||||
#include "macro-signals.hpp"
|
#include "macro-signals.hpp"
|
||||||
|
#include "macro.hpp"
|
||||||
#include "math-helpers.hpp"
|
#include "math-helpers.hpp"
|
||||||
#include "name-dialog.hpp"
|
#include "name-dialog.hpp"
|
||||||
#include "path-helpers.hpp"
|
|
||||||
#include "obs-module-helper.hpp"
|
#include "obs-module-helper.hpp"
|
||||||
|
#include "path-helpers.hpp"
|
||||||
#include "plugin-state-helpers.hpp"
|
#include "plugin-state-helpers.hpp"
|
||||||
#include "splitter-helpers.hpp"
|
#include "splitter-helpers.hpp"
|
||||||
#include "tab-helpers.hpp"
|
#include "tab-helpers.hpp"
|
||||||
@@ -650,6 +653,18 @@ void MacroEdit::HighlightCondition(int idx, QColor color) const
|
|||||||
ui->conditionsList->Highlight(idx, color);
|
ui->conditionsList->Highlight(idx, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroEdit::ScrollAndFocusNewSegment(MacroSegmentList *list, int newIdx,
|
||||||
|
int *currentIdx)
|
||||||
|
{
|
||||||
|
QTimer::singleShot(0, this, [this, list, newIdx, currentIdx]() {
|
||||||
|
list->ensureWidgetVisible(list->WidgetAt(*currentIdx));
|
||||||
|
auto *widget = list->WidgetAt(newIdx);
|
||||||
|
if (widget) {
|
||||||
|
widget->FocusTypeSelection();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static void resetSegmentHighlights(MacroSegmentList *list)
|
static void resetSegmentHighlights(MacroSegmentList *list)
|
||||||
{
|
{
|
||||||
MacroSegmentEdit *widget = nullptr;
|
MacroSegmentEdit *widget = nullptr;
|
||||||
@@ -1087,10 +1102,7 @@ void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id,
|
|||||||
ui->actionsList->SetHelpMsgVisible(false);
|
ui->actionsList->SetHelpMsgVisible(false);
|
||||||
emit(MacroSegmentOrderChanged());
|
emit(MacroSegmentOrderChanged());
|
||||||
|
|
||||||
QTimer::singleShot(0, this, [this]() {
|
ScrollAndFocusNewSegment(ui->actionsList, idx, ¤tActionIdx);
|
||||||
ui->actionsList->ensureWidgetVisible(
|
|
||||||
ui->actionsList->WidgetAt(currentActionIdx));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroEdit::AddMacroAction(int idx)
|
void MacroEdit::AddMacroAction(int idx)
|
||||||
@@ -1118,6 +1130,9 @@ void MacroEdit::AddMacroAction(int idx)
|
|||||||
macro->Actions().at(idx - 1)->Save(data);
|
macro->Actions().at(idx - 1)->Save(data);
|
||||||
}
|
}
|
||||||
AddMacroAction(macro.get(), idx, id, data);
|
AddMacroAction(macro.get(), idx, id, data);
|
||||||
|
if (idx < (int)macro->Actions().size()) {
|
||||||
|
macro->Actions().at(idx)->FixupVarMappingRefs();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroEdit::on_actionAdd_clicked()
|
void MacroEdit::on_actionAdd_clicked()
|
||||||
@@ -1395,10 +1410,8 @@ void MacroEdit::AddMacroElseAction(Macro *macro, int idx, const std::string &id,
|
|||||||
ui->elseActionsList->SetHelpMsgVisible(false);
|
ui->elseActionsList->SetHelpMsgVisible(false);
|
||||||
emit(MacroSegmentOrderChanged());
|
emit(MacroSegmentOrderChanged());
|
||||||
|
|
||||||
QTimer::singleShot(0, this, [this]() {
|
ScrollAndFocusNewSegment(ui->elseActionsList, idx,
|
||||||
ui->elseActionsList->ensureWidgetVisible(
|
¤tElseActionIdx);
|
||||||
ui->elseActionsList->WidgetAt(currentElseActionIdx));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroEdit::AddMacroElseAction(int idx)
|
void MacroEdit::AddMacroElseAction(int idx)
|
||||||
@@ -1426,6 +1439,9 @@ void MacroEdit::AddMacroElseAction(int idx)
|
|||||||
macro->ElseActions().at(idx - 1)->Save(data);
|
macro->ElseActions().at(idx - 1)->Save(data);
|
||||||
}
|
}
|
||||||
AddMacroElseAction(macro.get(), idx, id, data);
|
AddMacroElseAction(macro.get(), idx, id, data);
|
||||||
|
if (idx < (int)macro->ElseActions().size()) {
|
||||||
|
macro->ElseActions().at(idx)->FixupVarMappingRefs();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroEdit::RemoveMacroElseAction(int idx)
|
void MacroEdit::RemoveMacroElseAction(int idx)
|
||||||
@@ -1570,6 +1586,9 @@ void MacroEdit::AddMacroCondition(int idx)
|
|||||||
macro->Conditions().at(idx - 1)->Save(data);
|
macro->Conditions().at(idx - 1)->Save(data);
|
||||||
}
|
}
|
||||||
AddMacroCondition(macro.get(), idx, id, data.Get(), logic);
|
AddMacroCondition(macro.get(), idx, id, data.Get(), logic);
|
||||||
|
if (idx < (int)macro->Conditions().size()) {
|
||||||
|
macro->Conditions().at(idx)->FixupVarMappingRefs();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
|
void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
|
||||||
@@ -1602,10 +1621,7 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
|
|||||||
ui->conditionsList->SetHelpMsgVisible(false);
|
ui->conditionsList->SetHelpMsgVisible(false);
|
||||||
emit(MacroSegmentOrderChanged());
|
emit(MacroSegmentOrderChanged());
|
||||||
|
|
||||||
QTimer::singleShot(0, this, [this]() {
|
ScrollAndFocusNewSegment(ui->conditionsList, idx, ¤tConditionIdx);
|
||||||
ui->conditionsList->ensureWidgetVisible(
|
|
||||||
ui->conditionsList->WidgetAt(currentConditionIdx));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroEdit::on_conditionAdd_clicked()
|
void MacroEdit::on_conditionAdd_clicked()
|
||||||
@@ -1837,6 +1853,13 @@ bool MacroEdit::IsEmpty() const
|
|||||||
ui->elseActionsList->IsEmpty();
|
ui->elseActionsList->IsEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroEdit::HideElseSection() const
|
||||||
|
{
|
||||||
|
ui->toggleElseActions->setVisible(false);
|
||||||
|
ui->macroElseActions->setMaximumHeight(0);
|
||||||
|
ui->macroElseActionSplitter->handle(1)->setCursor(Qt::ArrowCursor);
|
||||||
|
}
|
||||||
|
|
||||||
void MacroEdit::ShowAllMacroSections()
|
void MacroEdit::ShowAllMacroSections()
|
||||||
{
|
{
|
||||||
if (!ElseSectionIsVisible()) {
|
if (!ElseSectionIsVisible()) {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public:
|
|||||||
void PasteMacroSegment();
|
void PasteMacroSegment();
|
||||||
bool IsEmpty() const;
|
bool IsEmpty() const;
|
||||||
void ShowAllMacroSections();
|
void ShowAllMacroSections();
|
||||||
|
void HideElseSection() const;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void on_conditionAdd_clicked();
|
void on_conditionAdd_clicked();
|
||||||
@@ -116,6 +117,8 @@ private:
|
|||||||
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 PopulateMacroElseActions(Macro &m, uint32_t afterIdx = 0);
|
||||||
void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0);
|
void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0);
|
||||||
|
void ScrollAndFocusNewSegment(MacroSegmentList *list, int newIdx,
|
||||||
|
int *currentIdx);
|
||||||
void SetupMacroSegmentSelection(MacroSection type, int idx);
|
void SetupMacroSegmentSelection(MacroSection type, int idx);
|
||||||
void
|
void
|
||||||
SetupContextMenu(const QPoint &pos,
|
SetupContextMenu(const QPoint &pos,
|
||||||
|
|||||||
89
lib/macro/macro-export-builtin-extensions.cpp
Normal file
89
lib/macro/macro-export-builtin-extensions.cpp
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
#include "macro-export-extensions.hpp"
|
||||||
|
|
||||||
|
#include "action-queue.hpp"
|
||||||
|
#include "variable.hpp"
|
||||||
|
|
||||||
|
#include <obs.hpp>
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
static bool setup();
|
||||||
|
static bool setupDone = setup();
|
||||||
|
|
||||||
|
static bool setup()
|
||||||
|
{
|
||||||
|
// --- Variables ---
|
||||||
|
AddMacroExportExtension(
|
||||||
|
{"AdvSceneSwitcher.macroTab.export.variables", "variables",
|
||||||
|
[](obs_data_t *data, const QStringList &selectedIds) {
|
||||||
|
if (selectedIds.isEmpty()) {
|
||||||
|
SaveVariables(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OBSDataArrayAutoRelease array =
|
||||||
|
obs_data_array_create();
|
||||||
|
for (const auto &v : GetVariables()) {
|
||||||
|
const QString name =
|
||||||
|
QString::fromStdString(v->Name());
|
||||||
|
if (!selectedIds.contains(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
OBSDataAutoRelease item = obs_data_create();
|
||||||
|
v->Save(item);
|
||||||
|
obs_data_array_push_back(array, item);
|
||||||
|
}
|
||||||
|
obs_data_set_array(data, "variables", array);
|
||||||
|
},
|
||||||
|
[](obs_data_t *data, const QStringList &) {
|
||||||
|
ImportVariables(data);
|
||||||
|
},
|
||||||
|
[]() -> QList<QPair<QString, QString>> {
|
||||||
|
QList<QPair<QString, QString>> items;
|
||||||
|
for (const auto &v : GetVariables()) {
|
||||||
|
const QString name =
|
||||||
|
QString::fromStdString(v->Name());
|
||||||
|
items.append({name, name});
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}});
|
||||||
|
|
||||||
|
// --- Action Queues ---
|
||||||
|
AddMacroExportExtension(
|
||||||
|
{"AdvSceneSwitcher.macroTab.export.actionQueues",
|
||||||
|
"actionQueues",
|
||||||
|
[](obs_data_t *data, const QStringList &selectedIds) {
|
||||||
|
if (selectedIds.isEmpty()) {
|
||||||
|
SaveActionQueues(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OBSDataArrayAutoRelease array =
|
||||||
|
obs_data_array_create();
|
||||||
|
for (const auto &q : GetActionQueues()) {
|
||||||
|
const QString name =
|
||||||
|
QString::fromStdString(q->Name());
|
||||||
|
if (!selectedIds.contains(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
OBSDataAutoRelease item = obs_data_create();
|
||||||
|
q->Save(item);
|
||||||
|
obs_data_array_push_back(array, item);
|
||||||
|
}
|
||||||
|
obs_data_set_array(data, "actionQueues", array);
|
||||||
|
},
|
||||||
|
[](obs_data_t *data, const QStringList &) {
|
||||||
|
ImportQueues(data);
|
||||||
|
},
|
||||||
|
[]() -> QList<QPair<QString, QString>> {
|
||||||
|
QList<QPair<QString, QString>> items;
|
||||||
|
for (const auto &q : GetActionQueues()) {
|
||||||
|
const QString name =
|
||||||
|
QString::fromStdString(q->Name());
|
||||||
|
items.append({name, name});
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
17
lib/macro/macro-export-extensions.cpp
Normal file
17
lib/macro/macro-export-extensions.cpp
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#include "macro-export-extensions.hpp"
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
static std::vector<MacroExportExtension> extensions;
|
||||||
|
|
||||||
|
void AddMacroExportExtension(const MacroExportExtension &ext)
|
||||||
|
{
|
||||||
|
extensions.emplace_back(ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<MacroExportExtension> &GetMacroExportExtensions()
|
||||||
|
{
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
41
lib/macro/macro-export-extensions.hpp
Normal file
41
lib/macro/macro-export-extensions.hpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "export-symbol-helper.hpp"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <obs-data.h>
|
||||||
|
#include <QList>
|
||||||
|
#include <QPair>
|
||||||
|
#include <QString>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
struct MacroExportExtension {
|
||||||
|
const char *displayNameKey;
|
||||||
|
const char *jsonKey;
|
||||||
|
|
||||||
|
// Export callback.
|
||||||
|
// Write content into 'data'. 'selectedIds' contains the item IDs the
|
||||||
|
// user explicitly checked; if empty (no per-item widget provided) save
|
||||||
|
// everything.
|
||||||
|
std::function<void(obs_data_t *data, const QStringList &selectedIds)>
|
||||||
|
save;
|
||||||
|
|
||||||
|
// Import callback.
|
||||||
|
// Read content from 'data'. 'selectedIds' contains the item IDs the
|
||||||
|
// user explicitly checked; if empty import every item found in 'data'.
|
||||||
|
std::function<void(obs_data_t *data, const QStringList &selectedIds)>
|
||||||
|
load;
|
||||||
|
|
||||||
|
// Optional: enumerate items available for export as (id, displayName)
|
||||||
|
// pairs. When nullptr the extension appears as a single all-or-nothing
|
||||||
|
// checkbox in the export dialog.
|
||||||
|
std::function<QList<QPair<QString, QString>>()> getExportItems =
|
||||||
|
nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPORT void AddMacroExportExtension(const MacroExportExtension &ext);
|
||||||
|
const std::vector<MacroExportExtension> &GetMacroExportExtensions();
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
@@ -1,22 +1,183 @@
|
|||||||
#include "macro-export-import-dialog.hpp"
|
#include "macro-export-import-dialog.hpp"
|
||||||
|
#include "macro-export-extensions.hpp"
|
||||||
#include "obs-module-helper.hpp"
|
#include "obs-module-helper.hpp"
|
||||||
|
#include "section.hpp"
|
||||||
|
|
||||||
#include <obs.hpp>
|
#include <obs.hpp>
|
||||||
#include <QLayout>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLayout>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QSplitter>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
static bool usePlainText = false;
|
static bool usePlainText = false;
|
||||||
|
|
||||||
MacroExportImportDialog::MacroExportImportDialog(Type type)
|
// ---------------------------------------------------------------------------
|
||||||
|
// Compress / decompress helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static QString compressMacroString(const QString &input)
|
||||||
|
{
|
||||||
|
QByteArray inputData = input.toUtf8();
|
||||||
|
auto compressedData = qCompress(inputData);
|
||||||
|
QByteArray encodedData = compressedData.toBase64();
|
||||||
|
return QString::fromUtf8(encodedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString decompressMacroString(const QString &input)
|
||||||
|
{
|
||||||
|
QByteArray encodedData = input.toUtf8();
|
||||||
|
QByteArray compressedData = QByteArray::fromBase64(encodedData);
|
||||||
|
auto outputData = qUncompress(compressedData);
|
||||||
|
return QString::fromUtf8(outputData);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isValidData(const QString &json)
|
||||||
|
{
|
||||||
|
OBSDataAutoRelease data =
|
||||||
|
obs_data_create_from_json(json.toStdString().c_str());
|
||||||
|
return !!data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Extension section
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
QWidget *MacroExportImportDialog::BuildExtensionWidget()
|
||||||
|
{
|
||||||
|
auto wrapper = new QWidget(this);
|
||||||
|
auto wrapperLayout = new QVBoxLayout(wrapper);
|
||||||
|
wrapperLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
wrapperLayout->setSpacing(4);
|
||||||
|
|
||||||
|
wrapperLayout->addWidget(new QLabel(obs_module_text(
|
||||||
|
"AdvSceneSwitcher.macroTab.export.additionalContent")));
|
||||||
|
|
||||||
|
auto outerContent = new QWidget();
|
||||||
|
auto outerLayout = new QVBoxLayout(outerContent);
|
||||||
|
outerLayout->setContentsMargins(4, 4, 4, 4);
|
||||||
|
outerLayout->setSpacing(4);
|
||||||
|
|
||||||
|
const auto &extensions = GetMacroExportExtensions();
|
||||||
|
|
||||||
|
std::vector<int> order(extensions.size());
|
||||||
|
std::iota(order.begin(), order.end(), 0);
|
||||||
|
std::sort(order.begin(), order.end(), [&](int a, int b) {
|
||||||
|
return QString::localeAwareCompare(
|
||||||
|
obs_module_text(extensions[a].displayNameKey),
|
||||||
|
obs_module_text(extensions[b].displayNameKey)) <
|
||||||
|
0;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int extIdx : order) {
|
||||||
|
const auto &ext = extensions[extIdx];
|
||||||
|
ExtensionUI ui;
|
||||||
|
|
||||||
|
// Simple all-or-nothing checkbox (no sub-items).
|
||||||
|
if (!ext.getExportItems) {
|
||||||
|
ui.mainCheck = new QCheckBox(
|
||||||
|
obs_module_text(ext.displayNameKey),
|
||||||
|
outerContent);
|
||||||
|
ui.mainCheck->setChecked(false);
|
||||||
|
connect(ui.mainCheck, &QCheckBox::stateChanged, this,
|
||||||
|
&MacroExportImportDialog::UpdateExportString);
|
||||||
|
outerLayout->addWidget(ui.mainCheck);
|
||||||
|
_extensionUIs.append(std::move(ui));
|
||||||
|
_extensionOrder.push_back(extIdx);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extensions with per-item selection get their own inner
|
||||||
|
// Section: the main checkbox sits in the header (next to the
|
||||||
|
// toggle arrow) and the per-item checkboxes live in the
|
||||||
|
// collapsible content area.
|
||||||
|
auto items = ext.getExportItems();
|
||||||
|
if (items.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(items.begin(), items.end(),
|
||||||
|
[](const QPair<QString, QString> &a,
|
||||||
|
const QPair<QString, QString> &b) {
|
||||||
|
return QString::localeAwareCompare(
|
||||||
|
a.second, b.second) < 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto innerSection = new Section(200, outerContent);
|
||||||
|
|
||||||
|
ui.mainCheck =
|
||||||
|
new QCheckBox(obs_module_text(ext.displayNameKey));
|
||||||
|
ui.mainCheck->setChecked(false);
|
||||||
|
innerSection->AddHeaderWidget(ui.mainCheck);
|
||||||
|
|
||||||
|
auto subWidget = new QWidget();
|
||||||
|
auto subLayout = new QVBoxLayout(subWidget);
|
||||||
|
subLayout->setContentsMargins(4, 2, 4, 2);
|
||||||
|
subLayout->setSpacing(2);
|
||||||
|
|
||||||
|
for (const auto &[id, displayName] : items) {
|
||||||
|
auto cb = new QCheckBox(displayName, subWidget);
|
||||||
|
cb->setChecked(false);
|
||||||
|
subLayout->addWidget(cb);
|
||||||
|
ui.itemChecks.append({id, cb});
|
||||||
|
connect(cb, &QCheckBox::stateChanged, this,
|
||||||
|
&MacroExportImportDialog::UpdateExportString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main checkbox toggles all sub-items.
|
||||||
|
// Capture uiIdx (position in _extensionUIs after append).
|
||||||
|
const int uiIdx = _extensionUIs.size();
|
||||||
|
connect(ui.mainCheck, &QCheckBox::stateChanged, this,
|
||||||
|
[this, uiIdx](int state) {
|
||||||
|
if (state == Qt::PartiallyChecked)
|
||||||
|
return;
|
||||||
|
const bool checked = (state == Qt::Checked);
|
||||||
|
if (uiIdx < _extensionUIs.size()) {
|
||||||
|
for (auto &[id, cb] :
|
||||||
|
_extensionUIs[uiIdx].itemChecks) {
|
||||||
|
QSignalBlocker b(cb);
|
||||||
|
cb->setChecked(checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UpdateExportString();
|
||||||
|
});
|
||||||
|
|
||||||
|
innerSection->SetContent(subWidget, true);
|
||||||
|
outerLayout->addWidget(innerSection);
|
||||||
|
_extensionUIs.append(std::move(ui));
|
||||||
|
_extensionOrder.push_back(extIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
outerLayout->addStretch();
|
||||||
|
|
||||||
|
auto scrollArea = new QScrollArea(wrapper);
|
||||||
|
scrollArea->setWidget(outerContent);
|
||||||
|
scrollArea->setWidgetResizable(true);
|
||||||
|
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
|
scrollArea->setFrameShape(QFrame::NoFrame);
|
||||||
|
wrapperLayout->addWidget(scrollArea);
|
||||||
|
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constructor
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
MacroExportImportDialog::MacroExportImportDialog(Type type,
|
||||||
|
const QString &baseJson)
|
||||||
: QDialog(nullptr),
|
: QDialog(nullptr),
|
||||||
|
_baseJson(baseJson),
|
||||||
_importExportString(new QPlainTextEdit(this)),
|
_importExportString(new QPlainTextEdit(this)),
|
||||||
_usePlainText(new QCheckBox(obs_module_text(
|
_usePlainText(new QCheckBox(obs_module_text(
|
||||||
"AdvSceneSwitcher.macroTab.export.usePlainText")))
|
"AdvSceneSwitcher.macroTab.export.usePlainText")))
|
||||||
{
|
{
|
||||||
_importExportString->setReadOnly(type == Type::EXPORT_MACRO);
|
_importExportString->setReadOnly(type == Type::EXPORT_MACRO);
|
||||||
|
|
||||||
auto label = new QLabel(obs_module_text(
|
auto label = new QLabel(obs_module_text(
|
||||||
type == Type::EXPORT_MACRO
|
type == Type::EXPORT_MACRO
|
||||||
? "AdvSceneSwitcher.macroTab.export.info"
|
? "AdvSceneSwitcher.macroTab.export.info"
|
||||||
@@ -39,40 +200,90 @@ MacroExportImportDialog::MacroExportImportDialog(Type type)
|
|||||||
connect(_usePlainText, &QCheckBox::stateChanged, this,
|
connect(_usePlainText, &QCheckBox::stateChanged, this,
|
||||||
&MacroExportImportDialog::UsePlainTextChanged);
|
&MacroExportImportDialog::UsePlainTextChanged);
|
||||||
|
|
||||||
auto layout = new QVBoxLayout;
|
auto layout = new QVBoxLayout(this);
|
||||||
layout->addWidget(label);
|
|
||||||
layout->addWidget(_importExportString);
|
if (type == Type::EXPORT_MACRO && !GetMacroExportExtensions().empty()) {
|
||||||
layout->addWidget(_usePlainText);
|
auto splitter = new QSplitter(Qt::Vertical, this);
|
||||||
|
splitter->setChildrenCollapsible(false);
|
||||||
|
splitter->addWidget(BuildExtensionWidget());
|
||||||
|
|
||||||
|
auto textWidget = new QWidget(splitter);
|
||||||
|
auto textLayout = new QVBoxLayout(textWidget);
|
||||||
|
textLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
textLayout->addWidget(label);
|
||||||
|
textLayout->addWidget(_importExportString);
|
||||||
|
textLayout->addWidget(_usePlainText);
|
||||||
|
splitter->addWidget(textWidget);
|
||||||
|
|
||||||
|
layout->addWidget(splitter);
|
||||||
|
} else {
|
||||||
|
layout->addWidget(label);
|
||||||
|
layout->addWidget(_importExportString);
|
||||||
|
layout->addWidget(_usePlainText);
|
||||||
|
}
|
||||||
|
|
||||||
layout->addWidget(buttons);
|
layout->addWidget(buttons);
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
|
|
||||||
setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
|
setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
|
||||||
|
|
||||||
|
if (type == Type::EXPORT_MACRO) {
|
||||||
|
RefreshExportText();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString compressMacroString(const QString &input)
|
// ---------------------------------------------------------------------------
|
||||||
|
// Export string helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
QString MacroExportImportDialog::BuildExportJson() const
|
||||||
{
|
{
|
||||||
QByteArray inputData = input.toUtf8();
|
OBSDataAutoRelease data =
|
||||||
auto compressedData = qCompress(inputData);
|
obs_data_create_from_json(_baseJson.toStdString().c_str());
|
||||||
QByteArray encodedData = compressedData.toBase64();
|
if (!data) {
|
||||||
return QString::fromUtf8(encodedData);
|
return _baseJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &extensions = GetMacroExportExtensions();
|
||||||
|
for (int i = 0; i < (int)_extensionUIs.size(); ++i) {
|
||||||
|
const auto &ui = _extensionUIs[i];
|
||||||
|
|
||||||
|
QStringList selectedIds;
|
||||||
|
if (ui.itemChecks.isEmpty()) {
|
||||||
|
if (!ui.mainCheck->isChecked()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const auto &[id, cb] : ui.itemChecks) {
|
||||||
|
if (cb->isChecked()) {
|
||||||
|
selectedIds << id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selectedIds.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions[_extensionOrder[i]].save(data, selectedIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *json = obs_data_get_json(data);
|
||||||
|
return json ? QString::fromUtf8(json) : QString{};
|
||||||
}
|
}
|
||||||
|
|
||||||
QString decompressMacroString(const QString &input)
|
void MacroExportImportDialog::RefreshExportText()
|
||||||
{
|
{
|
||||||
QByteArray encodedData = input.toUtf8();
|
const QString json = BuildExportJson();
|
||||||
QByteArray compressedData = QByteArray::fromBase64(encodedData);
|
if (usePlainText) {
|
||||||
auto outputData = qUncompress(compressedData);
|
_importExportString->setPlainText(json);
|
||||||
return QString::fromUtf8(outputData);
|
} else {
|
||||||
|
_importExportString->setPlainText(compressMacroString(json));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroExportImportDialog::ExportMacros(const QString &json)
|
void MacroExportImportDialog::UpdateExportString()
|
||||||
{
|
{
|
||||||
MacroExportImportDialog dialog(
|
RefreshExportText();
|
||||||
MacroExportImportDialog::Type::EXPORT_MACRO);
|
|
||||||
dialog._importExportString->setPlainText(compressMacroString(json));
|
|
||||||
dialog.adjustSize();
|
|
||||||
dialog.updateGeometry();
|
|
||||||
dialog.exec();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroExportImportDialog::UsePlainTextChanged(int value)
|
void MacroExportImportDialog::UsePlainTextChanged(int value)
|
||||||
@@ -90,26 +301,45 @@ void MacroExportImportDialog::UsePlainTextChanged(int value)
|
|||||||
usePlainText = value;
|
usePlainText = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isValidData(const QString &json)
|
// ---------------------------------------------------------------------------
|
||||||
|
// Static entry points
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void MacroExportImportDialog::ExportMacros(obs_data_t *macroData)
|
||||||
{
|
{
|
||||||
OBSDataAutoRelease data =
|
const char *rawJson = obs_data_get_json(macroData);
|
||||||
obs_data_create_from_json(json.toStdString().c_str());
|
const QString baseJson = rawJson ? QString::fromUtf8(rawJson)
|
||||||
return !!data;
|
: QString{};
|
||||||
|
|
||||||
|
MacroExportImportDialog dialog(Type::EXPORT_MACRO, baseJson);
|
||||||
|
dialog.adjustSize();
|
||||||
|
dialog.updateGeometry();
|
||||||
|
dialog.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroExportImportDialog::ImportMacros(QString &json)
|
bool MacroExportImportDialog::ImportMacros(QString &json)
|
||||||
{
|
{
|
||||||
MacroExportImportDialog dialog(
|
MacroExportImportDialog dialog(Type::IMPORT_MACRO);
|
||||||
MacroExportImportDialog::Type::IMPORT_MACRO);
|
if (dialog.exec() != QDialog::Accepted) {
|
||||||
if (dialog.exec() == QDialog::Accepted) {
|
return false;
|
||||||
json = decompressMacroString(
|
|
||||||
dialog._importExportString->toPlainText());
|
|
||||||
if (!isValidData(json)) { // Fallback to support raw json format
|
|
||||||
json = dialog._importExportString->toPlainText();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
json = decompressMacroString(dialog._importExportString->toPlainText());
|
||||||
|
if (!isValidData(json)) {
|
||||||
|
// Fallback: support raw (uncompressed) JSON pasted directly.
|
||||||
|
json = dialog._importExportString->toPlainText();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke all extension load callbacks.
|
||||||
|
OBSDataAutoRelease data =
|
||||||
|
obs_data_create_from_json(json.toStdString().c_str());
|
||||||
|
if (data) {
|
||||||
|
for (const auto &ext : GetMacroExportExtensions()) {
|
||||||
|
ext.load(data, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace advss
|
} // namespace advss
|
||||||
|
|||||||
@@ -1,25 +1,47 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
#include <QList>
|
||||||
#include <QPlainTextEdit>
|
#include <QPlainTextEdit>
|
||||||
|
|
||||||
|
#include <obs-data.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
class MacroExportImportDialog : public QDialog {
|
class MacroExportImportDialog : public QDialog {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum class Type { EXPORT_MACRO, IMPORT_MACRO };
|
enum class Type { EXPORT_MACRO, IMPORT_MACRO };
|
||||||
MacroExportImportDialog(Type type);
|
|
||||||
|
|
||||||
static void ExportMacros(const QString &json);
|
static void ExportMacros(obs_data_t *macroData);
|
||||||
static bool ImportMacros(QString &json);
|
static bool ImportMacros(QString &json);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void UsePlainTextChanged(int);
|
void UsePlainTextChanged(int);
|
||||||
|
void UpdateExportString();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
explicit MacroExportImportDialog(Type type,
|
||||||
|
const QString &baseJson = {});
|
||||||
|
|
||||||
|
// Per-extension widgets
|
||||||
|
struct ExtensionUI {
|
||||||
|
QCheckBox *mainCheck = nullptr;
|
||||||
|
// (itemId, checkbox) pairs - empty when no per-item selection.
|
||||||
|
QList<QPair<QString, QCheckBox *>> itemChecks;
|
||||||
|
};
|
||||||
|
|
||||||
|
QWidget *BuildExtensionWidget();
|
||||||
|
QString BuildExportJson() const;
|
||||||
|
void RefreshExportText();
|
||||||
|
|
||||||
|
QString _baseJson;
|
||||||
QPlainTextEdit *_importExportString;
|
QPlainTextEdit *_importExportString;
|
||||||
QCheckBox *_usePlainText;
|
QCheckBox *_usePlainText;
|
||||||
|
QList<ExtensionUI> _extensionUIs;
|
||||||
|
std::vector<int> _extensionOrder;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace advss
|
} // namespace advss
|
||||||
|
|||||||
@@ -214,6 +214,20 @@ bool MacroIsPaused(const Macro *macro)
|
|||||||
return macro ? macro->Paused() : true;
|
return macro ? macro->Paused() : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetMacroPaused(Macro *macro, bool paused)
|
||||||
|
{
|
||||||
|
if (macro) {
|
||||||
|
macro->SetPaused(paused);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StopMacro(Macro *macro)
|
||||||
|
{
|
||||||
|
if (macro) {
|
||||||
|
macro->Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool MacroWasPausedSince(
|
bool MacroWasPausedSince(
|
||||||
const Macro *macro,
|
const Macro *macro,
|
||||||
const std::chrono::high_resolution_clock::time_point &time)
|
const std::chrono::high_resolution_clock::time_point &time)
|
||||||
@@ -237,14 +251,20 @@ void AddMacroHelperThread(Macro *macro, std::thread &&newThread)
|
|||||||
macro->AddHelperThread(std::move(newThread));
|
macro->AddHelperThread(std::move(newThread));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RunMacroActions(Macro *macro)
|
bool RunMacroActions(Macro *macro, bool forceParallel, bool ignorePause)
|
||||||
{
|
{
|
||||||
return macro && macro->PerformActions(true);
|
return macro && macro->PerformActions(true, forceParallel, ignorePause);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RunMacroElseActions(Macro *macro)
|
bool RunMacroElseActions(Macro *macro, bool forceParallel, bool ignorePause)
|
||||||
{
|
{
|
||||||
return macro && macro->PerformActions(false);
|
return macro &&
|
||||||
|
macro->PerformActions(false, forceParallel, ignorePause);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckMacroConditions(Macro *macro, bool ignorePause)
|
||||||
|
{
|
||||||
|
return macro && macro->CheckConditions(ignorePause);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResetMacroConditionTimers(Macro *macro)
|
void ResetMacroConditionTimers(Macro *macro)
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ LastMacroConditionCheckTime(const Macro *);
|
|||||||
|
|
||||||
EXPORT bool MacroIsStopped(const Macro *);
|
EXPORT bool MacroIsStopped(const Macro *);
|
||||||
EXPORT bool MacroIsPaused(const Macro *);
|
EXPORT bool MacroIsPaused(const Macro *);
|
||||||
|
EXPORT void SetMacroPaused(Macro *, bool paused);
|
||||||
|
EXPORT void StopMacro(Macro *);
|
||||||
EXPORT bool
|
EXPORT bool
|
||||||
MacroWasPausedSince(const Macro *,
|
MacroWasPausedSince(const Macro *,
|
||||||
const std::chrono::high_resolution_clock::time_point &);
|
const std::chrono::high_resolution_clock::time_point &);
|
||||||
@@ -66,11 +68,16 @@ EXPORT bool MacroWasCheckedSinceLastStart(const Macro *);
|
|||||||
EXPORT void AddMacroHelperThread(Macro *, std::thread &&);
|
EXPORT void AddMacroHelperThread(Macro *, std::thread &&);
|
||||||
|
|
||||||
EXPORT bool CheckMacros();
|
EXPORT bool CheckMacros();
|
||||||
|
EXPORT bool CheckMacroConditions(Macro *, bool ignorePause = false);
|
||||||
|
|
||||||
EXPORT bool RunMacroActions(Macro *);
|
EXPORT bool RunMacroActions(Macro *, bool forceParallel = false,
|
||||||
bool RunMacroElseActions(Macro *);
|
bool ignorePause = false);
|
||||||
|
EXPORT bool RunMacroElseActions(Macro *, bool forceParallel = false,
|
||||||
|
bool ignorePause = false);
|
||||||
EXPORT bool RunMacros();
|
EXPORT bool RunMacros();
|
||||||
void StopAllMacros();
|
|
||||||
|
void SignalStopAllMacros();
|
||||||
|
void WaitForAllMacros();
|
||||||
|
|
||||||
EXPORT void LoadMacros(obs_data_t *obj);
|
EXPORT void LoadMacros(obs_data_t *obj);
|
||||||
EXPORT void SaveMacros(obs_data_t *obj);
|
EXPORT void SaveMacros(obs_data_t *obj);
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
#include "macro-ref.hpp"
|
#include "macro-ref.hpp"
|
||||||
|
|
||||||
|
#include "macro-helpers.hpp"
|
||||||
#include "macro.hpp"
|
#include "macro.hpp"
|
||||||
|
#include "plugin-state-helpers.hpp"
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
@@ -19,6 +22,7 @@ void MacroRef::Load(obs_data_t *obj)
|
|||||||
auto name = obs_data_get_string(obj, "macro");
|
auto name = obs_data_get_string(obj, "macro");
|
||||||
_postLoadName = name;
|
_postLoadName = name;
|
||||||
_macro = GetWeakMacroByName(name);
|
_macro = GetWeakMacroByName(name);
|
||||||
|
AddPostLoadStep([this]() { MacroRef::PostLoad(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroRef::PostLoad()
|
void MacroRef::PostLoad()
|
||||||
@@ -71,11 +75,11 @@ void LoadMacroList(obs_data_t *obj, std::vector<MacroRef> ¯os,
|
|||||||
{
|
{
|
||||||
obs_data_array_t *array = obs_data_get_array(obj, name.c_str());
|
obs_data_array_t *array = obs_data_get_array(obj, name.c_str());
|
||||||
size_t count = obs_data_array_count(array);
|
size_t count = obs_data_array_count(array);
|
||||||
|
macros.reserve(macros.size() + count);
|
||||||
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(array, i);
|
obs_data_t *array_obj = obs_data_array_item(array, i);
|
||||||
MacroRef ref;
|
macros.emplace_back();
|
||||||
ref.Load(array_obj);
|
macros.back().Load(array_obj);
|
||||||
macros.push_back(ref);
|
|
||||||
obs_data_release(array_obj);
|
obs_data_release(array_obj);
|
||||||
}
|
}
|
||||||
obs_data_array_release(array);
|
obs_data_array_release(array);
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ void MacroRunButton::Pressed()
|
|||||||
const bool abortMacro = DisplayMessage(
|
const bool abortMacro = DisplayMessage(
|
||||||
err.arg(QString::fromStdString(macro->Name())), true);
|
err.arg(QString::fromStdString(macro->Name())), true);
|
||||||
if (abortMacro) {
|
if (abortMacro) {
|
||||||
macro->Stop();
|
macro->SignalStop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "macro-segment-copy-paste.hpp"
|
#include "macro-segment-copy-paste.hpp"
|
||||||
|
|
||||||
#include "advanced-scene-switcher.hpp"
|
#include "advanced-scene-switcher.hpp"
|
||||||
|
#include "condition-logic.hpp"
|
||||||
#include "macro.hpp"
|
#include "macro.hpp"
|
||||||
|
|
||||||
#include <QShortcut>
|
#include <QShortcut>
|
||||||
@@ -72,15 +74,24 @@ void MacroEdit::PasteMacroSegment()
|
|||||||
}
|
}
|
||||||
AddMacroCondition(macro.get(), macro->Conditions().size(),
|
AddMacroCondition(macro.get(), macro->Conditions().size(),
|
||||||
copyInfo.segment->GetId(), data.Get(), logic);
|
copyInfo.segment->GetId(), data.Get(), logic);
|
||||||
|
if (!macro->Conditions().empty()) {
|
||||||
|
macro->Conditions().back()->FixupVarMappingRefs();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MacroSegmentCopyInfo::Type::ACTION:
|
case MacroSegmentCopyInfo::Type::ACTION:
|
||||||
AddMacroAction(macro.get(), macro->Actions().size(),
|
AddMacroAction(macro.get(), macro->Actions().size(),
|
||||||
copyInfo.segment->GetId(), data.Get());
|
copyInfo.segment->GetId(), data.Get());
|
||||||
|
if (!macro->Actions().empty()) {
|
||||||
|
macro->Actions().back()->FixupVarMappingRefs();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case MacroSegmentCopyInfo::Type::ELSE:
|
case MacroSegmentCopyInfo::Type::ELSE:
|
||||||
AddMacroElseAction(macro.get(), macro->ElseActions().size(),
|
AddMacroElseAction(macro.get(), macro->ElseActions().size(),
|
||||||
copyInfo.segment->GetId(), data.Get());
|
copyInfo.segment->GetId(), data.Get());
|
||||||
|
if (!macro->ElseActions().empty()) {
|
||||||
|
macro->ElseActions().back()->FixupVarMappingRefs();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "macro-segment.hpp"
|
#include "macro-segment.hpp"
|
||||||
|
|
||||||
#include "macro.hpp"
|
#include "macro.hpp"
|
||||||
#include "mouse-wheel-guard.hpp"
|
#include "mouse-wheel-guard.hpp"
|
||||||
#include "path-helpers.hpp"
|
#include "path-helpers.hpp"
|
||||||
@@ -8,10 +9,13 @@
|
|||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
|
#include <QFrame>
|
||||||
#include <QGraphicsOpacityEffect>
|
#include <QGraphicsOpacityEffect>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
|
#include <QPushButton>
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
@@ -54,6 +58,23 @@ const std::vector<VarMapping> &MacroSegment::GetVarMappings() const
|
|||||||
void MacroSegment::SetVarMappings(const std::vector<VarMapping> &mappings)
|
void MacroSegment::SetVarMappings(const std::vector<VarMapping> &mappings)
|
||||||
{
|
{
|
||||||
_varMappings = mappings;
|
_varMappings = mappings;
|
||||||
|
IncrementTempVarInUseGeneration();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroSegment::FixupVarMappingRefs()
|
||||||
|
{
|
||||||
|
for (auto &mapping : _varMappings) {
|
||||||
|
if (!mapping.tempVar.HasValidID()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const auto &var : _tempVariables) {
|
||||||
|
if (var.ID() == mapping.tempVar.GetID()) {
|
||||||
|
mapping.tempVar = var.GetRef();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NotifyUIAboutTempVarChange(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<TempVariable> MacroSegment::GetOwnTempVars() const
|
std::vector<TempVariable> MacroSegment::GetOwnTempVars() const
|
||||||
@@ -80,7 +101,7 @@ bool MacroSegment::Save(obs_data_t *obj) const
|
|||||||
obs_data_set_bool(data, "useCustomLabel", _useCustomLabel);
|
obs_data_set_bool(data, "useCustomLabel", _useCustomLabel);
|
||||||
obs_data_set_string(data, "customLabel", _customLabel.c_str());
|
obs_data_set_string(data, "customLabel", _customLabel.c_str());
|
||||||
obs_data_set_bool(data, "enabled", _enabled);
|
obs_data_set_bool(data, "enabled", _enabled);
|
||||||
obs_data_set_int(data, "version", 1);
|
obs_data_set_int(data, "version", 2);
|
||||||
|
|
||||||
OBSDataArrayAutoRelease mappingsArray = obs_data_array_create();
|
OBSDataArrayAutoRelease mappingsArray = obs_data_array_create();
|
||||||
for (const auto &mapping : _varMappings) {
|
for (const auto &mapping : _varMappings) {
|
||||||
@@ -112,6 +133,12 @@ bool MacroSegment::Load(obs_data_t *obj)
|
|||||||
_enabled = obs_data_get_bool(obj, "enabled");
|
_enabled = obs_data_get_bool(obj, "enabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the previously unused "enabled" value for conditions to "true"
|
||||||
|
if (obs_data_get_int(data, "version") < 2 &&
|
||||||
|
obs_data_has_user_value(obj, "logic")) {
|
||||||
|
_enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
_varMappings.clear();
|
_varMappings.clear();
|
||||||
OBSDataArrayAutoRelease mappingsArray =
|
OBSDataArrayAutoRelease mappingsArray =
|
||||||
obs_data_get_array(data, "varMappings");
|
obs_data_get_array(data, "varMappings");
|
||||||
|
|||||||
@@ -7,15 +7,17 @@
|
|||||||
#include "sync-helpers.hpp"
|
#include "sync-helpers.hpp"
|
||||||
#include "temp-variable.hpp"
|
#include "temp-variable.hpp"
|
||||||
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QFrame>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <obs-data.h>
|
#include <obs-data.h>
|
||||||
|
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
class QFrame;
|
||||||
class QLabel;
|
class QLabel;
|
||||||
|
class QVBoxLayout;
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
@@ -57,6 +59,7 @@ public:
|
|||||||
const std::vector<VarMapping> &GetVarMappings() const;
|
const std::vector<VarMapping> &GetVarMappings() const;
|
||||||
void SetVarMappings(const std::vector<VarMapping> &mappings);
|
void SetVarMappings(const std::vector<VarMapping> &mappings);
|
||||||
std::vector<TempVariable> GetOwnTempVars() const;
|
std::vector<TempVariable> GetOwnTempVars() const;
|
||||||
|
void FixupVarMappingRefs();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend bool SupportsVariableValue(MacroSegment *);
|
friend bool SupportsVariableValue(MacroSegment *);
|
||||||
@@ -133,6 +136,7 @@ public:
|
|||||||
void SetSelected(bool);
|
void SetSelected(bool);
|
||||||
virtual std::shared_ptr<MacroSegment> Data() const = 0;
|
virtual std::shared_ptr<MacroSegment> Data() const = 0;
|
||||||
virtual void SetupWidgets(bool basicSetup = false) = 0;
|
virtual void SetupWidgets(bool basicSetup = false) = 0;
|
||||||
|
virtual void FocusTypeSelection() {}
|
||||||
|
|
||||||
void SetupVarMappings(MacroSegment *segment);
|
void SetupVarMappings(MacroSegment *segment);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
#include "macro-selection.hpp"
|
#include "macro-selection.hpp"
|
||||||
|
|
||||||
#include "advanced-scene-switcher.hpp"
|
#include "advanced-scene-switcher.hpp"
|
||||||
#include "layout-helpers.hpp"
|
#include "layout-helpers.hpp"
|
||||||
#include "macro.hpp"
|
#include "macro-helpers.hpp"
|
||||||
|
#include "macro-ref.hpp"
|
||||||
#include "macro-signals.hpp"
|
#include "macro-signals.hpp"
|
||||||
|
#include "macro.hpp"
|
||||||
#include "ui-helpers.hpp"
|
#include "ui-helpers.hpp"
|
||||||
|
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ signals:
|
|||||||
void Remove(const QString &);
|
void Remove(const QString &);
|
||||||
void SegmentOrderChanged();
|
void SegmentOrderChanged();
|
||||||
void HighlightChanged(bool value);
|
void HighlightChanged(bool value);
|
||||||
|
void ConditionTypeCreated(const QString &typeId);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MacroSignalManager(QObject *parent = nullptr);
|
MacroSignalManager(QObject *parent = nullptr);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#include "advanced-scene-switcher.hpp"
|
#include "advanced-scene-switcher.hpp"
|
||||||
#include "action-queue.hpp"
|
|
||||||
#include "macro-action-edit.hpp"
|
#include "macro-action-edit.hpp"
|
||||||
#include "macro-condition-edit.hpp"
|
#include "macro-condition-edit.hpp"
|
||||||
#include "macro-export-import-dialog.hpp"
|
#include "macro-export-import-dialog.hpp"
|
||||||
#include "macro-settings.hpp"
|
#include "macro-helpers.hpp"
|
||||||
#include "macro-search.hpp"
|
#include "macro-search.hpp"
|
||||||
|
#include "macro-settings.hpp"
|
||||||
#include "macro-signals.hpp"
|
#include "macro-signals.hpp"
|
||||||
#include "macro-tree.hpp"
|
#include "macro-tree.hpp"
|
||||||
#include "macro.hpp"
|
#include "macro.hpp"
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
#include <obs-frontend-api.h>
|
#include <obs-frontend-api.h>
|
||||||
|
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
|
|
||||||
@@ -316,13 +317,9 @@ void AdvSceneSwitcher::ExportMacros() const
|
|||||||
obs_data_array_push_back(macroArray, obj);
|
obs_data_array_push_back(macroArray, obj);
|
||||||
}
|
}
|
||||||
obs_data_set_array(data, "macros", macroArray);
|
obs_data_set_array(data, "macros", macroArray);
|
||||||
SaveVariables(data);
|
|
||||||
SaveActionQueues(data);
|
|
||||||
obs_data_set_string(data, "version", g_GIT_TAG);
|
obs_data_set_string(data, "version", g_GIT_TAG);
|
||||||
auto json = obs_data_get_json(data);
|
|
||||||
QString exportString(json);
|
|
||||||
|
|
||||||
MacroExportImportDialog::ExportMacros(exportString);
|
MacroExportImportDialog::ExportMacros(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AdvSceneSwitcher::ResolveMacroImportNameConflict(
|
bool AdvSceneSwitcher::ResolveMacroImportNameConflict(
|
||||||
@@ -384,8 +381,6 @@ void AdvSceneSwitcher::ImportMacros()
|
|||||||
ImportMacros();
|
ImportMacros();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ImportVariables(data);
|
|
||||||
ImportQueues(data);
|
|
||||||
|
|
||||||
auto version = obs_data_get_string(data, "version");
|
auto version = obs_data_get_string(data, "version");
|
||||||
if (strcmp(version, g_GIT_TAG) != 0) {
|
if (strcmp(version, g_GIT_TAG) != 0) {
|
||||||
@@ -402,7 +397,7 @@ void AdvSceneSwitcher::ImportMacros()
|
|||||||
std::vector<std::shared_ptr<Macro>> importedMacros;
|
std::vector<std::shared_ptr<Macro>> importedMacros;
|
||||||
auto &tempMacros = GetTemporaryMacros();
|
auto &tempMacros = GetTemporaryMacros();
|
||||||
|
|
||||||
auto lock = LockContext();
|
std::unique_lock<std::mutex> lock(*GetMutex());
|
||||||
for (size_t i = 0; i < count; i++) {
|
for (size_t i = 0; i < count; i++) {
|
||||||
tempMacros.clear();
|
tempMacros.clear();
|
||||||
|
|
||||||
@@ -412,10 +407,14 @@ void AdvSceneSwitcher::ImportMacros()
|
|||||||
macro->Load(array_obj);
|
macro->Load(array_obj);
|
||||||
RunAndClearPostLoadSteps();
|
RunAndClearPostLoadSteps();
|
||||||
|
|
||||||
if (macroNameExists(macro->Name()) &&
|
if (macroNameExists(macro->Name())) {
|
||||||
!ResolveMacroImportNameConflict(macro)) {
|
lock.unlock();
|
||||||
groupSize--;
|
bool resolved = ResolveMacroImportNameConflict(macro);
|
||||||
continue;
|
lock.lock();
|
||||||
|
if (!resolved) {
|
||||||
|
groupSize--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
importedMacros.emplace_back(macro);
|
importedMacros.emplace_back(macro);
|
||||||
@@ -557,6 +556,10 @@ void AdvSceneSwitcher::HighlightOnChange() const
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (macro->Paused()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (macro->ActionTriggerModePreventedActionsSince(
|
if (macro->ActionTriggerModePreventedActionsSince(
|
||||||
lastOnChangeHighlightCheckTime)) {
|
lastOnChangeHighlightCheckTime)) {
|
||||||
HighlightWidget(ui->actionTriggerMode, Qt::yellow,
|
HighlightWidget(ui->actionTriggerMode, Qt::yellow,
|
||||||
@@ -742,6 +745,17 @@ void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
|
|||||||
remove->setDisabled(ui->macros->SelectionEmpty());
|
remove->setDisabled(ui->macros->SelectionEmpty());
|
||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
|
|
||||||
|
auto pause = menu.addAction(
|
||||||
|
obs_module_text("AdvSceneSwitcher.macroTab.pause"), this,
|
||||||
|
&AdvSceneSwitcher::PauseSelectedMacros);
|
||||||
|
pause->setDisabled(ui->macros->SelectionEmpty());
|
||||||
|
|
||||||
|
auto unpause = menu.addAction(
|
||||||
|
obs_module_text("AdvSceneSwitcher.macroTab.unpause"), this,
|
||||||
|
&AdvSceneSwitcher::UnpauseSelectedMacros);
|
||||||
|
unpause->setDisabled(ui->macros->SelectionEmpty());
|
||||||
|
menu.addSeparator();
|
||||||
|
|
||||||
auto group = menu.addAction(
|
auto group = menu.addAction(
|
||||||
obs_module_text("AdvSceneSwitcher.macroTab.group"), ui->macros,
|
obs_module_text("AdvSceneSwitcher.macroTab.group"), ui->macros,
|
||||||
&MacroTree::GroupSelectedItems);
|
&MacroTree::GroupSelectedItems);
|
||||||
@@ -802,6 +816,48 @@ void AdvSceneSwitcher::CopyMacro()
|
|||||||
MacroSignalManager::Instance()->Add(QString::fromStdString(name));
|
MacroSignalManager::Instance()->Add(QString::fromStdString(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AdvSceneSwitcher::PauseSelectedMacros()
|
||||||
|
{
|
||||||
|
auto selectedMacros = GetSelectedMacros();
|
||||||
|
if (selectedMacros.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lock = LockContext();
|
||||||
|
for (const auto ¯o : selectedMacros) {
|
||||||
|
if (macro->IsGroup()) {
|
||||||
|
for (const auto &subitem :
|
||||||
|
GetGroupMacroEntries(macro.get())) {
|
||||||
|
subitem->SetPaused(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
macro->SetPaused(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui->macros->UpdateRunningStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AdvSceneSwitcher::UnpauseSelectedMacros()
|
||||||
|
{
|
||||||
|
auto selectedMacros = GetSelectedMacros();
|
||||||
|
if (selectedMacros.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lock = LockContext();
|
||||||
|
for (const auto ¯o : selectedMacros) {
|
||||||
|
if (macro->IsGroup()) {
|
||||||
|
for (const auto &subitem :
|
||||||
|
GetGroupMacroEntries(macro.get())) {
|
||||||
|
subitem->SetPaused(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
macro->SetPaused(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui->macros->UpdateRunningStates();
|
||||||
|
}
|
||||||
|
|
||||||
bool MacroTabIsInFocus()
|
bool MacroTabIsInFocus()
|
||||||
{
|
{
|
||||||
return AdvSceneSwitcher::window &&
|
return AdvSceneSwitcher::window &&
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
#include "macro-tree.hpp"
|
#include "macro-tree.hpp"
|
||||||
#include "macro.hpp"
|
|
||||||
|
#include "macro-helpers.hpp"
|
||||||
#include "macro-search.hpp"
|
#include "macro-search.hpp"
|
||||||
#include "macro-signals.hpp"
|
#include "macro-signals.hpp"
|
||||||
|
#include "macro.hpp"
|
||||||
#include "path-helpers.hpp"
|
#include "path-helpers.hpp"
|
||||||
#include "sync-helpers.hpp"
|
#include "sync-helpers.hpp"
|
||||||
#include "ui-helpers.hpp"
|
#include "ui-helpers.hpp"
|
||||||
|
|
||||||
#include <obs.h>
|
#include <obs.h>
|
||||||
#include <string>
|
|
||||||
|
#include <QHBoxLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QSpacerItem>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
#include <QHBoxLayout>
|
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
|
#include <QSpacerItem>
|
||||||
#include <QStylePainter>
|
#include <QStylePainter>
|
||||||
#include <QToolTip>
|
#include <QToolTip>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(std::shared_ptr<advss::Macro>);
|
Q_DECLARE_METATYPE(std::shared_ptr<advss::Macro>);
|
||||||
|
|
||||||
@@ -977,6 +981,17 @@ void MacroTree::UpdateWidgets(bool force)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroTree::UpdateRunningStates()
|
||||||
|
{
|
||||||
|
MacroTreeModel *mtm = GetModel();
|
||||||
|
for (int i = 0; i < (int)mtm->_macros.size(); i++) {
|
||||||
|
auto widget = GetItemWidget(i);
|
||||||
|
if (widget) {
|
||||||
|
widget->UpdateRunning();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static inline void MoveItem(std::deque<std::shared_ptr<Macro>> &items,
|
static inline void MoveItem(std::deque<std::shared_ptr<Macro>> &items,
|
||||||
std::shared_ptr<Macro> &item, int to)
|
std::shared_ptr<Macro> &item, int to)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ public:
|
|||||||
void ExpandGroup(std::shared_ptr<Macro> item) const;
|
void ExpandGroup(std::shared_ptr<Macro> item) const;
|
||||||
void CollapseGroup(std::shared_ptr<Macro> item) const;
|
void CollapseGroup(std::shared_ptr<Macro> item) const;
|
||||||
void RefreshFilter();
|
void RefreshFilter();
|
||||||
|
void UpdateRunningStates();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void GroupSelectedItems();
|
void GroupSelectedItems();
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include "macro.hpp"
|
#include "macro.hpp"
|
||||||
|
|
||||||
|
#include "condition-logic.hpp"
|
||||||
#include "macro-action-factory.hpp"
|
#include "macro-action-factory.hpp"
|
||||||
#include "macro-condition-factory.hpp"
|
#include "macro-condition-factory.hpp"
|
||||||
#include "macro-helpers.hpp"
|
#include "macro-helpers.hpp"
|
||||||
@@ -7,16 +9,29 @@
|
|||||||
#include "splitter-helpers.hpp"
|
#include "splitter-helpers.hpp"
|
||||||
#include "sync-helpers.hpp"
|
#include "sync-helpers.hpp"
|
||||||
|
|
||||||
|
#include <obs-frontend-api.h>
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
#include <QMainWindow>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#undef max
|
#undef max
|
||||||
#include <obs-frontend-api.h>
|
|
||||||
#include <QAction>
|
|
||||||
#include <QMainWindow>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
|
static bool setup()
|
||||||
|
{
|
||||||
|
AddPluginCleanupStep([]() {
|
||||||
|
GetTopLevelMacros().clear();
|
||||||
|
GetTemporaryMacros().clear();
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setupDone = setup();
|
||||||
|
|
||||||
Macro::Macro(const std::string &name) : _dockSettings(this)
|
Macro::Macro(const std::string &name) : _dockSettings(this)
|
||||||
{
|
{
|
||||||
SetName(name);
|
SetName(name);
|
||||||
@@ -118,12 +133,11 @@ static bool checkCondition(const std::shared_ptr<MacroCondition> &condition)
|
|||||||
const auto timeSpent = endTime - startTime;
|
const auto timeSpent = endTime - startTime;
|
||||||
|
|
||||||
if (timeSpent >= perfLogThreshold) {
|
if (timeSpent >= perfLogThreshold) {
|
||||||
const long int ms =
|
|
||||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
timeSpent)
|
|
||||||
.count();
|
|
||||||
blog(LOG_WARNING,
|
blog(LOG_WARNING,
|
||||||
"spent %ld ms in %s condition check of macro '%s'!", ms,
|
"spent %lld ms in %s condition check of macro '%s'!",
|
||||||
|
(long long)std::chrono::duration_cast<
|
||||||
|
std::chrono::milliseconds>(timeSpent)
|
||||||
|
.count(),
|
||||||
condition->GetId().c_str(),
|
condition->GetId().c_str(),
|
||||||
condition->GetMacro()->Name().c_str());
|
condition->GetMacro()->Name().c_str());
|
||||||
}
|
}
|
||||||
@@ -146,6 +160,15 @@ bool Macro::CheckConditionHelper(
|
|||||||
return conditionMatched;
|
return conditionMatched;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!condition->Enabled()) {
|
||||||
|
vblog(LOG_INFO, "ignoring condition '%s' for '%s'",
|
||||||
|
condition->GetId().c_str(), _name.c_str());
|
||||||
|
if (!_useShortCircuitEvaluation) {
|
||||||
|
(void)evaluateCondition();
|
||||||
|
}
|
||||||
|
return _matched;
|
||||||
|
}
|
||||||
|
|
||||||
const auto logicType = condition->GetLogicType();
|
const auto logicType = condition->GetLogicType();
|
||||||
if (logicType == Logic::Type::NONE) {
|
if (logicType == Logic::Type::NONE) {
|
||||||
vblog(LOG_INFO, "ignoring condition '%s' for '%s'",
|
vblog(LOG_INFO, "ignoring condition '%s' for '%s'",
|
||||||
@@ -270,7 +293,7 @@ bool Macro::CheckConditions(bool ignorePause)
|
|||||||
|
|
||||||
const bool hasActionsToExecute = _matched ? (_actions.size() > 0)
|
const bool hasActionsToExecute = _matched ? (_actions.size() > 0)
|
||||||
: (_elseActions.size() > 0);
|
: (_elseActions.size() > 0);
|
||||||
if (!_actionModeMatch && hasActionsToExecute) {
|
if (!_actionModeMatch && hasActionsToExecute && !_paused) {
|
||||||
_lastActionRunModePreventTime =
|
_lastActionRunModePreventTime =
|
||||||
std::chrono::high_resolution_clock::now();
|
std::chrono::high_resolution_clock::now();
|
||||||
}
|
}
|
||||||
@@ -369,7 +392,7 @@ bool Macro::ShouldRunActions() const
|
|||||||
!_paused && (_matched || _elseActions.size() > 0) &&
|
!_paused && (_matched || _elseActions.size() > 0) &&
|
||||||
_actionModeMatch;
|
_actionModeMatch;
|
||||||
|
|
||||||
if (VerboseLoggingEnabled() && !_actionModeMatch) {
|
if (VerboseLoggingEnabled() && !_actionModeMatch && !_paused) {
|
||||||
if (_matched && _actions.size() > 0) {
|
if (_matched && _actions.size() > 0) {
|
||||||
blog(LOG_INFO, "skip actions for Macro %s (on change)",
|
blog(LOG_INFO, "skip actions for Macro %s (on change)",
|
||||||
_name.c_str());
|
_name.c_str());
|
||||||
@@ -533,6 +556,12 @@ Macro::PauseStateSaveBehavior Macro::GetPauseStateSaveBehavior() const
|
|||||||
return _pauseSaveBehavior;
|
return _pauseSaveBehavior;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Macro::SignalStop()
|
||||||
|
{
|
||||||
|
_stop = true;
|
||||||
|
GetMacroWaitCV().notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
void Macro::Stop()
|
void Macro::Stop()
|
||||||
{
|
{
|
||||||
_stop = true;
|
_stop = true;
|
||||||
@@ -1297,7 +1326,14 @@ bool RunMacros()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StopAllMacros()
|
void SignalStopAllMacros()
|
||||||
|
{
|
||||||
|
for (const auto &m : GetAllMacros()) {
|
||||||
|
m->SignalStop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaitForAllMacros()
|
||||||
{
|
{
|
||||||
for (const auto &m : GetAllMacros()) {
|
for (const auto &m : GetAllMacros()) {
|
||||||
m->Stop();
|
m->Stop();
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "macro-action.hpp"
|
#include "macro-action.hpp"
|
||||||
#include "macro-condition.hpp"
|
#include "macro-condition.hpp"
|
||||||
#include "macro-dock-settings.hpp"
|
#include "macro-dock-settings.hpp"
|
||||||
#include "macro-helpers.hpp"
|
|
||||||
#include "macro-input.hpp"
|
#include "macro-input.hpp"
|
||||||
#include "macro-ref.hpp"
|
|
||||||
#include "variable-string.hpp"
|
|
||||||
#include "temp-variable.hpp"
|
#include "temp-variable.hpp"
|
||||||
|
|
||||||
#include <future>
|
|
||||||
#include <string>
|
|
||||||
#include <deque>
|
|
||||||
#include <memory>
|
|
||||||
#include <map>
|
|
||||||
#include <thread>
|
|
||||||
#include <obs.hpp>
|
#include <obs.hpp>
|
||||||
#include <obs-module-helper.hpp>
|
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <future>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
@@ -61,6 +60,7 @@ public:
|
|||||||
PauseStateSaveBehavior GetPauseStateSaveBehavior() const;
|
PauseStateSaveBehavior GetPauseStateSaveBehavior() const;
|
||||||
|
|
||||||
void Stop();
|
void Stop();
|
||||||
|
void SignalStop();
|
||||||
bool GetStop() const { return _stop; }
|
bool GetStop() const { return _stop; }
|
||||||
void ResetTimers();
|
void ResetTimers();
|
||||||
|
|
||||||
@@ -224,12 +224,7 @@ private:
|
|||||||
MacroDockSettings _dockSettings;
|
MacroDockSettings _dockSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
void LoadMacros(obs_data_t *obj);
|
void WaitForAllMacros();
|
||||||
void SaveMacros(obs_data_t *obj);
|
|
||||||
bool CheckMacros();
|
|
||||||
bool RunMacros();
|
|
||||||
void StopAllMacros();
|
|
||||||
void InvalidateMacroTempVarValues();
|
|
||||||
std::shared_ptr<Macro> GetMacroWithInvalidConditionInterval();
|
std::shared_ptr<Macro> GetMacroWithInvalidConditionInterval();
|
||||||
|
|
||||||
} // namespace advss
|
} // namespace advss
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#import <Carbon/Carbon.h>
|
#import <Carbon/Carbon.h>
|
||||||
#include <Carbon/Carbon.h>
|
#include <Carbon/Carbon.h>
|
||||||
#include <util/platform.h>
|
#include <util/platform.h>
|
||||||
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
@@ -14,41 +15,13 @@
|
|||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
std::vector<std::string> GetWindowList()
|
static std::string nsStringToStdString(NSString *str)
|
||||||
{
|
{
|
||||||
std::vector<std::string> windows;
|
if (!str) {
|
||||||
@autoreleasepool {
|
return "";
|
||||||
CFArrayRef cfApps = CGWindowListCopyWindowInfo(
|
|
||||||
kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
|
|
||||||
NSMutableArray *apps = (__bridge NSMutableArray *)cfApps;
|
|
||||||
for (NSDictionary *app in apps) {
|
|
||||||
std::string name([[app objectForKey:@"kCGWindowName"]
|
|
||||||
UTF8String],
|
|
||||||
[[app objectForKey:@"kCGWindowName"]
|
|
||||||
lengthOfBytesUsingEncoding:
|
|
||||||
NSUTF8StringEncoding]);
|
|
||||||
std::string owner(
|
|
||||||
[[app objectForKey:@"kCGWindowOwnerName"]
|
|
||||||
UTF8String],
|
|
||||||
[[app objectForKey:@"kCGWindowOwnerName"]
|
|
||||||
lengthOfBytesUsingEncoding:
|
|
||||||
NSUTF8StringEncoding]);
|
|
||||||
|
|
||||||
if (!name.empty() &&
|
|
||||||
find(windows.begin(), windows.end(), name) ==
|
|
||||||
windows.end()) {
|
|
||||||
windows.emplace_back(name);
|
|
||||||
}
|
|
||||||
if (!owner.empty() &&
|
|
||||||
find(windows.begin(), windows.end(), owner) ==
|
|
||||||
windows.end()) {
|
|
||||||
windows.emplace_back(owner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
apps = nil;
|
|
||||||
CFRelease(cfApps);
|
|
||||||
}
|
}
|
||||||
return windows;
|
const char *utf8 = [str UTF8String];
|
||||||
|
return utf8 ? utf8 : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GetCurrentWindowTitle()
|
std::string GetCurrentWindowTitle()
|
||||||
@@ -100,100 +73,49 @@ std::string GetCurrentWindowTitle()
|
|||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isWindowOriginOnScreen(NSDictionary *app, NSScreen *screen,
|
|
||||||
bool fullscreen = false)
|
|
||||||
{
|
|
||||||
NSArray *screens = [NSScreen screens];
|
|
||||||
NSRect mainScreenFrame = [screens[0] frame];
|
|
||||||
NSRect screenFrame;
|
|
||||||
if (fullscreen) {
|
|
||||||
screenFrame = [screen frame];
|
|
||||||
} else {
|
|
||||||
screenFrame = [screen visibleFrame];
|
|
||||||
}
|
|
||||||
NSRect windowBounds;
|
|
||||||
CGRectMakeWithDictionaryRepresentation(
|
|
||||||
(CFDictionaryRef)[app objectForKey:@"kCGWindowBounds"],
|
|
||||||
&windowBounds);
|
|
||||||
|
|
||||||
return (windowBounds.origin.x == screenFrame.origin.x &&
|
|
||||||
(mainScreenFrame.size.height - screenFrame.size.height -
|
|
||||||
windowBounds.origin.y ==
|
|
||||||
screenFrame.origin.y));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isWindowMaximizedOnScreen(NSDictionary *app, NSScreen *screen)
|
bool isWindowMaximizedOnScreen(NSDictionary *app, NSScreen *screen)
|
||||||
{
|
{
|
||||||
double maximizedTolerance = 0.99;
|
|
||||||
NSRect screenFrame = [screen frame];
|
|
||||||
NSRect windowBounds;
|
NSRect windowBounds;
|
||||||
CGRectMakeWithDictionaryRepresentation(
|
CGRectMakeWithDictionaryRepresentation(
|
||||||
(CFDictionaryRef)[app objectForKey:@"kCGWindowBounds"],
|
(CFDictionaryRef)[app objectForKey:@"kCGWindowBounds"],
|
||||||
&windowBounds);
|
&windowBounds);
|
||||||
|
|
||||||
int sumX = windowBounds.origin.x + windowBounds.size.width;
|
// CGWindowList: flipped coords, origin at top-left of main display.
|
||||||
int sumY = windowBounds.origin.y + windowBounds.size.height;
|
// NSScreen: Cocoa coords, origin at bottom-left of main display.
|
||||||
|
NSRect mainFrame = [[NSScreen screens][0] frame];
|
||||||
|
NSRect visFrame = [screen visibleFrame];
|
||||||
|
|
||||||
// Return false if window spans over multiple screens
|
// Convert visible area to CGWindow coordinates.
|
||||||
if (sumX > screenFrame.size.width) {
|
// screenTop is just below the menubar; screenBottom is just above the
|
||||||
|
// dock.
|
||||||
|
CGFloat screenLeft = visFrame.origin.x;
|
||||||
|
CGFloat screenTop =
|
||||||
|
mainFrame.size.height - visFrame.origin.y - visFrame.size.height;
|
||||||
|
CGFloat screenRight = screenLeft + visFrame.size.width;
|
||||||
|
CGFloat screenBottom = screenTop + visFrame.size.height;
|
||||||
|
|
||||||
|
const double tolerance = 4.0;
|
||||||
|
|
||||||
|
// Width must span the full visible width.
|
||||||
|
if (fabs(windowBounds.origin.x - screenLeft) > tolerance) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (sumY > screenFrame.size.height) {
|
if (fabs((windowBounds.origin.x + windowBounds.size.width) -
|
||||||
|
screenRight) > tolerance) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// Bottom edge must align with the visible bottom (above dock).
|
||||||
return ((double)sumX / (double)screenFrame.size.width) >
|
if (fabs((windowBounds.origin.y + windowBounds.size.height) -
|
||||||
maximizedTolerance &&
|
screenBottom) > tolerance) {
|
||||||
((double)sumY / (double)screenFrame.size.height) >
|
return false;
|
||||||
maximizedTolerance;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool nameMachesPattern(std::string windowName, std::string pattern)
|
|
||||||
{
|
|
||||||
return QString::fromStdString(windowName)
|
|
||||||
.contains(QRegularExpression(QString::fromStdString(pattern)));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsMaximized(const std::string &title)
|
|
||||||
{
|
|
||||||
@autoreleasepool {
|
|
||||||
NSArray *screens = [NSScreen screens];
|
|
||||||
CFArrayRef cfApps = CGWindowListCopyWindowInfo(
|
|
||||||
kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
|
|
||||||
NSMutableArray *apps = (__bridge NSMutableArray *)cfApps;
|
|
||||||
for (NSDictionary *app in apps) {
|
|
||||||
std::string name([[app objectForKey:@"kCGWindowName"]
|
|
||||||
UTF8String],
|
|
||||||
[[app objectForKey:@"kCGWindowName"]
|
|
||||||
lengthOfBytesUsingEncoding:
|
|
||||||
NSUTF8StringEncoding]);
|
|
||||||
std::string owner(
|
|
||||||
[[app objectForKey:@"kCGWindowOwnerName"]
|
|
||||||
UTF8String],
|
|
||||||
[[app objectForKey:@"kCGWindowOwnerName"]
|
|
||||||
lengthOfBytesUsingEncoding:
|
|
||||||
NSUTF8StringEncoding]);
|
|
||||||
|
|
||||||
bool equals = (title == name || title == owner);
|
|
||||||
bool matches = nameMachesPattern(name, title) ||
|
|
||||||
nameMachesPattern(owner, title);
|
|
||||||
if (equals || matches) {
|
|
||||||
for (NSScreen *screen in screens) {
|
|
||||||
if (isWindowOriginOnScreen(app,
|
|
||||||
screen) &&
|
|
||||||
isWindowMaximizedOnScreen(app,
|
|
||||||
screen)) {
|
|
||||||
apps = nil;
|
|
||||||
CFRelease(cfApps);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
apps = nil;
|
|
||||||
CFRelease(cfApps);
|
|
||||||
}
|
}
|
||||||
return false;
|
// Top edge must be at or above the menubar line (some apps extend
|
||||||
|
// their window frame behind the menubar).
|
||||||
|
if (windowBounds.origin.y > screenTop + tolerance) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isWindowFullscreenOnScreen(NSDictionary *app, NSScreen *screen)
|
bool isWindowFullscreenOnScreen(NSDictionary *app, NSScreen *screen)
|
||||||
@@ -207,52 +129,176 @@ bool isWindowFullscreenOnScreen(NSDictionary *app, NSScreen *screen)
|
|||||||
return NSEqualSizes(windowBounds.size, screenFrame.size);
|
return NSEqualSizes(windowBounds.size, screenFrame.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsFullscreen(const std::string &title)
|
std::vector<WindowInfo> GetWindows(const WindowQueryOptions &options)
|
||||||
{
|
{
|
||||||
|
std::vector<WindowInfo> result;
|
||||||
|
const std::string foregroundTitle = GetCurrentWindowTitle();
|
||||||
|
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSArray *screens = [NSScreen screens];
|
NSArray *screens = [NSScreen screens];
|
||||||
CFArrayRef cfApps = CGWindowListCopyWindowInfo(
|
|
||||||
kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
|
|
||||||
NSMutableArray *apps = (__bridge NSMutableArray *)cfApps;
|
|
||||||
for (NSDictionary *app in apps) {
|
|
||||||
std::string name([[app objectForKey:@"kCGWindowName"]
|
|
||||||
UTF8String],
|
|
||||||
[[app objectForKey:@"kCGWindowName"]
|
|
||||||
lengthOfBytesUsingEncoding:
|
|
||||||
NSUTF8StringEncoding]);
|
|
||||||
std::string owner(
|
|
||||||
[[app objectForKey:@"kCGWindowOwnerName"]
|
|
||||||
UTF8String],
|
|
||||||
[[app objectForKey:@"kCGWindowOwnerName"]
|
|
||||||
lengthOfBytesUsingEncoding:
|
|
||||||
NSUTF8StringEncoding]);
|
|
||||||
|
|
||||||
bool equals = (title == name || title == owner);
|
CFArrayRef cfApps = CGWindowListCopyWindowInfo(
|
||||||
bool matches = nameMachesPattern(name, title) ||
|
kCGWindowListExcludeDesktopElements, kCGNullWindowID);
|
||||||
nameMachesPattern(owner, title);
|
NSMutableArray *apps = (__bridge NSMutableArray *)cfApps;
|
||||||
if (equals || matches) {
|
|
||||||
|
// Pre-compute which PIDs own at least one fullscreen-sized window.
|
||||||
|
// A fullscreen app reports a small chrome/title bar window first and
|
||||||
|
// its full-size content window second; checking only the first
|
||||||
|
// window's bounds gives a false negative.
|
||||||
|
std::set<int> fullscreenPIDs;
|
||||||
|
if (options.fullscreen) {
|
||||||
|
for (NSDictionary *app in apps) {
|
||||||
|
int layer = [[app objectForKey:@"kCGWindowLayer"]
|
||||||
|
intValue];
|
||||||
|
if (layer != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for (NSScreen *screen in screens) {
|
for (NSScreen *screen in screens) {
|
||||||
if (isWindowOriginOnScreen(app, screen,
|
if (isWindowFullscreenOnScreen(app,
|
||||||
true) &&
|
screen)) {
|
||||||
isWindowFullscreenOnScreen(
|
int pid = [[app objectForKey:
|
||||||
app, screen)) {
|
@"kCGWindowOwnerPID"]
|
||||||
apps = nil;
|
intValue];
|
||||||
CFRelease(cfApps);
|
fullscreenPIDs.insert(pid);
|
||||||
return true;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::set<int> maximizedPIDs;
|
||||||
|
if (options.maximized) {
|
||||||
|
for (NSDictionary *app in apps) {
|
||||||
|
int layer = [[app objectForKey:@"kCGWindowLayer"]
|
||||||
|
intValue];
|
||||||
|
if (layer != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (NSScreen *screen in screens) {
|
||||||
|
if (isWindowMaximizedOnScreen(app,
|
||||||
|
screen)) {
|
||||||
|
int pid = [[app objectForKey:
|
||||||
|
@"kCGWindowOwnerPID"]
|
||||||
|
intValue];
|
||||||
|
maximizedPIDs.insert(pid);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-compute the largest-area window bounds per PID.
|
||||||
|
// CGWindowList often lists small auxiliary windows (toolbars,
|
||||||
|
// chrome) before the main content window; using the largest area
|
||||||
|
// avoids reporting those wrong bounds for geometry.
|
||||||
|
std::map<int, NSRect> largestBoundsPerPID;
|
||||||
|
if (options.geometry || options.maximized) {
|
||||||
|
for (NSDictionary *app in apps) {
|
||||||
|
int layer = [[app objectForKey:@"kCGWindowLayer"]
|
||||||
|
intValue];
|
||||||
|
if (layer != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
NSRect b = NSZeroRect;
|
||||||
|
CGRectMakeWithDictionaryRepresentation(
|
||||||
|
(CFDictionaryRef)[app
|
||||||
|
objectForKey:@"kCGWindowBounds"],
|
||||||
|
&b);
|
||||||
|
int pid = [[app objectForKey:@"kCGWindowOwnerPID"]
|
||||||
|
intValue];
|
||||||
|
auto it = largestBoundsPerPID.find(pid);
|
||||||
|
if (it == largestBoundsPerPID.end() ||
|
||||||
|
b.size.width * b.size.height >
|
||||||
|
it->second.size.width *
|
||||||
|
it->second.size.height) {
|
||||||
|
largestBoundsPerPID[pid] = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track titles already added to avoid duplicates (name + owner)
|
||||||
|
std::vector<std::string> seen;
|
||||||
|
|
||||||
|
for (NSDictionary *app in apps) {
|
||||||
|
int layer =
|
||||||
|
[[app objectForKey:@"kCGWindowLayer"] intValue];
|
||||||
|
if (layer != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string name = nsStringToStdString(
|
||||||
|
[app objectForKey:@"kCGWindowName"]);
|
||||||
|
std::string owner = nsStringToStdString(
|
||||||
|
[app objectForKey:@"kCGWindowOwnerName"]);
|
||||||
|
|
||||||
|
// Collect geometry once (used for multiple fields)
|
||||||
|
NSRect bounds = NSZeroRect;
|
||||||
|
if (options.geometry || options.fullscreen ||
|
||||||
|
options.maximized) {
|
||||||
|
CGRectMakeWithDictionaryRepresentation(
|
||||||
|
(CFDictionaryRef)[app
|
||||||
|
objectForKey:@"kCGWindowBounds"],
|
||||||
|
&bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process both the window name and the owner name as
|
||||||
|
// separate entries, matching the old GetWindowList() behaviour
|
||||||
|
for (int pass = 0; pass < 2; ++pass) {
|
||||||
|
const std::string &title = (pass == 0) ? name
|
||||||
|
: owner;
|
||||||
|
if (title.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (std::find(seen.begin(), seen.end(),
|
||||||
|
title) != seen.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.emplace_back(title);
|
||||||
|
|
||||||
|
WindowInfo info;
|
||||||
|
info.title = title;
|
||||||
|
|
||||||
|
if (options.focus) {
|
||||||
|
info.focused =
|
||||||
|
(title == foregroundTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.geometry || options.fullscreen ||
|
||||||
|
options.maximized) {
|
||||||
|
int pid = [[app objectForKey:
|
||||||
|
@"kCGWindowOwnerPID"]
|
||||||
|
intValue];
|
||||||
|
auto it = largestBoundsPerPID.find(pid);
|
||||||
|
const NSRect &geoBounds =
|
||||||
|
(it != largestBoundsPerPID.end())
|
||||||
|
? it->second
|
||||||
|
: bounds;
|
||||||
|
info.x = (int)geoBounds.origin.x;
|
||||||
|
info.y = (int)geoBounds.origin.y;
|
||||||
|
info.width = (int)geoBounds.size.width;
|
||||||
|
info.height = (int)geoBounds.size.height;
|
||||||
|
|
||||||
|
if (options.fullscreen) {
|
||||||
|
info.fullscreen =
|
||||||
|
fullscreenPIDs.count(pid) >
|
||||||
|
0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.maximized) {
|
||||||
|
info.maximized =
|
||||||
|
maximizedPIDs.count(pid) >
|
||||||
|
0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// windowClass and text not implemented on macOS
|
||||||
|
result.emplace_back(std::move(info));
|
||||||
|
}
|
||||||
|
}
|
||||||
apps = nil;
|
apps = nil;
|
||||||
CFRelease(cfApps);
|
CFRelease(cfApps);
|
||||||
}
|
}
|
||||||
return false;
|
return result;
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::string> GetTextInWindow(const std::string &)
|
|
||||||
{
|
|
||||||
// Not implemented
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int SecondsSinceLastInput()
|
int SecondsSinceLastInput()
|
||||||
@@ -341,8 +387,7 @@ QStringList GetProcessPathsFromName(const QString &name)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const char *nameStr = appName.UTF8String;
|
const char *nameStr = appName.UTF8String;
|
||||||
if (!nameStr ||
|
if (!nameStr || name != QString::fromUtf8(nameStr)) {
|
||||||
name != QString::fromUtf8(nameStr)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
NSURL *url = app.executableURL;
|
NSURL *url = app.executableURL;
|
||||||
|
|||||||
40
lib/pch.hpp
Normal file
40
lib/pch.hpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// OBS
|
||||||
|
#include <obs-data.h>
|
||||||
|
#include <obs-frontend-api.h>
|
||||||
|
#include <obs.hpp>
|
||||||
|
|
||||||
|
// Qt
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QDoubleSpinBox>
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLayout>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QList>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QSpinBox>
|
||||||
|
#include <QString>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
// Standard library
|
||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <deque>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
@@ -11,11 +11,31 @@ namespace advss {
|
|||||||
|
|
||||||
enum class HotkeyType;
|
enum class HotkeyType;
|
||||||
|
|
||||||
EXPORT std::vector<std::string> GetWindowList();
|
struct WindowQueryOptions {
|
||||||
|
bool geometry = false;
|
||||||
|
bool focus = false;
|
||||||
|
bool fullscreen = false;
|
||||||
|
bool maximized = false;
|
||||||
|
bool windowClass = false; // Windows only, no-op on other platforms
|
||||||
|
bool text = false; // Windows only, expensive, no-op on other platforms
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WindowInfo {
|
||||||
|
std::string title;
|
||||||
|
bool focused = false;
|
||||||
|
bool fullscreen = false;
|
||||||
|
bool maximized = false;
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
std::string windowClass;
|
||||||
|
std::optional<std::string> text;
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPORT std::vector<WindowInfo>
|
||||||
|
GetWindows(const WindowQueryOptions &options = {});
|
||||||
EXPORT std::string GetCurrentWindowTitle();
|
EXPORT std::string GetCurrentWindowTitle();
|
||||||
EXPORT bool IsFullscreen(const std::string &title);
|
|
||||||
EXPORT bool IsMaximized(const std::string &title);
|
|
||||||
EXPORT std::optional<std::string> GetTextInWindow(const std::string &window);
|
|
||||||
EXPORT int SecondsSinceLastInput();
|
EXPORT int SecondsSinceLastInput();
|
||||||
EXPORT QStringList GetProcessList();
|
EXPORT QStringList GetProcessList();
|
||||||
EXPORT std::string GetForegroundProcessName();
|
EXPORT std::string GetForegroundProcessName();
|
||||||
|
|||||||
@@ -16,14 +16,17 @@ static void setupTab(QTabWidget *);
|
|||||||
|
|
||||||
static ActionQueueTable *tabWidget = nullptr;
|
static ActionQueueTable *tabWidget = nullptr;
|
||||||
|
|
||||||
void RegisterActionQueueTab()
|
static bool setup()
|
||||||
{
|
{
|
||||||
AddPluginInitStep([]() {
|
AddPluginInitStep([] {
|
||||||
AddSetupTabCallback("actionQueueTab", ActionQueueTable::Create,
|
AddSetupTabCallback("actionQueueTab", ActionQueueTable::Create,
|
||||||
setupTab);
|
setupTab);
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool setupDone = setup();
|
||||||
|
|
||||||
static void setTabVisible(QTabWidget *tabWidget, bool visible)
|
static void setTabVisible(QTabWidget *tabWidget, bool visible)
|
||||||
{
|
{
|
||||||
SetTabVisibleByName(
|
SetTabVisibleByName(
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
#include "plugin-state-helpers.hpp"
|
#include "plugin-state-helpers.hpp"
|
||||||
#include "ui-helpers.hpp"
|
#include "ui-helpers.hpp"
|
||||||
|
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
static std::deque<std::shared_ptr<Item>> queues;
|
static std::deque<std::shared_ptr<Item>> queues;
|
||||||
@@ -12,20 +15,16 @@ std::deque<std::shared_ptr<Item>> &GetActionQueues()
|
|||||||
return queues;
|
return queues;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RegisterActionQueueTab();
|
static bool setup()
|
||||||
|
|
||||||
void SetupActionQueues()
|
|
||||||
{
|
{
|
||||||
static bool done = false;
|
|
||||||
if (done) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AddSaveStep(SaveActionQueues);
|
AddSaveStep(SaveActionQueues);
|
||||||
AddLoadStep(LoadActionQueues);
|
AddLoadStep(LoadActionQueues);
|
||||||
RegisterActionQueueTab();
|
AddPluginCleanupStep([]() { queues.clear(); });
|
||||||
done = true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool setupDone = setup();
|
||||||
|
|
||||||
ActionQueue::ActionQueue() : Item()
|
ActionQueue::ActionQueue() : Item()
|
||||||
{
|
{
|
||||||
_lastEmpty = std::chrono::high_resolution_clock::now();
|
_lastEmpty = std::chrono::high_resolution_clock::now();
|
||||||
@@ -46,6 +45,7 @@ void ActionQueue::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, "runOnStartup", _runOnStartup);
|
obs_data_set_bool(obj, "runOnStartup", _runOnStartup);
|
||||||
obs_data_set_bool(obj, "resolveVariablesOnAdd", _resolveVariablesOnAdd);
|
obs_data_set_bool(obj, "resolveVariablesOnAdd", _resolveVariablesOnAdd);
|
||||||
|
obs_data_set_bool(obj, "cloneVariableContext", _cloneVariableContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActionQueue::Load(obs_data_t *obj)
|
void ActionQueue::Load(obs_data_t *obj)
|
||||||
@@ -55,6 +55,7 @@ void ActionQueue::Load(obs_data_t *obj)
|
|||||||
_runOnStartup = obs_data_get_bool(obj, "runOnStartup");
|
_runOnStartup = obs_data_get_bool(obj, "runOnStartup");
|
||||||
_resolveVariablesOnAdd =
|
_resolveVariablesOnAdd =
|
||||||
obs_data_get_bool(obj, "resolveVariablesOnAdd");
|
obs_data_get_bool(obj, "resolveVariablesOnAdd");
|
||||||
|
_cloneVariableContext = obs_data_get_bool(obj, "cloneVariableContext");
|
||||||
|
|
||||||
if (_runOnStartup) {
|
if (_runOnStartup) {
|
||||||
Start();
|
Start();
|
||||||
@@ -115,9 +116,17 @@ void ActionQueue::Add(const std::shared_ptr<MacroAction> &action)
|
|||||||
copy->PostLoad();
|
copy->PostLoad();
|
||||||
RunAndClearPostLoadSteps();
|
RunAndClearPostLoadSteps();
|
||||||
copy->ResolveVariablesToFixedValues();
|
copy->ResolveVariablesToFixedValues();
|
||||||
_actions.emplace_back(copy);
|
_actions.push_back({copy, {}});
|
||||||
|
} else if (_cloneVariableContext) {
|
||||||
|
auto copy = action->Copy();
|
||||||
|
OBSDataAutoRelease data = obs_data_create();
|
||||||
|
action->Save(data);
|
||||||
|
copy->Load(data);
|
||||||
|
copy->PostLoad();
|
||||||
|
RunAndClearPostLoadSteps();
|
||||||
|
_actions.push_back({copy, CreateVariableContext()});
|
||||||
} else {
|
} else {
|
||||||
_actions.emplace_back(action);
|
_actions.push_back({action, {}});
|
||||||
}
|
}
|
||||||
_cv.notify_all();
|
_cv.notify_all();
|
||||||
}
|
}
|
||||||
@@ -144,7 +153,7 @@ size_t ActionQueue::Size()
|
|||||||
|
|
||||||
void ActionQueue::RunActions()
|
void ActionQueue::RunActions()
|
||||||
{
|
{
|
||||||
std::shared_ptr<MacroAction> action;
|
QueueEntry entry;
|
||||||
while (true) {
|
while (true) {
|
||||||
{ // Grab next action to run
|
{ // Grab next action to run
|
||||||
std::unique_lock<std::mutex> lock(_mutex);
|
std::unique_lock<std::mutex> lock(_mutex);
|
||||||
@@ -157,20 +166,25 @@ void ActionQueue::RunActions()
|
|||||||
if (_stop) {
|
if (_stop) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
action = _actions.front();
|
entry = _actions.front();
|
||||||
_actions.pop_front();
|
_actions.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!action) {
|
if (!entry.action) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ActionLoggingEnabled()) {
|
if (ActionLoggingEnabled()) {
|
||||||
blog(LOG_INFO, "Performing action '%s' in queue '%s'",
|
blog(LOG_INFO, "Performing action '%s' in queue '%s'",
|
||||||
action->GetId().c_str(), _name.c_str());
|
entry.action->GetId().c_str(), _name.c_str());
|
||||||
action->LogAction();
|
entry.action->LogAction();
|
||||||
}
|
}
|
||||||
action->PerformAction();
|
|
||||||
|
if (entry.context) {
|
||||||
|
SetActiveVariableContext(&*entry.context);
|
||||||
|
}
|
||||||
|
entry.action->PerformAction();
|
||||||
|
SetActiveVariableContext(nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +202,7 @@ ActionQueueSettingsDialog::ActionQueueSettingsDialog(QWidget *parent,
|
|||||||
obs_module_text("AdvSceneSwitcher.actionQueues.clear"))),
|
obs_module_text("AdvSceneSwitcher.actionQueues.clear"))),
|
||||||
_runOnStartup(new QCheckBox()),
|
_runOnStartup(new QCheckBox()),
|
||||||
_resolveVariablesOnAdd(new QCheckBox()),
|
_resolveVariablesOnAdd(new QCheckBox()),
|
||||||
|
_cloneVariableContext(new QCheckBox()),
|
||||||
_queue(settings)
|
_queue(settings)
|
||||||
{
|
{
|
||||||
QWidget::connect(_startStopToggle, SIGNAL(clicked()), this,
|
QWidget::connect(_startStopToggle, SIGNAL(clicked()), this,
|
||||||
@@ -196,6 +211,7 @@ ActionQueueSettingsDialog::ActionQueueSettingsDialog(QWidget *parent,
|
|||||||
|
|
||||||
_runOnStartup->setChecked(settings._runOnStartup);
|
_runOnStartup->setChecked(settings._runOnStartup);
|
||||||
_resolveVariablesOnAdd->setChecked(settings._resolveVariablesOnAdd);
|
_resolveVariablesOnAdd->setChecked(settings._resolveVariablesOnAdd);
|
||||||
|
_cloneVariableContext->setChecked(settings._cloneVariableContext);
|
||||||
UpdateLabels();
|
UpdateLabels();
|
||||||
|
|
||||||
auto layout = new QGridLayout();
|
auto layout = new QGridLayout();
|
||||||
@@ -225,6 +241,14 @@ ActionQueueSettingsDialog::ActionQueueSettingsDialog(QWidget *parent,
|
|||||||
_resolveVariablesOnAdd->setToolTip(obs_module_text(
|
_resolveVariablesOnAdd->setToolTip(obs_module_text(
|
||||||
"AdvSceneSwitcher.actionQueues.resolveVariablesOnAdd"));
|
"AdvSceneSwitcher.actionQueues.resolveVariablesOnAdd"));
|
||||||
++row;
|
++row;
|
||||||
|
layout->addWidget(
|
||||||
|
new QLabel(obs_module_text(
|
||||||
|
"AdvSceneSwitcher.actionQueues.cloneVariableContext")),
|
||||||
|
row, 0);
|
||||||
|
layout->addWidget(_cloneVariableContext, row, 1);
|
||||||
|
_cloneVariableContext->setToolTip(obs_module_text(
|
||||||
|
"AdvSceneSwitcher.actionQueues.cloneVariableContext.tooltip"));
|
||||||
|
++row;
|
||||||
layout->addWidget(_queueRunStatus, row, 0);
|
layout->addWidget(_queueRunStatus, row, 0);
|
||||||
layout->addWidget(_startStopToggle, row, 1);
|
layout->addWidget(_startStopToggle, row, 1);
|
||||||
++row;
|
++row;
|
||||||
@@ -254,6 +278,8 @@ bool ActionQueueSettingsDialog::AskForSettings(QWidget *parent,
|
|||||||
settings._runOnStartup = dialog._runOnStartup->isChecked();
|
settings._runOnStartup = dialog._runOnStartup->isChecked();
|
||||||
settings._resolveVariablesOnAdd =
|
settings._resolveVariablesOnAdd =
|
||||||
dialog._resolveVariablesOnAdd->isChecked();
|
dialog._resolveVariablesOnAdd->isChecked();
|
||||||
|
settings._cloneVariableContext =
|
||||||
|
dialog._cloneVariableContext->isChecked();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "item-selection-helpers.hpp"
|
#include "item-selection-helpers.hpp"
|
||||||
#include "macro-action.hpp"
|
#include "macro-action.hpp"
|
||||||
|
#include "variable.hpp"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <obs-data.h>
|
#include <obs-data.h>
|
||||||
|
#include <optional>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
@@ -17,6 +19,11 @@ class ActionQueueSettingsDialog;
|
|||||||
class ActionQueue : public Item {
|
class ActionQueue : public Item {
|
||||||
using TimePoint = std::chrono::high_resolution_clock::time_point;
|
using TimePoint = std::chrono::high_resolution_clock::time_point;
|
||||||
|
|
||||||
|
struct QueueEntry {
|
||||||
|
std::shared_ptr<MacroAction> action;
|
||||||
|
std::optional<VariableContext> context;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ActionQueue();
|
ActionQueue();
|
||||||
~ActionQueue();
|
~ActionQueue();
|
||||||
@@ -43,11 +50,12 @@ private:
|
|||||||
|
|
||||||
bool _runOnStartup = true;
|
bool _runOnStartup = true;
|
||||||
bool _resolveVariablesOnAdd = true;
|
bool _resolveVariablesOnAdd = true;
|
||||||
|
bool _cloneVariableContext = false;
|
||||||
std::atomic_bool _stop = {true};
|
std::atomic_bool _stop = {true};
|
||||||
std::mutex _mutex;
|
std::mutex _mutex;
|
||||||
std::condition_variable _cv;
|
std::condition_variable _cv;
|
||||||
std::thread _thread;
|
std::thread _thread;
|
||||||
std::deque<std::shared_ptr<MacroAction>> _actions;
|
std::deque<QueueEntry> _actions;
|
||||||
TimePoint _lastEmpty;
|
TimePoint _lastEmpty;
|
||||||
|
|
||||||
friend ActionQueueSelection;
|
friend ActionQueueSelection;
|
||||||
@@ -73,6 +81,7 @@ private:
|
|||||||
QPushButton *_clear;
|
QPushButton *_clear;
|
||||||
QCheckBox *_runOnStartup;
|
QCheckBox *_runOnStartup;
|
||||||
QCheckBox *_resolveVariablesOnAdd;
|
QCheckBox *_resolveVariablesOnAdd;
|
||||||
|
QCheckBox *_cloneVariableContext;
|
||||||
|
|
||||||
ActionQueue &_queue;
|
ActionQueue &_queue;
|
||||||
};
|
};
|
||||||
@@ -101,7 +110,6 @@ signals:
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::deque<std::shared_ptr<Item>> &GetActionQueues();
|
std::deque<std::shared_ptr<Item>> &GetActionQueues();
|
||||||
void SetupActionQueues();
|
|
||||||
void SaveActionQueues(obs_data_t *);
|
void SaveActionQueues(obs_data_t *);
|
||||||
void LoadActionQueues(obs_data_t *);
|
void LoadActionQueues(obs_data_t *);
|
||||||
void ImportQueues(obs_data_t *);
|
void ImportQueues(obs_data_t *);
|
||||||
|
|||||||
@@ -163,6 +163,8 @@ public:
|
|||||||
bool disableHints = false;
|
bool disableHints = false;
|
||||||
bool disableFilterComboboxFilter = false;
|
bool disableFilterComboboxFilter = false;
|
||||||
bool hideLegacyTabs = true;
|
bool hideLegacyTabs = true;
|
||||||
|
bool settingsLockEnabled = false;
|
||||||
|
std::string settingsLockPassword;
|
||||||
bool saveWindowGeo = false;
|
bool saveWindowGeo = false;
|
||||||
QPoint windowPos = {};
|
QPoint windowPos = {};
|
||||||
QSize windowSize = {};
|
QSize windowSize = {};
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
const std::map<Logic::Type, const char *> Logic::localeMap = {
|
const std::map<Logic::Type, const char *> Logic::localeMap = {
|
||||||
{Logic::Type::NONE, {"AdvSceneSwitcher.logic.none"}},
|
|
||||||
{Logic::Type::AND, {"AdvSceneSwitcher.logic.and"}},
|
{Logic::Type::AND, {"AdvSceneSwitcher.logic.and"}},
|
||||||
{Logic::Type::OR, {"AdvSceneSwitcher.logic.or"}},
|
{Logic::Type::OR, {"AdvSceneSwitcher.logic.or"}},
|
||||||
{Logic::Type::AND_NOT, {"AdvSceneSwitcher.logic.andNot"}},
|
{Logic::Type::AND_NOT, {"AdvSceneSwitcher.logic.andNot"}},
|
||||||
@@ -67,7 +66,7 @@ void Logic::PopulateLogicTypeSelection(QComboBox *list, bool isRootCondition)
|
|||||||
return typeValue < rootOffset;
|
return typeValue < rootOffset;
|
||||||
}}
|
}}
|
||||||
: std::function<bool(int)>{[](int typeValue) {
|
: std::function<bool(int)>{[](int typeValue) {
|
||||||
return typeValue >= rootOffset;
|
return typeValue > rootOffset;
|
||||||
}};
|
}};
|
||||||
for (const auto &[type, name] : localeMap) {
|
for (const auto &[type, name] : localeMap) {
|
||||||
const int typeValue = static_cast<int>(type);
|
const int typeValue = static_cast<int>(type);
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ static constexpr bool handleUncleanShutdown = true;
|
|||||||
static bool wasCleanShutdown = false;
|
static bool wasCleanShutdown = false;
|
||||||
static bool suppressCrashDialog = false;
|
static bool suppressCrashDialog = false;
|
||||||
|
|
||||||
|
static char *sentinelFile = nullptr;
|
||||||
|
|
||||||
bool GetSuppressCrashDialog()
|
bool GetSuppressCrashDialog()
|
||||||
{
|
{
|
||||||
return suppressCrashDialog;
|
return suppressCrashDialog;
|
||||||
@@ -58,7 +60,6 @@ static void handleShutdown(enum obs_frontend_event event, void *)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *sentinelFile = obs_module_config_path(sentinel.data());
|
|
||||||
if (!sentinelFile) {
|
if (!sentinelFile) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -78,7 +79,8 @@ static void handleShutdown(enum obs_frontend_event event, void *)
|
|||||||
|
|
||||||
static void setup()
|
static void setup()
|
||||||
{
|
{
|
||||||
char *sentinelFile = obs_module_config_path(sentinel.data());
|
// Freed in handleShutdown()
|
||||||
|
sentinelFile = obs_module_config_path(sentinel.data());
|
||||||
if (!sentinelFile) {
|
if (!sentinelFile) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -106,7 +108,6 @@ static void setup()
|
|||||||
|
|
||||||
file.write("running");
|
file.write("running");
|
||||||
file.close();
|
file.close();
|
||||||
bfree(sentinelFile);
|
|
||||||
|
|
||||||
obs_frontend_add_event_callback(handleShutdown, nullptr);
|
obs_frontend_add_event_callback(handleShutdown, nullptr);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ static void populateUnits(QComboBox *list)
|
|||||||
list->addItem(obs_module_text("AdvSceneSwitcher.unit.seconds"));
|
list->addItem(obs_module_text("AdvSceneSwitcher.unit.seconds"));
|
||||||
list->addItem(obs_module_text("AdvSceneSwitcher.unit.minutes"));
|
list->addItem(obs_module_text("AdvSceneSwitcher.unit.minutes"));
|
||||||
list->addItem(obs_module_text("AdvSceneSwitcher.unit.hours"));
|
list->addItem(obs_module_text("AdvSceneSwitcher.unit.hours"));
|
||||||
|
list->addItem(obs_module_text("AdvSceneSwitcher.unit.days"));
|
||||||
}
|
}
|
||||||
|
|
||||||
DurationSelection::DurationSelection(QWidget *parent, bool showUnitSelection,
|
DurationSelection::DurationSelection(QWidget *parent, bool showUnitSelection,
|
||||||
@@ -66,6 +67,8 @@ static int durationUnitToMultiplier(Duration::Unit u)
|
|||||||
return 60;
|
return 60;
|
||||||
case Duration::Unit::HOURS:
|
case Duration::Unit::HOURS:
|
||||||
return 3600;
|
return 3600;
|
||||||
|
case Duration::Unit::DAYS:
|
||||||
|
return 86400;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ static std::string durationUnitToString(Duration::Unit u)
|
|||||||
return obs_module_text("AdvSceneSwitcher.unit.minutes");
|
return obs_module_text("AdvSceneSwitcher.unit.minutes");
|
||||||
case Duration::Unit::HOURS:
|
case Duration::Unit::HOURS:
|
||||||
return obs_module_text("AdvSceneSwitcher.unit.hours");
|
return obs_module_text("AdvSceneSwitcher.unit.hours");
|
||||||
|
case Duration::Unit::DAYS:
|
||||||
|
return obs_module_text("AdvSceneSwitcher.unit.days");
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -151,6 +153,8 @@ static int durationUnitToMultiplier(Duration::Unit u)
|
|||||||
return 60;
|
return 60;
|
||||||
case Duration::Unit::HOURS:
|
case Duration::Unit::HOURS:
|
||||||
return 3600;
|
return 3600;
|
||||||
|
case Duration::Unit::DAYS:
|
||||||
|
return 86400;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ public:
|
|||||||
SECONDS,
|
SECONDS,
|
||||||
MINUTES,
|
MINUTES,
|
||||||
HOURS,
|
HOURS,
|
||||||
|
DAYS,
|
||||||
};
|
};
|
||||||
EXPORT Unit GetUnit() const { return _unit; }
|
EXPORT Unit GetUnit() const { return _unit; }
|
||||||
|
|
||||||
|
|||||||
425
lib/utils/first-run-wizard-audio.cpp
Normal file
425
lib/utils/first-run-wizard-audio.cpp
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
#include "first-run-wizard-audio.hpp"
|
||||||
|
#include "first-run-wizard-helpers.hpp"
|
||||||
|
|
||||||
|
#include "layout-helpers.hpp"
|
||||||
|
#include "log-helper.hpp"
|
||||||
|
#include "macro-settings.hpp"
|
||||||
|
#include "selection-helpers.hpp"
|
||||||
|
|
||||||
|
#include <obs-data.h>
|
||||||
|
#include <obs.hpp>
|
||||||
|
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
namespace wiz {
|
||||||
|
|
||||||
|
// Builds the obs_data blob for an audio volume condition,
|
||||||
|
// matching MacroConditionAudio::Save() output.
|
||||||
|
//
|
||||||
|
// Checks output volume (checkType 0) ABOVE (outputCondition 0) the given
|
||||||
|
// threshold in dB, held for at least `duration`.
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "segmentSettings": { "enabled": true, "version": 2 },
|
||||||
|
// "id": "audio",
|
||||||
|
// "logic": 0,
|
||||||
|
// "durationModifier": {
|
||||||
|
// "time_constraint": 1, // AT_LEAST
|
||||||
|
// "seconds": <Duration::Save output>
|
||||||
|
// },
|
||||||
|
// "audioSource": { "type": 0, "name": "<source>" },
|
||||||
|
// "monitor": 0,
|
||||||
|
// "volume": { "value": 0.0, "type": 0 },
|
||||||
|
// "syncOffset": { "value": 0, "type": 0 },
|
||||||
|
// "balance": { "value": 0.5, "type": 0 },
|
||||||
|
// "checkType": 0, // OUTPUT_VOLUME
|
||||||
|
// "outputCondition": 0, // ABOVE
|
||||||
|
// "volumeCondition": 0,
|
||||||
|
// "useDb": true,
|
||||||
|
// "volumeDB": { "value": <thresholdDb>, "type": 0 },
|
||||||
|
// "version": 3
|
||||||
|
// }
|
||||||
|
static OBSDataAutoRelease buildAudioConditionData(const QString &sourceName,
|
||||||
|
double thresholdDb,
|
||||||
|
const Duration &duration)
|
||||||
|
{
|
||||||
|
OBSDataAutoRelease seg = obs_data_create();
|
||||||
|
obs_data_set_bool(seg, "enabled", true);
|
||||||
|
obs_data_set_int(seg, "version", 2);
|
||||||
|
|
||||||
|
OBSDataAutoRelease durMod = obs_data_create();
|
||||||
|
obs_data_set_int(durMod, "time_constraint", 1);
|
||||||
|
duration.Save(durMod, "seconds");
|
||||||
|
|
||||||
|
OBSDataAutoRelease audioSrc = obs_data_create();
|
||||||
|
obs_data_set_int(audioSrc, "type", 0);
|
||||||
|
obs_data_set_string(audioSrc, "name", sourceName.toUtf8().constData());
|
||||||
|
|
||||||
|
OBSDataAutoRelease volume = obs_data_create();
|
||||||
|
obs_data_set_double(volume, "value", 0.0);
|
||||||
|
obs_data_set_int(volume, "type", 0);
|
||||||
|
|
||||||
|
OBSDataAutoRelease syncOffset = obs_data_create();
|
||||||
|
obs_data_set_int(syncOffset, "value", 0);
|
||||||
|
obs_data_set_int(syncOffset, "type", 0);
|
||||||
|
|
||||||
|
OBSDataAutoRelease balance = obs_data_create();
|
||||||
|
obs_data_set_double(balance, "value", 0.5);
|
||||||
|
obs_data_set_int(balance, "type", 0);
|
||||||
|
|
||||||
|
OBSDataAutoRelease volumeDB = obs_data_create();
|
||||||
|
obs_data_set_double(volumeDB, "value", thresholdDb);
|
||||||
|
obs_data_set_int(volumeDB, "type", 0);
|
||||||
|
|
||||||
|
OBSDataAutoRelease data = obs_data_create();
|
||||||
|
obs_data_set_obj(data, "segmentSettings", seg);
|
||||||
|
obs_data_set_string(data, "id", "audio");
|
||||||
|
obs_data_set_int(data, "logic", 0);
|
||||||
|
obs_data_set_obj(data, "durationModifier", durMod);
|
||||||
|
obs_data_set_obj(data, "audioSource", audioSrc);
|
||||||
|
obs_data_set_int(data, "monitor", 0);
|
||||||
|
obs_data_set_obj(data, "volume", volume);
|
||||||
|
obs_data_set_obj(data, "syncOffset", syncOffset);
|
||||||
|
obs_data_set_obj(data, "balance", balance);
|
||||||
|
obs_data_set_int(data, "checkType", 0);
|
||||||
|
obs_data_set_int(data, "outputCondition", 0);
|
||||||
|
obs_data_set_int(data, "volumeCondition", 0);
|
||||||
|
obs_data_set_bool(data, "useDb", true);
|
||||||
|
obs_data_set_obj(data, "volumeDB", volumeDB);
|
||||||
|
obs_data_set_int(data, "version", 3);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds the obs_data blob for a scene-visibility action on the current scene,
|
||||||
|
// matching MacroActionSceneVisibility::Save() output.
|
||||||
|
//
|
||||||
|
// sceneSelection type 3 = current scene.
|
||||||
|
// action 0 = SHOW, action 1 = HIDE.
|
||||||
|
static OBSDataAutoRelease buildVisibilityActionData(const QString &sourceName,
|
||||||
|
int action)
|
||||||
|
{
|
||||||
|
OBSDataAutoRelease seg = obs_data_create();
|
||||||
|
obs_data_set_bool(seg, "enabled", true);
|
||||||
|
obs_data_set_int(seg, "version", 2);
|
||||||
|
|
||||||
|
OBSDataAutoRelease sceneSel = obs_data_create();
|
||||||
|
obs_data_set_int(sceneSel, "type", 3); // current scene
|
||||||
|
obs_data_set_string(sceneSel, "canvasSelection", "Main");
|
||||||
|
|
||||||
|
OBSDataAutoRelease itemSel = obs_data_create();
|
||||||
|
obs_data_set_int(itemSel, "type", 0);
|
||||||
|
obs_data_set_int(itemSel, "idxType", 0);
|
||||||
|
obs_data_set_int(itemSel, "idx", 0);
|
||||||
|
obs_data_set_string(itemSel, "item", sourceName.toUtf8().constData());
|
||||||
|
|
||||||
|
OBSDataAutoRelease data = obs_data_create();
|
||||||
|
obs_data_set_obj(data, "segmentSettings", seg);
|
||||||
|
obs_data_set_string(data, "id", "scene_visibility");
|
||||||
|
obs_data_set_obj(data, "sceneSelection", sceneSel);
|
||||||
|
obs_data_set_obj(data, "sceneItemSelection", itemSel);
|
||||||
|
obs_data_set_bool(data, "updateTransition", false);
|
||||||
|
obs_data_set_int(data, "transitionType", 0);
|
||||||
|
obs_data_set_string(data, "transition", "");
|
||||||
|
obs_data_set_bool(data, "updateDuration", false);
|
||||||
|
Duration().Save(data, "duration");
|
||||||
|
obs_data_set_int(data, "action", action);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// AudioSourcePage
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
AudioSourcePage::AudioSourcePage(QWidget *parent)
|
||||||
|
: QWizardPage(parent),
|
||||||
|
_sourceCombo(new QComboBox(this)),
|
||||||
|
_thresholdSpinbox(new QDoubleSpinBox(this)),
|
||||||
|
_durationSelection(new DurationSelection(this, true, 0.0)),
|
||||||
|
_volmeterLayout(new QVBoxLayout)
|
||||||
|
{
|
||||||
|
setTitle(obs_module_text("FirstRunWizard.audio.source.title"));
|
||||||
|
setSubTitle(obs_module_text("FirstRunWizard.audio.source.subtitle"));
|
||||||
|
|
||||||
|
_sourceCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
_thresholdSpinbox->setRange(-100.0, 0.0);
|
||||||
|
_thresholdSpinbox->setValue(-24.0);
|
||||||
|
_thresholdSpinbox->setDecimals(1);
|
||||||
|
_thresholdSpinbox->setSingleStep(1.0);
|
||||||
|
_thresholdSpinbox->setSuffix(" dB");
|
||||||
|
|
||||||
|
_durationSelection->SetDuration(Duration(1.0));
|
||||||
|
|
||||||
|
registerField("audioSourceName*", _sourceCombo, "currentText",
|
||||||
|
SIGNAL(currentTextChanged(QString)));
|
||||||
|
|
||||||
|
connect(_sourceCombo, &QComboBox::currentTextChanged, this,
|
||||||
|
&QWizardPage::completeChanged);
|
||||||
|
connect(_sourceCombo, &QComboBox::currentTextChanged, this,
|
||||||
|
&AudioSourcePage::UpdateVolmeter);
|
||||||
|
connect(_thresholdSpinbox,
|
||||||
|
QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
|
||||||
|
&AudioSourcePage::SyncSliderFromSpinbox);
|
||||||
|
|
||||||
|
auto *sourceRow = new QHBoxLayout;
|
||||||
|
PlaceWidgets(obs_module_text("FirstRunWizard.audio.source.sourceRow"),
|
||||||
|
sourceRow, {{"{{source}}", _sourceCombo}}, false);
|
||||||
|
|
||||||
|
auto *thresholdRow = new QHBoxLayout;
|
||||||
|
PlaceWidgets(
|
||||||
|
obs_module_text("FirstRunWizard.audio.source.thresholdRow"),
|
||||||
|
thresholdRow,
|
||||||
|
{{"{{threshold}}", _thresholdSpinbox},
|
||||||
|
{"{{duration}}", _durationSelection}},
|
||||||
|
false);
|
||||||
|
|
||||||
|
auto *layout = new QVBoxLayout(this);
|
||||||
|
layout->addLayout(sourceRow);
|
||||||
|
layout->addLayout(thresholdRow);
|
||||||
|
layout->addLayout(_volmeterLayout);
|
||||||
|
layout->addStretch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioSourcePage::initializePage()
|
||||||
|
{
|
||||||
|
_sourceCombo->clear();
|
||||||
|
PopulateAudioSelection(_sourceCombo, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioSourcePage::UpdateVolmeter()
|
||||||
|
{
|
||||||
|
delete _volControl;
|
||||||
|
_volControl = nullptr;
|
||||||
|
|
||||||
|
OBSSourceAutoRelease source = obs_get_source_by_name(
|
||||||
|
_sourceCombo->currentText().toUtf8().constData());
|
||||||
|
if (!source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_volControl = new VolControl(source.Get());
|
||||||
|
_volmeterLayout->addWidget(_volControl);
|
||||||
|
|
||||||
|
connect(_volControl->GetSlider(), &DoubleSlider::DoubleValChanged, this,
|
||||||
|
&AudioSourcePage::SyncSpinboxFromSlider);
|
||||||
|
SyncSliderFromSpinbox();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioSourcePage::SyncSpinboxFromSlider()
|
||||||
|
{
|
||||||
|
if (!_volControl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const QSignalBlocker blocker(_thresholdSpinbox);
|
||||||
|
_thresholdSpinbox->setValue(_volControl->GetSlider()->DoubleValue() -
|
||||||
|
100.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioSourcePage::SyncSliderFromSpinbox()
|
||||||
|
{
|
||||||
|
if (!_volControl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const QSignalBlocker blocker(_volControl->GetSlider());
|
||||||
|
_volControl->GetSlider()->SetDoubleVal(_thresholdSpinbox->value() +
|
||||||
|
100.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioSourcePage::isComplete() const
|
||||||
|
{
|
||||||
|
return _sourceCombo->count() > 0 &&
|
||||||
|
!_sourceCombo->currentText().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Duration AudioSourcePage::GetDuration() const
|
||||||
|
{
|
||||||
|
return _durationSelection->GetDuration();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// AudioTargetPage
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
AudioTargetPage::AudioTargetPage(QWidget *parent)
|
||||||
|
: QWizardPage(parent),
|
||||||
|
_sourceCombo(new QComboBox(this))
|
||||||
|
{
|
||||||
|
setTitle(obs_module_text("FirstRunWizard.audio.target.title"));
|
||||||
|
setSubTitle(obs_module_text("FirstRunWizard.audio.target.subtitle"));
|
||||||
|
|
||||||
|
_sourceCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
registerField("audioTargetSource*", _sourceCombo, "currentText",
|
||||||
|
SIGNAL(currentTextChanged(QString)));
|
||||||
|
|
||||||
|
connect(_sourceCombo, &QComboBox::currentTextChanged, this,
|
||||||
|
&QWizardPage::completeChanged);
|
||||||
|
|
||||||
|
auto *row = new QHBoxLayout;
|
||||||
|
PlaceWidgets(obs_module_text("FirstRunWizard.audio.target.sourceRow"),
|
||||||
|
row, {{"{{source}}", _sourceCombo}}, false);
|
||||||
|
|
||||||
|
auto *layout = new QVBoxLayout(this);
|
||||||
|
layout->addLayout(row);
|
||||||
|
layout->addStretch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioTargetPage::initializePage()
|
||||||
|
{
|
||||||
|
QStringList names;
|
||||||
|
auto enumScenes = [](void *param, obs_source_t *src) -> bool {
|
||||||
|
auto *list = reinterpret_cast<QStringList *>(param);
|
||||||
|
obs_scene_t *scene = obs_scene_from_source(src);
|
||||||
|
obs_scene_enum_items(
|
||||||
|
scene,
|
||||||
|
[](obs_scene_t *, obs_sceneitem_t *item,
|
||||||
|
void *p) -> bool {
|
||||||
|
auto *l = reinterpret_cast<QStringList *>(p);
|
||||||
|
OBSSource s = obs_sceneitem_get_source(item);
|
||||||
|
if (s) {
|
||||||
|
*l << obs_source_get_name(s);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
list);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
obs_enum_scenes(enumScenes, &names);
|
||||||
|
names.sort(Qt::CaseInsensitive);
|
||||||
|
names.removeDuplicates();
|
||||||
|
|
||||||
|
const QString prev = _sourceCombo->currentText();
|
||||||
|
_sourceCombo->clear();
|
||||||
|
_sourceCombo->addItems(names);
|
||||||
|
const int idx = _sourceCombo->findText(prev);
|
||||||
|
if (idx >= 0) {
|
||||||
|
_sourceCombo->setCurrentIndex(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioTargetPage::isComplete() const
|
||||||
|
{
|
||||||
|
return _sourceCombo->count() > 0 &&
|
||||||
|
!_sourceCombo->currentText().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// AudioReviewPage
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
AudioReviewPage::AudioReviewPage(QWidget *parent, std::shared_ptr<Macro> ¯o)
|
||||||
|
: QWizardPage(parent),
|
||||||
|
_summary(new QLabel(this)),
|
||||||
|
_macro(macro)
|
||||||
|
{
|
||||||
|
setTitle(obs_module_text("FirstRunWizard.audio.review.title"));
|
||||||
|
setSubTitle(obs_module_text("FirstRunWizard.audio.review.subtitle"));
|
||||||
|
|
||||||
|
detail::setupSummaryLabel(_summary);
|
||||||
|
|
||||||
|
auto *layout = new QVBoxLayout(this);
|
||||||
|
layout->addWidget(_summary);
|
||||||
|
layout->addStretch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioReviewPage::initializePage()
|
||||||
|
{
|
||||||
|
const QString audioSource = field("audioSourceName").toString();
|
||||||
|
const QString targetSource = field("audioTargetSource").toString();
|
||||||
|
|
||||||
|
auto *sourcePage = qobject_cast<AudioSourcePage *>(
|
||||||
|
wizard()->page(PAGE_AUDIO_SOURCE));
|
||||||
|
const double thresholdDb = sourcePage ? sourcePage->GetThresholdDb()
|
||||||
|
: -24.0;
|
||||||
|
const Duration duration = sourcePage ? sourcePage->GetDuration()
|
||||||
|
: Duration(1.0);
|
||||||
|
|
||||||
|
_summary->setText(
|
||||||
|
QString(obs_module_text("FirstRunWizard.audio.review.summary"))
|
||||||
|
.arg(audioSource.toHtmlEscaped())
|
||||||
|
.arg(thresholdDb, 0, 'f', 1)
|
||||||
|
.arg(QString::fromStdString(duration.ToString()))
|
||||||
|
.arg(targetSource.toHtmlEscaped()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioReviewPage::validatePage()
|
||||||
|
{
|
||||||
|
const QString audioSource = field("audioSourceName").toString();
|
||||||
|
const QString targetSource = field("audioTargetSource").toString();
|
||||||
|
const std::string name =
|
||||||
|
("Audio: " + audioSource + " -> " + targetSource).toStdString();
|
||||||
|
|
||||||
|
auto *sourcePage = qobject_cast<AudioSourcePage *>(
|
||||||
|
wizard()->page(PAGE_AUDIO_SOURCE));
|
||||||
|
const double thresholdDb = sourcePage ? sourcePage->GetThresholdDb()
|
||||||
|
: -24.0;
|
||||||
|
const Duration duration = sourcePage ? sourcePage->GetDuration()
|
||||||
|
: Duration(1.0);
|
||||||
|
|
||||||
|
_macro = std::make_shared<Macro>(name, GetGlobalMacroSettings());
|
||||||
|
if (!_macro) {
|
||||||
|
blog(LOG_WARNING,
|
||||||
|
"FirstRunWizard: audio macro allocation failed");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_macro->SetActionConditionSplitterPosition(
|
||||||
|
{QWIDGETSIZE_MAX / 2, QWIDGETSIZE_MAX / 2});
|
||||||
|
_macro->SetElseActionSplitterPosition(
|
||||||
|
{QWIDGETSIZE_MAX / 2, QWIDGETSIZE_MAX / 2});
|
||||||
|
|
||||||
|
OBSDataAutoRelease condData =
|
||||||
|
buildAudioConditionData(audioSource, thresholdDb, duration);
|
||||||
|
if (!detail::addCondition(_macro.get(), "audio", condData)) {
|
||||||
|
_macro.reset();
|
||||||
|
QMessageBox::warning(
|
||||||
|
this,
|
||||||
|
obs_module_text(
|
||||||
|
"FirstRunWizard.audio.review.errorTitle"),
|
||||||
|
obs_module_text(
|
||||||
|
"FirstRunWizard.audio.review.errorBody"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease showData =
|
||||||
|
buildVisibilityActionData(targetSource, 0);
|
||||||
|
if (!detail::addAction(_macro.get(), "scene_visibility", showData)) {
|
||||||
|
_macro.reset();
|
||||||
|
QMessageBox::warning(
|
||||||
|
this,
|
||||||
|
obs_module_text(
|
||||||
|
"FirstRunWizard.audio.review.errorTitle"),
|
||||||
|
obs_module_text(
|
||||||
|
"FirstRunWizard.audio.review.errorBody"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease hideData =
|
||||||
|
buildVisibilityActionData(targetSource, 1);
|
||||||
|
if (!detail::addElseAction(_macro.get(), "scene_visibility",
|
||||||
|
hideData)) {
|
||||||
|
_macro.reset();
|
||||||
|
QMessageBox::warning(
|
||||||
|
this,
|
||||||
|
obs_module_text(
|
||||||
|
"FirstRunWizard.audio.review.errorTitle"),
|
||||||
|
obs_module_text(
|
||||||
|
"FirstRunWizard.audio.review.errorBody"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
blog(LOG_INFO, "FirstRunWizard: created audio macro '%s'",
|
||||||
|
name.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace wiz
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user