mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 04:36:21 -05:00
Compare commits
110 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8174004eda | ||
|
|
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 | ||
|
|
713f695aa4 | ||
|
|
dec5c2d763 | ||
|
|
f090dea136 | ||
|
|
d00986df03 | ||
|
|
03e5397b7e | ||
|
|
94d49db196 | ||
|
|
1483f9d9dc | ||
|
|
99629e8c66 | ||
|
|
ba38b8bf27 | ||
|
|
38513735dd | ||
|
|
acfc7d605b | ||
|
|
df42538319 | ||
|
|
b02259e926 | ||
|
|
2b6bd9fb51 | ||
|
|
aadedbb610 | ||
|
|
d15a324137 | ||
|
|
119fac17bf | ||
|
|
088881b674 | ||
|
|
f008d5367b | ||
|
|
1ace185a57 | ||
|
|
a01d26e25d | ||
|
|
7e2ef9aacc | ||
|
|
4a98d064f7 | ||
|
|
b0d6cea52a | ||
|
|
d6bf9ed101 | ||
|
|
7e940e515b | ||
|
|
29e1ff0754 | ||
|
|
6309100cc4 | ||
|
|
ec3052c823 | ||
|
|
a28b800228 | ||
|
|
713a64ab45 | ||
|
|
4d5ffe38fc | ||
|
|
85460157b1 | ||
|
|
35e5b7620e | ||
|
|
e4c06b14a7 | ||
|
|
7fe3258269 | ||
|
|
ec19b4d4fb | ||
|
|
9301ead060 | ||
|
|
bc29ece526 | ||
|
|
4158c7a363 | ||
|
|
944d1059da | ||
|
|
bf18d8e106 | ||
|
|
bf216e0917 | ||
|
|
2405b6dbbf | ||
|
|
e64f2d195e | ||
|
|
f66bec8caf | ||
|
|
3eb79e3adb | ||
|
|
07e2ac3ca0 |
@@ -19,14 +19,9 @@ inputs:
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1.13
|
||||
with:
|
||||
cmake-version: '3.x.x'
|
||||
|
||||
- name: Restore cached dependencies
|
||||
id: restore-cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ env.DEP_DIR }}
|
||||
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)"
|
||||
echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH
|
||||
echo "/home/linuxbrew/.linuxbrew/opt/clang-format@16/bin" >> $GITHUB_PATH
|
||||
brew update
|
||||
brew install --quiet zsh
|
||||
echo ::endgroup::
|
||||
|
||||
@@ -52,6 +53,7 @@ runs:
|
||||
|
||||
if (( ${changes[(I)(*.c|*.h|*.cpp|*.hpp|*.m|*.mm)]} )) {
|
||||
echo ::group::Install clang-format-16
|
||||
brew update
|
||||
brew install --quiet obsproject/tools/clang-format@16
|
||||
echo ::endgroup::
|
||||
|
||||
|
||||
2
.github/scripts/.Wingetfile
vendored
2
.github/scripts/.Wingetfile
vendored
@@ -1,4 +1,4 @@
|
||||
package 'cmake', path: 'Cmake\bin', bin: 'cmake'
|
||||
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'
|
||||
4
.github/scripts/.build.zsh
vendored
4
.github/scripts/.build.zsh
vendored
@@ -255,6 +255,10 @@ ${_usage_host:-}"
|
||||
|
||||
cmake_args+=(
|
||||
-DCMAKE_PREFIX_PATH="${advss_deps_path}"
|
||||
-DOPENSSL_ROOT_DIR="${advss_deps_path}"
|
||||
-DOPENSSL_INCLUDE_DIR="${advss_deps_path}/include"
|
||||
-DOPENSSL_CRYPTO_LIBRARY="${advss_deps_path}/lib/libcrypto.a"
|
||||
-DOPENSSL_SSL_LIBRARY="${advss_deps_path}/lib/libssl.a"
|
||||
--preset ${_preset}
|
||||
)
|
||||
|
||||
|
||||
35
.github/scripts/Package-Windows.ps1
vendored
35
.github/scripts/Package-Windows.ps1
vendored
@@ -68,14 +68,44 @@ function Package {
|
||||
|
||||
Remove-Item @RemoveArgs
|
||||
|
||||
# Build a staging directory with two subfolders:
|
||||
# recommended/ - new layout, extract to %ProgramData%\obs-studio\
|
||||
# legacy/ - old layout, extract to the OBS install directory
|
||||
$ReleasePath = "${ProjectRoot}/release/${Configuration}"
|
||||
$StagingPath = "${ProjectRoot}/release/zip-staging"
|
||||
$NewBinPath = "${ReleasePath}/${ProductName}/bin/64bit"
|
||||
$NewDataPath = "${ReleasePath}/${ProductName}/data"
|
||||
|
||||
Remove-Item -Path $StagingPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Path $StagingPath | Out-Null
|
||||
Copy-Item -Path "${ProjectRoot}/build-aux/CI/windows/README.txt" -Destination "${StagingPath}/README.txt"
|
||||
|
||||
if ( Test-Path -Path $NewBinPath ) {
|
||||
$RecBinPath = "${StagingPath}/recommended/${ProductName}/bin/64bit"
|
||||
$LegacyBinPath = "${StagingPath}/legacy/obs-plugins/64bit"
|
||||
New-Item -ItemType Directory -Force -Path $RecBinPath | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $LegacyBinPath | Out-Null
|
||||
Copy-Item -Path "${NewBinPath}/*" -Destination $RecBinPath -Recurse -Force
|
||||
Copy-Item -Path "${NewBinPath}/*" -Destination $LegacyBinPath -Recurse -Force
|
||||
}
|
||||
if ( Test-Path -Path $NewDataPath ) {
|
||||
$RecDataPath = "${StagingPath}/recommended/${ProductName}/data"
|
||||
$LegacyDataPath = "${StagingPath}/legacy/data/obs-plugins/${ProductName}"
|
||||
New-Item -ItemType Directory -Force -Path $RecDataPath | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $LegacyDataPath | Out-Null
|
||||
Copy-Item -Path "${NewDataPath}/*" -Destination $RecDataPath -Recurse -Force
|
||||
Copy-Item -Path "${NewDataPath}/*" -Destination $LegacyDataPath -Recurse -Force
|
||||
}
|
||||
|
||||
Log-Group "Archiving ${ProductName}..."
|
||||
$CompressArgs = @{
|
||||
Path = (Get-ChildItem -Path "${ProjectRoot}/release/${Configuration}" -Exclude "${OutputName}*.*")
|
||||
Path = (Get-ChildItem -Path $StagingPath)
|
||||
CompressionLevel = 'Optimal'
|
||||
DestinationPath = "${ProjectRoot}/release/${OutputName}.zip"
|
||||
Verbose = ($Env:CI -ne $null)
|
||||
Verbose = ($null -ne $Env:CI)
|
||||
}
|
||||
Compress-Archive -Force @CompressArgs
|
||||
Remove-Item -Path $StagingPath -Recurse -Force
|
||||
Log-Group
|
||||
|
||||
if ( ( $BuildInstaller ) ) {
|
||||
@@ -89,6 +119,7 @@ function Package {
|
||||
Log-Information 'Creating InnoSetup installer...'
|
||||
Push-Location -Stack BuildTemp
|
||||
Ensure-Location -Path "${ProjectRoot}/release"
|
||||
Remove-Item -Path Package -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Copy-Item -Path ${Configuration} -Destination Package -Recurse
|
||||
Invoke-External iscc ${IsccFile} /O"${ProjectRoot}/release" /F"${OutputName}-Installer"
|
||||
Remove-Item -Path Package -Recurse
|
||||
|
||||
4
.github/scripts/utils.zsh/check_macos
vendored
4
.github/scripts/utils.zsh/check_macos
vendored
@@ -18,7 +18,5 @@ if (( ! ${+commands[brew]} )) {
|
||||
}
|
||||
|
||||
brew bundle --file ${SCRIPT_HOME}/.Brewfile
|
||||
# Workaround to make sure locally built openssl is picked up by cmake
|
||||
brew uninstall --ignore-dependencies openssl@3 || true
|
||||
rehash || true
|
||||
rehash
|
||||
log_group
|
||||
|
||||
4
.github/workflows/build-debian.yml
vendored
4
.github/workflows/build-debian.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: "recursive"
|
||||
- name: check_libobs_revision
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
mv ../*.deb .
|
||||
- name: Publish
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: "obs-scene-switcher.deb"
|
||||
path: ${{ github.workspace }}/*.deb
|
||||
|
||||
34
.github/workflows/build-project.yaml
vendored
34
.github/workflows/build-project.yaml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
description: "Project name detected by parsing build spec file"
|
||||
value: ${{ jobs.check-event.outputs.pluginName }}
|
||||
env:
|
||||
DEP_DIR: .deps/advss-build-dependencies-3
|
||||
DEP_DIR: .deps/advss-build-dependencies-5
|
||||
jobs:
|
||||
check-event:
|
||||
name: Check GitHub Event Data 🔎
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
commitHash: ${{ steps.setup.outputs.commitHash }}
|
||||
pluginName: ${{ steps.setup.outputs.pluginName }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Check Event Data ☑️
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
run:
|
||||
shell: zsh --no-rcs --errexit --pipefail {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
sudo xcode-select --switch /Applications/Xcode_16.1.0.app/Contents/Developer
|
||||
print '::endgroup::'
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
id: ccache-cache
|
||||
with:
|
||||
path: ${{ github.workspace }}/.ccache
|
||||
@@ -163,13 +163,13 @@ jobs:
|
||||
codesignPass: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
|
||||
|
||||
- name: Upload Artifacts 📡
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
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.*
|
||||
|
||||
- name: Upload Debug Symbol Artifacts 🪲
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
if: ${{ needs.check-event.outputs.config == 'Release' }}
|
||||
with:
|
||||
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-macos-universal-${{ needs.check-event.outputs.commitHash }}-dSYMs
|
||||
@@ -183,7 +183,7 @@ jobs:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
@@ -202,7 +202,7 @@ jobs:
|
||||
echo "pluginName=${product_name}" >> $GITHUB_OUTPUT
|
||||
echo "pluginVersion=${git_tag}" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
id: ccache-cache
|
||||
with:
|
||||
path: ${{ github.workspace }}/.ccache
|
||||
@@ -210,13 +210,8 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ 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 🍺
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
|
||||
- name: Build Plugin 🧱
|
||||
uses: ./.github/actions/build-plugin
|
||||
@@ -238,23 +233,24 @@ jobs:
|
||||
config: ${{ needs.check-event.outputs.config }}
|
||||
|
||||
- name: Upload Source Tarball 🗜️
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-sources-${{ needs.check-event.outputs.commitHash }}
|
||||
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-source.*
|
||||
|
||||
- name: Upload Artifacts 📡
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
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*.*
|
||||
|
||||
- name: Upload debug symbol artifacts 🪲
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
if: ${{ fromJSON(needs.check-event.outputs.package) }}
|
||||
with:
|
||||
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
|
||||
if-no-files-found: ignore
|
||||
|
||||
windows-build:
|
||||
name: Build for Windows 🪟
|
||||
@@ -264,7 +260,7 @@ jobs:
|
||||
run:
|
||||
shell: pwsh
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
@@ -311,7 +307,7 @@ jobs:
|
||||
package: ${{ fromJSON(needs.check-event.outputs.package) }}
|
||||
|
||||
- name: Upload Artifacts 📡
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
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*.*
|
||||
|
||||
4
.github/workflows/check-format.yaml
vendored
4
.github/workflows/check-format.yaml
vendored
@@ -5,7 +5,7 @@ jobs:
|
||||
clang-format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: clang-format check 🐉
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
cmake-format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- 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
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Check locale files
|
||||
run: |
|
||||
|
||||
4
.github/workflows/push.yaml
vendored
4
.github/workflows/push.yaml
vendored
@@ -59,7 +59,7 @@ jobs:
|
||||
esac
|
||||
|
||||
- name: Download Build Artifacts 📥
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
if: fromJSON(steps.check.outputs.validTag)
|
||||
id: download
|
||||
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
- name: Create Release 🛫
|
||||
if: fromJSON(steps.check.outputs.validTag)
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@d4e8205d7e959a9107da6396278b2f1f07af0f9b
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ fromJSON(steps.check.outputs.prerelease) }}
|
||||
|
||||
@@ -134,6 +134,9 @@ target_sources(
|
||||
lib/macro/macro-dock-window.hpp
|
||||
lib/macro/macro-edit.cpp
|
||||
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.hpp
|
||||
lib/macro/macro-helpers.cpp
|
||||
@@ -504,7 +507,11 @@ endif()
|
||||
# --- End of section ---
|
||||
|
||||
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 ---
|
||||
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
"description": "Build for macOS 11.0+ (Universal binary) for CI",
|
||||
"generator": "Xcode",
|
||||
"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",
|
||||
"description": "Build for Windows x64 on CI",
|
||||
"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",
|
||||
"cacheVariables": {
|
||||
"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",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true
|
||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true,
|
||||
"ADVSS_ENABLE_TESTS": true
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -14,11 +14,6 @@ On Linux the plugin is available via the **Flatpak** package manager for users w
|
||||
flatpak install com.obsproject.Studio.Plugin.SceneSwitcher
|
||||
```
|
||||
|
||||
The **Snap** package manager offers an OBS Studio installation which is bundled with the plugin:
|
||||
```
|
||||
sudo snap install obs-studio
|
||||
```
|
||||
|
||||
More information can be found [here](https://github.com/WarmUpTill/SceneSwitcher/wiki/Installation).
|
||||
|
||||
## Contributing
|
||||
|
||||
37
build-aux/CI/windows/README.txt
Normal file
37
build-aux/CI/windows/README.txt
Normal file
@@ -0,0 +1,37 @@
|
||||
Advanced Scene Switcher - Windows Installation
|
||||
==============================================
|
||||
|
||||
For full installation instructions visit:
|
||||
https://github.com/WarmUpTill/SceneSwitcher/wiki/Installation
|
||||
|
||||
|
||||
Quick guide
|
||||
-----------
|
||||
|
||||
This archive contains two installation methods.
|
||||
Choose ONE depending on your OBS version.
|
||||
|
||||
|
||||
recommended\ (preferred)
|
||||
---------------------------------------------
|
||||
Extract the CONTENTS of this folder into:
|
||||
|
||||
C:\ProgramData\obs-studio\
|
||||
|
||||
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
|
||||
|
||||
|
||||
legacy\ (older OBS versions, portable OBS installs, or if the recommended method does not work)
|
||||
---------------------------------------------------------------------------
|
||||
Extract the CONTENTS of this folder 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 legacy\ into that directory.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
if(BUILD_OUT_OF_TREE)
|
||||
if(OS_WINDOWS)
|
||||
set(OBS_PLUGIN_DESTINATION "obs-plugins/64bit")
|
||||
set(OBS_PLUGIN_DESTINATION "${CMAKE_PROJECT_NAME}/bin/64bit")
|
||||
else()
|
||||
set(OBS_PLUGIN_DESTINATION "${CMAKE_INSTALL_LIBDIR}/obs-plugins")
|
||||
endif()
|
||||
|
||||
@@ -18,7 +18,9 @@ set(CPACK_PACKAGE_FILE_NAME
|
||||
"${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_MAINTAINER "${PLUGIN_EMAIL}")
|
||||
set(CPACK_SET_DESTDIR ON)
|
||||
@@ -46,6 +48,19 @@ set(CPACK_SOURCE_PACKAGE_FILE_NAME
|
||||
set(CPACK_ARCHIVE_THREADS 0)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@@ -6,3 +6,9 @@ include_guard(GLOBAL)
|
||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL TRUE)
|
||||
|
||||
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(
|
||||
TARGETS ${target}
|
||||
RUNTIME DESTINATION bin/64bit
|
||||
LIBRARY DESTINATION obs-plugins/64bit)
|
||||
RUNTIME DESTINATION "${target}/bin/64bit"
|
||||
LIBRARY DESTINATION "${target}/bin/64bit")
|
||||
|
||||
install(
|
||||
FILES "$<TARGET_PDB_FILE:${target}>"
|
||||
CONFIGURATIONS RelWithDebInfo Debug Release
|
||||
DESTINATION obs-plugins/64bit
|
||||
DESTINATION "${target}/bin/64bit"
|
||||
OPTIONAL)
|
||||
|
||||
if(OBS_BUILD_DIR)
|
||||
@@ -44,11 +44,11 @@ function(set_target_properties_plugin target)
|
||||
TARGET ${target}
|
||||
POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E make_directory
|
||||
"${OBS_BUILD_DIR}/obs-plugins/64bit"
|
||||
"${OBS_BUILD_DIR}/plugins/${target}/bin/64bit"
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}" -E copy_if_different "$<TARGET_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}"
|
||||
VERBATIM)
|
||||
endif()
|
||||
@@ -105,7 +105,7 @@ function(target_install_resources target)
|
||||
|
||||
install(
|
||||
DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/"
|
||||
DESTINATION data/obs-plugins/${target}
|
||||
DESTINATION "${target}/data"
|
||||
USE_SOURCE_PERMISSIONS)
|
||||
|
||||
if(OBS_BUILD_DIR)
|
||||
@@ -113,11 +113,11 @@ function(target_install_resources target)
|
||||
TARGET ${target}
|
||||
POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E make_directory
|
||||
"${OBS_BUILD_DIR}/data/obs-plugins/${target}"
|
||||
"${OBS_BUILD_DIR}/plugins/${target}/data"
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}" -E copy_directory
|
||||
"${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"
|
||||
VERBATIM)
|
||||
endif()
|
||||
@@ -133,7 +133,7 @@ function(target_add_resource target resource)
|
||||
|
||||
install(
|
||||
FILES "${resource}"
|
||||
DESTINATION data/obs-plugins/${target}
|
||||
DESTINATION "${target}/data"
|
||||
COMPONENT Runtime)
|
||||
|
||||
if(OBS_BUILD_DIR)
|
||||
@@ -141,9 +141,9 @@ function(target_add_resource target resource)
|
||||
TARGET ${target}
|
||||
POST_BUILD
|
||||
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}"
|
||||
"${OBS_BUILD_DIR}/data/obs-plugins/${target}"
|
||||
"${OBS_BUILD_DIR}/plugins/${target}/data"
|
||||
COMMENT "Copy ${target} resource ${resource} to library directory"
|
||||
VERBATIM)
|
||||
endif()
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
#define MyAppVersion "@CMAKE_PROJECT_VERSION@"
|
||||
#define MyAppPublisher "@PLUGIN_AUTHOR@"
|
||||
#define MyAppURL "@PLUGIN_WEBSITE@"
|
||||
#define MyAppId "@UUID_APP@"
|
||||
|
||||
[Setup]
|
||||
; NOTE: The value of AppId uniquely identifies this application.
|
||||
; Do not use the same AppId value in installers for other applications.
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
AppId={{@UUID_APP@}
|
||||
AppId={{{#MyAppId}}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
@@ -20,8 +21,9 @@ OutputBaseFilename={#MyAppName}-{#MyAppVersion}-Windows-Installer
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
DirExistsWarning=no
|
||||
UsePreviousAppDir=no
|
||||
SetupIconFile=installer.ico
|
||||
UninstallDisplayIcon={app}\data\obs-plugins\advanced-scene-switcher\res\images\logo.ico
|
||||
UninstallDisplayIcon={app}\advanced-scene-switcher\data\res\images\logo.ico
|
||||
DisableDirPage=no
|
||||
|
||||
[Languages]
|
||||
@@ -51,17 +53,34 @@ begin
|
||||
);
|
||||
end;
|
||||
|
||||
// credit where it's due :
|
||||
// following function come from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45
|
||||
function GetDirName(Value: string): string;
|
||||
var
|
||||
InstallPath: string;
|
||||
begin
|
||||
// initialize default path, which will be returned when the following registry
|
||||
// key queries fail due to missing keys or for some different reason
|
||||
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
|
||||
Result := ExpandConstant('{commonappdata}\obs-studio\plugins');
|
||||
end;
|
||||
|
||||
// Remove files placed by the legacy installer (obs-plugins/64bit and
|
||||
// data/obs-plugins layout) so the plugin is not loaded twice by OBS.
|
||||
// We read InstallLocation from our own previous uninstall entry, which is
|
||||
// the exact directory the old installer used regardless of OBS install path.
|
||||
procedure RemoveLegacyFiles();
|
||||
var
|
||||
OldInstallPath: string;
|
||||
UninstallKey: string;
|
||||
begin
|
||||
UninstallKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + '{' + '{#MyAppId}' + '}_is1';
|
||||
if not RegQueryStringValue(HKLM, UninstallKey, 'InstallLocation', OldInstallPath) 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;
|
||||
|
||||
|
||||
@@ -99,7 +99,6 @@ AdvSceneSwitcher.macroList.deleted="gelöscht"
|
||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" ist bereits ausgewählt!"
|
||||
|
||||
; Macro Logic
|
||||
AdvSceneSwitcher.logic.none="Eintrag ignorieren"
|
||||
AdvSceneSwitcher.logic.and="Und"
|
||||
AdvSceneSwitcher.logic.or="Oder"
|
||||
AdvSceneSwitcher.logic.andNot="Und nicht"
|
||||
@@ -145,11 +144,7 @@ AdvSceneSwitcher.condition.file="Datei"
|
||||
AdvSceneSwitcher.condition.file.type.match="entspricht"
|
||||
AdvSceneSwitcher.condition.file.type.contentChange="Inhalt geändert"
|
||||
AdvSceneSwitcher.condition.file.type.dateChange="Änderungsdatum geändert"
|
||||
AdvSceneSwitcher.condition.file.remote="Entfernte Datei"
|
||||
AdvSceneSwitcher.condition.file.local="Lokale Datei"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.file.layout="{{filePath}}{{conditions}}{{regex}}"
|
||||
AdvSceneSwitcher.condition.media="Medien"
|
||||
AdvSceneSwitcher.condition.media.source="Quelle"
|
||||
AdvSceneSwitcher.condition.media.anyOnScene="Beliebige Medienquelle in"
|
||||
@@ -215,8 +210,8 @@ AdvSceneSwitcher.condition.record.state.start="Aufnahme läuft"
|
||||
AdvSceneSwitcher.condition.record.state.pause="Aufnahme pausiert"
|
||||
AdvSceneSwitcher.condition.record.state.stop="Aufnahme gestoppt"
|
||||
AdvSceneSwitcher.condition.process="Prozess"
|
||||
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}läuft{{focused}}und ist fokusiert"
|
||||
AdvSceneSwitcher.condition.process.entry.focus="Aktueller Vordergrundprozess: {{focusProcess}}"
|
||||
AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}läuft{{focused}}und ist fokusiert"
|
||||
AdvSceneSwitcher.condition.process.layout.focus="Aktueller Vordergrundprozess: {{focusProcess}}"
|
||||
AdvSceneSwitcher.condition.idle="Leerlauf"
|
||||
AdvSceneSwitcher.condition.idle.entry="Keine Tastatur- oder Mauseingaben für {{duration}}"
|
||||
AdvSceneSwitcher.condition.pluginState="Plugin-Status"
|
||||
@@ -412,7 +407,6 @@ AdvSceneSwitcher.action.recording.type.unpause="Aufnahme nicht mehr pausieren"
|
||||
AdvSceneSwitcher.action.recording.type.split="Aufnahme-Datei teilen"
|
||||
AdvSceneSwitcher.action.recording.pause.hint="Bitte beachten, dass je nach Aufnahmeeinstellung die Aufnahme möglicherweise nicht unterbrochen werden kann"
|
||||
AdvSceneSwitcher.action.recording.split.hint="Vergewissern, dass die automatische Dateiaufteilung in den OBS-Einstellungen aktiviert ist!"
|
||||
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
|
||||
AdvSceneSwitcher.action.replay="Replay Buffer"
|
||||
AdvSceneSwitcher.action.replay.saveWarn="Warnung: Ein zu häufiges Speichern kann dazu führen, dass der Replay Buffer nicht gespeichert wird!"
|
||||
AdvSceneSwitcher.action.replay.type.stop="Replay Buffer stoppen"
|
||||
@@ -421,7 +415,6 @@ AdvSceneSwitcher.action.replay.type.save="Replay Buffer speichern"
|
||||
AdvSceneSwitcher.action.streaming="Stream"
|
||||
AdvSceneSwitcher.action.streaming.type.stop="Stream stoppen"
|
||||
AdvSceneSwitcher.action.streaming.type.start="Stream starten"
|
||||
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
||||
AdvSceneSwitcher.action.run="Ausführen"
|
||||
AdvSceneSwitcher.action.sceneVisibility="Sichtbarkeit von Szenenelementen"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.show="Anzeigen"
|
||||
@@ -457,7 +450,7 @@ AdvSceneSwitcher.action.macro="Makro"
|
||||
AdvSceneSwitcher.action.macro.type.pause="Pausieren"
|
||||
AdvSceneSwitcher.action.macro.type.unpause="Nicht mehr pausieren"
|
||||
AdvSceneSwitcher.action.macro.type.resetCounter="Zähler zurücksetzen"
|
||||
AdvSceneSwitcher.action.macro.type.run="Aktionen ausführen"
|
||||
AdvSceneSwitcher.action.macro.type.runActions="Aktionen ausführen"
|
||||
AdvSceneSwitcher.action.macro.type.stop="Aktionen stoppen"
|
||||
AdvSceneSwitcher.action.pluginState="Plugin-Status"
|
||||
AdvSceneSwitcher.action.pluginState.type.stop="Erweiterten Automatischen Szenenwechsler stoppen"
|
||||
@@ -505,9 +498,9 @@ AdvSceneSwitcher.action.transition.type.scene="Szenenübergang"
|
||||
AdvSceneSwitcher.action.transition.type.sceneOverride="Szenenübergang überschreiben"
|
||||
AdvSceneSwitcher.action.transition.type.sourceShow="Übergang der Quelle anzeigen"
|
||||
AdvSceneSwitcher.action.transition.type.sourceHide="Übergang der Quelle verstecken"
|
||||
AdvSceneSwitcher.action.transition.entry.line1="Anpassen {{type}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Übergangstyp festlegen auf {{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Übergangsdauer festlegen auf {{duration}}Sekunden"
|
||||
AdvSceneSwitcher.action.transition.layout.type="Anpassen {{type}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Übergangstyp festlegen auf {{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Übergangsdauer festlegen auf {{duration}}Sekunden"
|
||||
AdvSceneSwitcher.action.timer="Timer"
|
||||
AdvSceneSwitcher.action.timer.type.pause="Pausieren"
|
||||
AdvSceneSwitcher.action.timer.type.continue="Fortsetzen"
|
||||
|
||||
@@ -40,12 +40,20 @@ AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Show system t
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Disable UI hints"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.comboBoxFilterDisable="Disable filtering by typing in drop down menus"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailure="Display warning if plugins cannot be loaded"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.suppressCrashRecoveryDialog="Do not ask to keep the plugin stopped after an unclean shutdown"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage="<html><body>Loading of the following plugin libraries was unsuccessful, which could result in some Advanced Scene Switcher functions not being available:%1Check the OBS logs for details.<br>This message can be disabled on the General tab.</body></html>"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.warnCorruptedInstallMessage="The plugin installation seems to be corrupted and might crash!\nPlease make sure the plugin was installed correctly!"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="Hide tabs which can be represented via macros"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowMacroSearch="Always show macro search"
|
||||
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.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.priority="Priority"
|
||||
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
|
||||
@@ -201,6 +209,13 @@ AdvSceneSwitcher.macroTab.remove="Remove"
|
||||
AdvSceneSwitcher.macroTab.export="Export"
|
||||
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.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.info="Paste the export string into the below text box to import macros:"
|
||||
AdvSceneSwitcher.macroTab.import.invalid="Invalid import data provided!"
|
||||
@@ -305,7 +320,6 @@ AdvSceneSwitcher.macroList.deleted="deleted"
|
||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" is alreay selected!"
|
||||
|
||||
# Macro Logic
|
||||
AdvSceneSwitcher.logic.none="Ignore entry"
|
||||
AdvSceneSwitcher.logic.and="And"
|
||||
AdvSceneSwitcher.logic.or="Or"
|
||||
AdvSceneSwitcher.logic.andNot="And not"
|
||||
@@ -368,11 +382,7 @@ AdvSceneSwitcher.condition.file.type.dateChange="modification date changed"
|
||||
AdvSceneSwitcher.condition.file.type.exists="exists"
|
||||
AdvSceneSwitcher.condition.file.type.isFile="is a file"
|
||||
AdvSceneSwitcher.condition.file.type.isFolder="is a folder"
|
||||
AdvSceneSwitcher.condition.file.remote="Remote file"
|
||||
AdvSceneSwitcher.condition.file.local="Local file"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.file.layout="{{filePath}}{{conditions}}{{regex}}"
|
||||
AdvSceneSwitcher.condition.media="Media"
|
||||
AdvSceneSwitcher.condition.media.checkType.state="State matches"
|
||||
AdvSceneSwitcher.condition.media.checkType.time="Time restriction matches"
|
||||
@@ -487,8 +497,9 @@ AdvSceneSwitcher.condition.record.state.stop="Recording stopped"
|
||||
AdvSceneSwitcher.condition.record.state.duration="Recording duration is longer than"
|
||||
AdvSceneSwitcher.condition.record.entry="{{condition}}{{duration}}"
|
||||
AdvSceneSwitcher.condition.process="Process"
|
||||
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}is running{{focused}}and is focused"
|
||||
AdvSceneSwitcher.condition.process.entry.focus="Current foreground process:{{focusProcess}}"
|
||||
AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}is running{{focused}}and is focused"
|
||||
AdvSceneSwitcher.condition.process.layout.focus="Current foreground process:{{focusProcess}}"
|
||||
AdvSceneSwitcher.condition.process.layout.path="{{checkPath}}Match binary path:{{path}}{{pathRegex}}"
|
||||
AdvSceneSwitcher.condition.idle="Idle"
|
||||
AdvSceneSwitcher.condition.idle.entry="No keyboard or mouse inputs for{{duration}}"
|
||||
AdvSceneSwitcher.condition.pluginState="Plugin state"
|
||||
@@ -676,6 +687,34 @@ AdvSceneSwitcher.condition.websocket.type.event="Scene Switcher Event"
|
||||
AdvSceneSwitcher.condition.websocket.useRegex="Use regular expressions"
|
||||
AdvSceneSwitcher.condition.websocket.entry.request="{{type}}was received:"
|
||||
AdvSceneSwitcher.condition.websocket.entry.event="{{type}}was received from{{connection}}:"
|
||||
AdvSceneSwitcher.condition.http="HTTP"
|
||||
AdvSceneSwitcher.condition.http.layout="Receive{{method}}request on{{server}}"
|
||||
AdvSceneSwitcher.condition.http.layout.path="Path:{{path}}{{regex}}"
|
||||
AdvSceneSwitcher.condition.http.layout.body="Body:{{body}}{{regex}}"
|
||||
AdvSceneSwitcher.condition.http.method.any="any"
|
||||
AdvSceneSwitcher.condition.http.method.get="GET"
|
||||
AdvSceneSwitcher.condition.http.method.post="POST"
|
||||
AdvSceneSwitcher.condition.http.method.put="PUT"
|
||||
AdvSceneSwitcher.condition.http.method.patch="PATCH"
|
||||
AdvSceneSwitcher.condition.http.method.delete="DELETE"
|
||||
AdvSceneSwitcher.httpServer.select="Select HTTP server"
|
||||
AdvSceneSwitcher.httpServer.add="Add HTTP server"
|
||||
AdvSceneSwitcher.httpServer.configure="Configure HTTP server"
|
||||
AdvSceneSwitcher.httpServer.invalid="Invalid HTTP server"
|
||||
AdvSceneSwitcher.httpServer.name="Name:"
|
||||
AdvSceneSwitcher.httpServer.port="Port:"
|
||||
AdvSceneSwitcher.httpServer.startOnLoad="Start on load"
|
||||
AdvSceneSwitcher.httpServer.status.listening="Listening"
|
||||
AdvSceneSwitcher.httpServer.status.stopped="Stopped"
|
||||
AdvSceneSwitcher.httpServerTab.title="HTTP Servers"
|
||||
AdvSceneSwitcher.httpServerTab.help="No HTTP servers configured.\nAdd one to receive incoming HTTP requests in conditions."
|
||||
AdvSceneSwitcher.httpServerTab.addButton.tooltip="Add HTTP server"
|
||||
AdvSceneSwitcher.httpServerTab.removeButton.tooltip="Remove HTTP server"
|
||||
AdvSceneSwitcher.httpServerTab.name.header="Name"
|
||||
AdvSceneSwitcher.httpServerTab.port.header="Port"
|
||||
AdvSceneSwitcher.httpServerTab.status.header="Status"
|
||||
AdvSceneSwitcher.httpServerTab.removeSingle.text="Are you sure you want to remove \"%1\"?"
|
||||
AdvSceneSwitcher.httpServerTab.removeMultiple.text="Are you sure you want to remove %1 HTTP servers?"
|
||||
AdvSceneSwitcher.condition.temporaryVariable="Macro property"
|
||||
AdvSceneSwitcher.condition.variable="Variable"
|
||||
AdvSceneSwitcher.condition.variable.type.compare="equals"
|
||||
@@ -830,6 +869,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.gameCapture="Game capture"
|
||||
AdvSceneSwitcher.condition.gameCapture.entry="{{sources}}hooked a game."
|
||||
|
||||
AdvSceneSwitcher.condition.screenshot="Screenshot"
|
||||
AdvSceneSwitcher.condition.screenshot.entry="A screenshot was taken"
|
||||
AdvSceneSwitcher.condition.mqtt="MQTT"
|
||||
@@ -879,6 +919,15 @@ AdvSceneSwitcher.action.audio.fade.rate="{{fade}}Fade{{fadeTypes}}{{rate}}per se
|
||||
AdvSceneSwitcher.action.audio.fade.wait="Wait for fade to complete."
|
||||
AdvSceneSwitcher.action.audio.fade.abort="Abort already active fade."
|
||||
AdvSceneSwitcher.action.audio.entry="{{actions}}{{audioSources}}{{volume}}{{volumeDB}}{{percentDBToggle}}{{syncOffset}}{{monitorTypes}}{{track}}"
|
||||
AdvSceneSwitcher.action.playAudio="Play Audio File"
|
||||
AdvSceneSwitcher.action.playAudio.file.browse="Browse for audio file"
|
||||
AdvSceneSwitcher.action.playAudio.layout.volume="Volume{{volumeDB}}"
|
||||
AdvSceneSwitcher.action.playAudio.layout.monitor="Monitoring{{monitorTypes}}"
|
||||
AdvSceneSwitcher.action.playAudio.wait="Wait for playback to complete"
|
||||
AdvSceneSwitcher.action.playAudio.monitorUnavailable="Audio monitoring not available"
|
||||
AdvSceneSwitcher.action.playAudio.tracks="Tracks"
|
||||
AdvSceneSwitcher.action.playAudio.layout.startOffset="{{useStartOffset}}Start at{{startOffset}}"
|
||||
AdvSceneSwitcher.action.playAudio.layout.playbackDuration="{{useDuration}}Play for{{playbackDuration}}(0 = until end)"
|
||||
AdvSceneSwitcher.action.recording="Recording"
|
||||
AdvSceneSwitcher.action.recording.type.stop="Stop recording"
|
||||
AdvSceneSwitcher.action.recording.type.start="Start recording"
|
||||
@@ -887,9 +936,10 @@ AdvSceneSwitcher.action.recording.type.unpause="Unpause recording"
|
||||
AdvSceneSwitcher.action.recording.type.split="Split recording file"
|
||||
AdvSceneSwitcher.action.recording.type.changeOutputFolder="Change output folder"
|
||||
AdvSceneSwitcher.action.recording.type.changeOutputFileFormat="Change filename formatting"
|
||||
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.split.hint="Make sure to enable automatic file splitting in the OBS settings first!"
|
||||
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
|
||||
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{chapterName}}{{pauseHint}}{{splitHint}}"
|
||||
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.durationWarn="Warning: Changing the maximum replay time will only apply the next time the replay buffer is started!"
|
||||
@@ -906,7 +956,8 @@ AdvSceneSwitcher.action.streaming.type.server="Set server URL"
|
||||
AdvSceneSwitcher.action.streaming.type.streamKey="Set stream key"
|
||||
AdvSceneSwitcher.action.streaming.type.username="Set username"
|
||||
AdvSceneSwitcher.action.streaming.type.password="Set password"
|
||||
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
||||
AdvSceneSwitcher.action.streaming.getCurrentValue="Get current value"
|
||||
AdvSceneSwitcher.action.streaming.layout="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}{{getCurrentValue}}"
|
||||
AdvSceneSwitcher.action.run="Run"
|
||||
AdvSceneSwitcher.action.run.wait.entry="{{wait}}Wait for process exit or at most {{timeout}}{{waitHelp}}"
|
||||
AdvSceneSwitcher.action.run.wait.help.tooltip="Note that macro properties won't work if you leave this unticked, as the process spawns detached from the rest of logic and there's no control over it."
|
||||
@@ -949,6 +1000,8 @@ AdvSceneSwitcher.action.source.type.openPropertiesDialog="Open properties dialog
|
||||
AdvSceneSwitcher.action.source.type.closeInteractionDialog="Close interaction dialog"
|
||||
AdvSceneSwitcher.action.source.type.closeFilterDialog="Close filter dialog"
|
||||
AdvSceneSwitcher.action.source.type.closePropertiesDialog="Close properties dialog"
|
||||
AdvSceneSwitcher.action.source.type.getSetting="Get setting value"
|
||||
AdvSceneSwitcher.action.source.type.getSettings="Get settings JSON"
|
||||
AdvSceneSwitcher.action.source.entry="{{actions}}{{sources}}{{settingsButtons}}{{deinterlaceMode}}{{deinterlaceOrder}}{{refresh}}"
|
||||
AdvSceneSwitcher.action.source.entry.settings="{{settings}}{{settingsInputMethod}}{{settingValue}}{{tempVar}}"
|
||||
AdvSceneSwitcher.action.source.warning="Warning: Enabling and disabling sources globally cannot be controlled by the OBS UI\nYou might be looking for \"Scene item visibility\""
|
||||
@@ -971,6 +1024,49 @@ AdvSceneSwitcher.action.source.inputMethod.json="Set setting JSON string"
|
||||
AdvSceneSwitcher.action.source.refresh="Refresh"
|
||||
AdvSceneSwitcher.action.source.refresh.tooltip="Repopulate the source settings selection with the settings of the source which's name matches the variable value."
|
||||
AdvSceneSwitcher.action.source.dialog.accept="Accept changes"
|
||||
AdvSceneSwitcher.action.sourceInteraction="Source Interaction"
|
||||
AdvSceneSwitcher.action.sourceInteraction.source="Source"
|
||||
AdvSceneSwitcher.action.sourceInteraction.noSelection="Select a step to edit it"
|
||||
AdvSceneSwitcher.action.sourceInteraction.record="Record interaction ..."
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseMove="Mouse move (%1, %2)"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseUp="up"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseDown="down"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseClick="Mouse %1 %2 (%3, %4)"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseClickCount=" x%1"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseWheel="Mouse wheel (%1, %2) dx=%3 dy=%4"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.keyUp="up"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.keyDown="down"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.keyPress="Key %1"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.keyPressWithText="Key %1 '%2'"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.typeText="Type \"%1\""
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.wait="Wait %1 ms"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.type="Type"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.mouseMove="Mouse move"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.mouseClick="Mouse click"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.mouseWheel="Mouse wheel"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.keyPress="Key press"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.typeText="Type text"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.wait="Wait"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.x="X"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.y="Y"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.button="Button"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.mouseUp="Mouse up"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.clickCount="Click count"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.wheelDx="Delta X"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.wheelDy="Delta Y"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.vkey="Virtual key"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.keyUp="Key up"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.text="Text"
|
||||
AdvSceneSwitcher.action.sourceInteraction.step.edit.waitMs="Duration (ms)"
|
||||
AdvSceneSwitcher.action.sourceInteraction.button.left="Left"
|
||||
AdvSceneSwitcher.action.sourceInteraction.button.middle="Middle"
|
||||
AdvSceneSwitcher.action.sourceInteraction.button.right="Right"
|
||||
AdvSceneSwitcher.action.sourceInteraction.record.title="Record Source Interactions"
|
||||
AdvSceneSwitcher.action.sourceInteraction.record.start="Start recording"
|
||||
AdvSceneSwitcher.action.sourceInteraction.record.stop="Stop recording"
|
||||
AdvSceneSwitcher.action.sourceInteraction.record.accept="Accept"
|
||||
AdvSceneSwitcher.action.sourceInteraction.record.placeholder="Start recording and interact with the source to capture steps"
|
||||
AdvSceneSwitcher.action.sourceInteraction.record.invalidSource="No valid source is selected.\nPlease select a source that supports interaction before recording."
|
||||
AdvSceneSwitcher.action.media="Media"
|
||||
AdvSceneSwitcher.action.media.type.play="Play"
|
||||
AdvSceneSwitcher.action.media.type.pause="Pause"
|
||||
@@ -990,12 +1086,12 @@ AdvSceneSwitcher.action.macro.type.pause="Pause"
|
||||
AdvSceneSwitcher.action.macro.type.unpause="Unpause"
|
||||
AdvSceneSwitcher.action.macro.type.togglePause="Toggle pause"
|
||||
AdvSceneSwitcher.action.macro.type.resetCounter="Reset counter"
|
||||
AdvSceneSwitcher.action.macro.type.run="Run macro"
|
||||
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.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.updateConditionMatchState="Reevaluate the condition state before executing this action"
|
||||
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="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.\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.else="else-actions"
|
||||
AdvSceneSwitcher.action.macro.type.run.skipWhenPaused="Skip execution when macro is paused"
|
||||
@@ -1005,6 +1101,10 @@ AdvSceneSwitcher.action.macro.type.stop="Stop actions"
|
||||
AdvSceneSwitcher.action.macro.type.disableAction="Disable action"
|
||||
AdvSceneSwitcher.action.macro.type.enableAction="Enable action"
|
||||
AdvSceneSwitcher.action.macro.type.toggleAction="Toggle action"
|
||||
AdvSceneSwitcher.action.macro.type.getInfo="Get macro info"
|
||||
AdvSceneSwitcher.action.macro.type.runMacro="Run macro"
|
||||
AdvSceneSwitcher.action.macro.type.runMacro.noConditionsWarning="Warning: The selected macro has no conditions!"
|
||||
AdvSceneSwitcher.action.macro.type.runMacro.help="Unlike \"Run macro actions\", this option will immediately re-evaluate the conditions of the selected macro and then run either its actions or else-actions depending on the result."
|
||||
AdvSceneSwitcher.action.macro.type.nestedMacro="Nested macro"
|
||||
AdvSceneSwitcher.action.macro.actionSelectionType.index="at index"
|
||||
AdvSceneSwitcher.action.macro.actionSelectionType.label="with label"
|
||||
@@ -1015,6 +1115,7 @@ AdvSceneSwitcher.action.macro.type.nestedMacro.elseActionHelp="This section allo
|
||||
AdvSceneSwitcher.action.macro.layout.run="{{actions}}{{actionSections}}of{{macros}}"
|
||||
AdvSceneSwitcher.action.macro.layout.run.condition="{{conditionBehaviors}}of{{conditionMacros}}"
|
||||
AdvSceneSwitcher.action.macro.layout.actionState="{{actions}}{{actionSelectionType}}{{actionIndex}}{{label}}{{regex}}{{actionTypes}}in{{actionSections}}section of{{macros}}"
|
||||
AdvSceneSwitcher.action.macro.layout.runMacro="{{actions}}{{runMacroHelp}}{{macros}}"
|
||||
AdvSceneSwitcher.action.macro.layout.other="{{actions}}{{macros}}"
|
||||
AdvSceneSwitcher.action.pluginState="Plugin state"
|
||||
AdvSceneSwitcher.action.pluginState.type.stop="Stop the Advanced Scene Switcher plugin"
|
||||
@@ -1087,13 +1188,16 @@ AdvSceneSwitcher.action.studioMode.type.enable="Enable studio mode"
|
||||
AdvSceneSwitcher.action.studioMode.type.disable="Disable studio mode"
|
||||
AdvSceneSwitcher.action.studioMode.entry="{{actions}}{{scenes}}"
|
||||
AdvSceneSwitcher.action.transition="Transition"
|
||||
AdvSceneSwitcher.action.transition.type.scene="scene transition"
|
||||
AdvSceneSwitcher.action.transition.type.sceneOverride="scene transition override"
|
||||
AdvSceneSwitcher.action.transition.type.sourceShow="source show transition"
|
||||
AdvSceneSwitcher.action.transition.type.sourceHide="source hide transition"
|
||||
AdvSceneSwitcher.action.transition.entry.line1="Modify{{type}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Set transition type to{{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Set transition duration to{{duration}}seconds"
|
||||
AdvSceneSwitcher.action.transition.type.scene="Modify scene transition"
|
||||
AdvSceneSwitcher.action.transition.type.sceneOverride="Modify scene transition override"
|
||||
AdvSceneSwitcher.action.transition.type.sourceShow="Modify source show transition"
|
||||
AdvSceneSwitcher.action.transition.type.sourceHide="Modify source hide transition"
|
||||
AdvSceneSwitcher.action.transition.type.tbar="Modify T-Bar position"
|
||||
AdvSceneSwitcher.action.transition.type.releaseTbar="Release T-Bar"
|
||||
AdvSceneSwitcher.action.transition.layout.type="{{type}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Set transition type to{{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Set transition duration to{{duration}}seconds"
|
||||
AdvSceneSwitcher.action.transition.layout.tbar="Set T-Bar position to{{tbarPosition}}"
|
||||
AdvSceneSwitcher.action.timer="Timer"
|
||||
AdvSceneSwitcher.action.timer.type.pause="Pause"
|
||||
AdvSceneSwitcher.action.timer.type.continue="Continue"
|
||||
@@ -1253,23 +1357,52 @@ AdvSceneSwitcher.action.twitch.type.channel.info.category.set="Set stream catego
|
||||
AdvSceneSwitcher.action.twitch.type.channel.info.tags.set="Set stream tags"
|
||||
AdvSceneSwitcher.action.twitch.type.channel.info.language.set="Set stream language"
|
||||
AdvSceneSwitcher.action.twitch.type.raid.start="Start raid"
|
||||
AdvSceneSwitcher.action.twitch.type.raid.end="Cancel raid"
|
||||
AdvSceneSwitcher.action.twitch.type.shoutout.send="Send shoutout"
|
||||
AdvSceneSwitcher.action.twitch.type.shieldMode.start="Enable shield mode"
|
||||
AdvSceneSwitcher.action.twitch.type.shieldMode.end="Disable shield mode"
|
||||
AdvSceneSwitcher.action.twitch.type.commercial.start="Start commercial"
|
||||
AdvSceneSwitcher.action.twitch.type.commercial.snooze="Snooze next ad"
|
||||
AdvSceneSwitcher.action.twitch.type.marker.create="Create stream marker"
|
||||
AdvSceneSwitcher.action.twitch.type.clip.create="Create stream clip"
|
||||
AdvSceneSwitcher.action.twitch.type.chat.announcement.send="Send chat announcement"
|
||||
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable="Enable chat's emote-only mode"
|
||||
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="Disable chat's emote-only mode"
|
||||
AdvSceneSwitcher.action.twitch.type.chat.followerOnly.enable="Enable chat's follower-only mode"
|
||||
AdvSceneSwitcher.action.twitch.type.chat.followerOnly.disable="Disable chat's follower-only mode"
|
||||
AdvSceneSwitcher.action.twitch.type.chat.subscriberOnly.enable="Enable chat's subscriber-only mode"
|
||||
AdvSceneSwitcher.action.twitch.type.chat.subscriberOnly.disable="Disable chat's subscriber-only mode"
|
||||
AdvSceneSwitcher.action.twitch.type.chat.slowMode.enable="Enable chat's slow mode"
|
||||
AdvSceneSwitcher.action.twitch.type.chat.slowMode.disable="Disable chat's slow mode"
|
||||
AdvSceneSwitcher.action.twitch.type.chat.nonModeratorDelay.enable="Enable chat's non-moderator message delay"
|
||||
AdvSceneSwitcher.action.twitch.type.chat.nonModeratorDelay.disable="Disable chat's non-moderator message delay"
|
||||
AdvSceneSwitcher.action.twitch.type.chat.uniqueMode.enable="Enable chat's unique message mode"
|
||||
AdvSceneSwitcher.action.twitch.type.chat.uniqueMode.disable="Disable chat's unique message mode"
|
||||
AdvSceneSwitcher.action.twitch.type.chat.sendMessage="Send chat message"
|
||||
AdvSceneSwitcher.action.twitch.type.user.getInfo="Get user information"
|
||||
AdvSceneSwitcher.action.twitch.type.user.ban="Ban user"
|
||||
AdvSceneSwitcher.action.twitch.type.user.unban="Unban user"
|
||||
AdvSceneSwitcher.action.twitch.type.user.block="Block user"
|
||||
AdvSceneSwitcher.action.twitch.type.user.unblock="Unblock user"
|
||||
AdvSceneSwitcher.action.twitch.type.user.moderator.add="Add user as moderator"
|
||||
AdvSceneSwitcher.action.twitch.type.user.moderator.delete="Remove user as moderator"
|
||||
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.reward.getInfo="Get channel points reward information"
|
||||
AdvSceneSwitcher.action.twitch.type.channel.getInfo="Get channel information"
|
||||
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.layout.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{nonModDelayDuration}}{{channel}}{{pointsReward}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.chat="Using account{{account}}{{actions}}on{{channel}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.channel.getInfo="Using account{{account}}{{actions}}of{{channel}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.getInfo="Using account{{account}}{{actions}}for{{userInfoQueryType}}{{userLogin}}{{userId}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.reward.getInfo="Using account{{account}}{{actions}}for channel{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.getInfo.row1="Using account{{account}}{{actions}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.getInfo.row2="for{{userInfoQueryType}}{{userLogin}}{{userId}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.moderation.row1="Using account{{account}}{{actions}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.moderation.row2="on channel{{channel}}for{{userInfoQueryType}}{{userLogin}}{{userId}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.ban.row1="Using account{{account}}{{actions}}timeout{{duration}}"
|
||||
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.row2="{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
|
||||
AdvSceneSwitcher.action.twitch.title.title="Enter title"
|
||||
AdvSceneSwitcher.action.twitch.marker.description="Describe marker"
|
||||
AdvSceneSwitcher.action.twitch.clip.hasDelay="Add a slight delay before capturing the clip"
|
||||
@@ -1290,6 +1423,8 @@ AdvSceneSwitcher.action.twitch.tags.duplicate.info="This tag is already in the l
|
||||
AdvSceneSwitcher.action.twitch.tags.limit="Tag Limit Reached"
|
||||
AdvSceneSwitcher.action.twitch.tags.limit.info="You can only have up to %1 tags."
|
||||
AdvSceneSwitcher.action.twitch.type.channel.info.contentClassification.set="Set content classification labels"
|
||||
AdvSceneSwitcher.action.twitch.type.channel.info.brandedContent.enable="Enable branded content"
|
||||
AdvSceneSwitcher.action.twitch.type.channel.info.brandedContent.disable="Disable branded content"
|
||||
AdvSceneSwitcher.action.twitch.contentClassification.debatedSocialIssuesAndPolitics="Discussions or debates about politics or sensitive social issues such as elections, civic integrity, military conflict, and civil rights."
|
||||
AdvSceneSwitcher.action.twitch.contentClassification.drugsIntoxication="Excessive tobacco glorification or promotion, any marijuana consumption/use, legal drug and alcohol induced intoxication, discussions of illegal drugs."
|
||||
AdvSceneSwitcher.action.twitch.contentClassification.sexualThemes="Content that focuses on sexualized activities, sexual topics, or experiences."
|
||||
@@ -1395,6 +1530,7 @@ AdvSceneSwitcher.askBackup="Detected a new version of the Advanced Scene Switche
|
||||
AdvSceneSwitcher.askForMacro="Select macro{{macroSelection}}"
|
||||
|
||||
AdvSceneSwitcher.crashDetected="OBS did not shut down cleanly (for example, due to a crash or freeze).\n\nThe Advanced Scene Switcher plugin would normally start automatically.\nWould you like to keep it stopped for now?"
|
||||
AdvSceneSwitcher.crashDetected.suppressCheckbox="Do not show this dialog again"
|
||||
|
||||
AdvSceneSwitcher.close="Close"
|
||||
AdvSceneSwitcher.browse="Browse"
|
||||
@@ -1664,6 +1800,7 @@ AdvSceneSwitcher.script.language.lua="LUA"
|
||||
AdvSceneSwitcher.script.language.select="--select language--"
|
||||
AdvSceneSwitcher.script.language.layout="Script language:{{language}}"
|
||||
AdvSceneSwitcher.script.file.open="Open"
|
||||
AdvSceneSwitcher.script.file.select="Select Script File"
|
||||
AdvSceneSwitcher.script.file.open.failed="Could not open script file!"
|
||||
AdvSceneSwitcher.script.file.warning.notFound="Warning: The given file path was not found!"
|
||||
AdvSceneSwitcher.script.file.warning.openFail="Warning: Could not open the script file!"
|
||||
@@ -1672,6 +1809,10 @@ AdvSceneSwitcher.script.file.layout="Script file:{{path}}{{open}}"
|
||||
|
||||
AdvSceneSwitcher.tempVar.select="--select value--"
|
||||
AdvSceneSwitcher.tempVar.selectionInfo.lastValues="Last values:"
|
||||
AdvSceneSwitcher.tempVar.outputMappings="Save outputs to variables:"
|
||||
AdvSceneSwitcher.tempVar.outputMappings.add="+ Add output mapping"
|
||||
AdvSceneSwitcher.tempVar.outputMappings.toggle="Assign outputs to variables"
|
||||
AdvSceneSwitcher.tempVar.outputMappings.remove="Remove output mapping"
|
||||
|
||||
AdvSceneSwitcher.tempVar.twitch.broadcaster_user_id="Twitch broadcaster user ID"
|
||||
AdvSceneSwitcher.tempVar.twitch.broadcaster_user_id.description="The numerical Twitch user ID of the broadcaster."
|
||||
@@ -2153,6 +2294,14 @@ AdvSceneSwitcher.tempVar.scene.position="Position in scenes list"
|
||||
|
||||
AdvSceneSwitcher.tempVar.window.window="Window title"
|
||||
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.description="The window class of the matching window."
|
||||
AdvSceneSwitcher.tempVar.window.windowText="Window text"
|
||||
@@ -2181,8 +2330,21 @@ AdvSceneSwitcher.tempVar.macro.runCount="Run count"
|
||||
AdvSceneSwitcher.tempVar.macro.runCount.description="The number of times a macro was executed."
|
||||
AdvSceneSwitcher.tempVar.macro.matchedCount="Matched count"
|
||||
AdvSceneSwitcher.tempVar.macro.matchedCount.description="The number of macros of which the condition state was true."
|
||||
AdvSceneSwitcher.tempVar.macro.info.conditionCount="Condition count"
|
||||
AdvSceneSwitcher.tempVar.macro.info.conditionCount.description="The number of conditions in the macro."
|
||||
AdvSceneSwitcher.tempVar.macro.info.actionCount="Action count"
|
||||
AdvSceneSwitcher.tempVar.macro.info.actionCount.description="The number of actions in the macro."
|
||||
AdvSceneSwitcher.tempVar.macro.info.elseActionCount="Else-action count"
|
||||
AdvSceneSwitcher.tempVar.macro.info.elseActionCount.description="The number of else-actions in the macro."
|
||||
AdvSceneSwitcher.tempVar.macro.info.paused="Paused"
|
||||
AdvSceneSwitcher.tempVar.macro.info.paused.description="Whether the macro is currently paused. Value is \"true\" or \"false\"."
|
||||
AdvSceneSwitcher.tempVar.macro.info.runCount="Run count"
|
||||
AdvSceneSwitcher.tempVar.macro.info.runCount.description="The number of times the macro has been executed."
|
||||
AdvSceneSwitcher.tempVar.macro.info.secondsSinceLastRun="Seconds since last run"
|
||||
AdvSceneSwitcher.tempVar.macro.info.secondsSinceLastRun.description="The number of seconds elapsed since the macro was last executed. Value is -1 if the macro has never been executed."
|
||||
|
||||
AdvSceneSwitcher.tempVar.process.name="Process name"
|
||||
AdvSceneSwitcher.tempVar.process.path="Process path"
|
||||
|
||||
AdvSceneSwitcher.tempVar.run.process.id="Process ID"
|
||||
AdvSceneSwitcher.tempVar.run.process.id.description="PID of the process assigned by the system."
|
||||
@@ -2205,6 +2367,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.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.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.description="The number of objects the given model has identified in a given video input frame."
|
||||
AdvSceneSwitcher.tempVar.video.brightness="Average brightness"
|
||||
@@ -2213,6 +2383,8 @@ AdvSceneSwitcher.tempVar.video.text="OCR text"
|
||||
AdvSceneSwitcher.tempVar.video.text.description="The text detected in a given video input frame."
|
||||
AdvSceneSwitcher.tempVar.video.color="Average color"
|
||||
AdvSceneSwitcher.tempVar.video.color.description="The average RGB color in a given video input frame in HexArgb format."
|
||||
AdvSceneSwitcher.tempVar.video.dominantColor="Dominant color"
|
||||
AdvSceneSwitcher.tempVar.video.dominantColor.description="The most dominant RGB color in a given video input frame in HexArgb format.\nDetermined using k-means clustering, which groups similar colors and returns the center of the largest group.\nDue to the nature of the algorithm the result may vary slightly between evaluations."
|
||||
|
||||
AdvSceneSwitcher.tempVar.websocket.message="Received websocket message"
|
||||
AdvSceneSwitcher.tempVar.websocket.message.description="The received websocket message, which matched the given pattern"
|
||||
@@ -2342,9 +2514,14 @@ AdvSceneSwitcher.tempVar.gameCapture.executable="Executable"
|
||||
AdvSceneSwitcher.tempVar.gameCapture.executable.description="Executable name of the application captured by the source."
|
||||
|
||||
AdvSceneSwitcher.tempVar.http.status="Status code"
|
||||
AdvSceneSwitcher.tempVar.http.body="Message body"
|
||||
AdvSceneSwitcher.tempVar.http.error="Error"
|
||||
AdvSceneSwitcher.tempVar.http.error.description="Empty when no error occurred.\nOther possible values:\n\n * Could not establish connection\n * Failed to bind IP address\n * Failed to read connection\n * Failed to write connection\n * Maximum redirect count exceeded\n * Connection handling canceled\n * SSL connection failed\n * SSL certificate loading failed\n * SSL server verification failed\n * Unsupported HTTP multipart boundary characters\n * Compression failed\n * Connection timed out\n * Proxy connection failed\n * Unknown"
|
||||
AdvSceneSwitcher.tempVar.http.method="Method"
|
||||
AdvSceneSwitcher.tempVar.http.method.description="Received HTTP request method"
|
||||
AdvSceneSwitcher.tempVar.http.path="Request path"
|
||||
AdvSceneSwitcher.tempVar.http.path.description="Received HTTP request path"
|
||||
AdvSceneSwitcher.tempVar.http.body="Message body"
|
||||
AdvSceneSwitcher.tempVar.http.body.description="Received HTTP request body"
|
||||
|
||||
AdvSceneSwitcher.tempVar.mqtt.message="Message"
|
||||
|
||||
@@ -2354,6 +2531,14 @@ AdvSceneSwitcher.tempVar.cursor.y="Cursor position (Y)"
|
||||
AdvSceneSwitcher.tempVar.replay.lastSavePath="Last save path"
|
||||
AdvSceneSwitcher.tempVar.replay.lastSavePath.description="The file path of the last replay buffer saved."
|
||||
|
||||
AdvSceneSwitcher.tempVar.sequence.macro="Macro"
|
||||
AdvSceneSwitcher.tempVar.sequence.macro.description="The name of the macro executed by the \"Sequence\" action.\nIf no macro was executed the returned value is the empty string."
|
||||
AdvSceneSwitcher.tempVar.sequence.nextMacro="Next Macro"
|
||||
AdvSceneSwitcher.tempVar.sequence.nextMacro.description="The name of the macro which will be executed next after the index was set.\nIf no macro will be executed the returned value is the empty string."
|
||||
|
||||
AdvSceneSwitcher.tempVar.random.macro="Macro"
|
||||
AdvSceneSwitcher.tempVar.random.macro.description="The name of the macro executed by the \"Random\" action.\nIf no macro was executed the returned value is the empty string."
|
||||
|
||||
AdvSceneSwitcher.selectScene="--select scene--"
|
||||
AdvSceneSwitcher.selectCanvas="--select canvas--"
|
||||
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
|
||||
@@ -2383,6 +2568,9 @@ AdvSceneSwitcher.selectDisplay="--select display--"
|
||||
AdvSceneSwitcher.invaildEntriesWillNotBeSaved="invalid entries will not be saved"
|
||||
AdvSceneSwitcher.selectWindowTip="Use \"OBS\" to specify OBS window\nUse \"Task Switching\"to specify ALT + TAB"
|
||||
|
||||
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.bool=" (Bool)"
|
||||
AdvSceneSwitcher.settings.suffix.type.int=" (Int)"
|
||||
@@ -2550,6 +2738,64 @@ Basic.Settings.Video.FPSCommon="Common FPS Values"
|
||||
Basic.Settings.Video.FPSInteger="Integer 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 :)
|
||||
# Transition Tab
|
||||
AdvSceneSwitcher.transitionTab.title="Transition"
|
||||
|
||||
@@ -83,7 +83,6 @@ AdvSceneSwitcher.macroTab.highlightTrueConditions="Resaltar condiciones de la ma
|
||||
AdvSceneSwitcher.macroTab.highlightPerformedActions="Resaltar acciones realizadas recientemente de la macro seleccionada actualmente"
|
||||
|
||||
; Lógica de macros
|
||||
AdvSceneSwitcher.logic.none="Omitir entrada"
|
||||
AdvSceneSwitcher.logic.and="Y"
|
||||
AdvSceneSwitcher.logic.or="O"
|
||||
AdvSceneSwitcher.logic.andNot="Y no"
|
||||
@@ -117,9 +116,7 @@ AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour="Durante la tra
|
||||
AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="Durante la transición, verifique la escena de origen de la transición"
|
||||
AdvSceneSwitcher.condition.window="Ventana"
|
||||
AdvSceneSwitcher.condition.file="Archivo"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="Contenido de{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.file.layout="Contenido de{{filePath}}{{conditions}}{{regex}}"
|
||||
AdvSceneSwitcher.condition.media="Medios"
|
||||
AdvSceneSwitcher.condition.media.anyOnScene="Cualquier fuente multimedia activada"
|
||||
AdvSceneSwitcher.condition.media.allOnScene="Todas las fuentes de medios activadas"
|
||||
@@ -175,7 +172,7 @@ AdvSceneSwitcher.condition.record.state.start="Grabación en ejecución"
|
||||
AdvSceneSwitcher.condition.record.state.pause="Grabación en pausa"
|
||||
AdvSceneSwitcher.condition.record.state.stop="Grabación detenida"
|
||||
AdvSceneSwitcher.condition.process="Proceso"
|
||||
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}se está ejecutando{{focused}}y está enfocado"
|
||||
AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}se está ejecutando{{focused}}y está enfocado"
|
||||
AdvSceneSwitcher.condition.idle="Inactivo"
|
||||
AdvSceneSwitcher.condition.idle.entry="No hay entradas de teclado o ratón durante {{duration}}"
|
||||
AdvSceneSwitcher.condition.pluginState="Estado del complemento"
|
||||
@@ -334,7 +331,6 @@ AdvSceneSwitcher.action.recording.type.start="Iniciar grabación"
|
||||
AdvSceneSwitcher.action.recording.type.pause="Pausar grabación"
|
||||
AdvSceneSwitcher.action.recording.type.unpause="Reanudar grabación"
|
||||
AdvSceneSwitcher.action.recording.pause.hint="Tenga en cuenta que, dependiendo de la configuración de grabación, es posible que no pueda pausar la grabación"
|
||||
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
|
||||
AdvSceneSwitcher.action.replay="Búfer de reproducción"
|
||||
AdvSceneSwitcher.action.replay.saveWarn="Advertencia: ¡Guardar con demasiada frecuencia puede hacer que el búfer de reproducción no se guarde realmente!"
|
||||
AdvSceneSwitcher.action.replay.type.stop="Detener el búfer de reproducción"
|
||||
@@ -343,7 +339,6 @@ AdvSceneSwitcher.action.replay.type.save="Guardar búfer de reproducción"
|
||||
AdvSceneSwitcher.action.streaming="Transmisión"
|
||||
AdvSceneSwitcher.action.streaming.type.stop="Detener transmisión"
|
||||
AdvSceneSwitcher.action.streaming.type.start="Iniciar transmisión"
|
||||
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
||||
AdvSceneSwitcher.action.run="Ejecutar"
|
||||
AdvSceneSwitcher.action.sceneVisibility="Visibilidad del elemento de escena"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.show="Mostrar"
|
||||
@@ -375,7 +370,7 @@ AdvSceneSwitcher.action.macro="Macro"
|
||||
AdvSceneSwitcher.action.macro.type.pause="Pausa"
|
||||
AdvSceneSwitcher.action.macro.type.unpause="Reanudar"
|
||||
AdvSceneSwitcher.action.macro.type.resetCounter="Reiniciar contador"
|
||||
AdvSceneSwitcher.action.macro.type.run="Ejecutar"
|
||||
AdvSceneSwitcher.action.macro.type.runActions="Ejecutar"
|
||||
AdvSceneSwitcher.action.macro.type.stop="Detener"
|
||||
AdvSceneSwitcher.action.pluginState="Estado del complemento"
|
||||
AdvSceneSwitcher.action.pluginState.type.stop="Detener el complemento Advanced Scene Switcher"
|
||||
@@ -423,9 +418,9 @@ AdvSceneSwitcher.action.transition.type.scene="transición de escena"
|
||||
AdvSceneSwitcher.action.transition.type.sceneOverride="anulación de transición de escena"
|
||||
AdvSceneSwitcher.action.transition.type.sourceShow="transición del programa de origen"
|
||||
AdvSceneSwitcher.action.transition.type.sourceHide="fuente ocultar transición"
|
||||
AdvSceneSwitcher.action.transition.entry.line1="Modificar {{type}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Establecer el tipo de transición en {{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Establecer la duración de la transición en {{duration}}segundos"
|
||||
AdvSceneSwitcher.action.transition.layout.type="Modificar {{type}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Establecer el tipo de transición en {{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Establecer la duración de la transición en {{duration}}segundos"
|
||||
AdvSceneSwitcher.action.timer="Temporizador"
|
||||
AdvSceneSwitcher.action.timer.type.pause="Pausa"
|
||||
AdvSceneSwitcher.action.timer.type.continue="Continuar"
|
||||
|
||||
@@ -131,7 +131,6 @@ AdvSceneSwitcher.macroList.deleted="supprimé"
|
||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" est déjà sélectionné !"
|
||||
|
||||
; Macro Logic
|
||||
AdvSceneSwitcher.logic.none="Ignorer l'entrée"
|
||||
AdvSceneSwitcher.logic.and="Et"
|
||||
AdvSceneSwitcher.logic.or="Ou"
|
||||
AdvSceneSwitcher.logic.andNot="Et pas"
|
||||
@@ -187,8 +186,6 @@ AdvSceneSwitcher.condition.file="Fichier"
|
||||
AdvSceneSwitcher.condition.file.type.match="correspond à"
|
||||
AdvSceneSwitcher.condition.file.type.contentChange="a changé de contenu"
|
||||
AdvSceneSwitcher.condition.file.type.dateChange="a changé de date de modification"
|
||||
AdvSceneSwitcher.condition.file.remote="Fichier distant"
|
||||
AdvSceneSwitcher.condition.file.local="Fichier local"
|
||||
AdvSceneSwitcher.condition.media="Média"
|
||||
AdvSceneSwitcher.condition.media.source="Source"
|
||||
AdvSceneSwitcher.condition.media.anyOnScene="Toute source média sur la scène"
|
||||
@@ -281,8 +278,8 @@ AdvSceneSwitcher.condition.record.state.start="Enregistrement en cours"
|
||||
AdvSceneSwitcher.condition.record.state.pause="Enregistrement en pause"
|
||||
AdvSceneSwitcher.condition.record.state.stop="Arrêt de l'enregistrement"
|
||||
AdvSceneSwitcher.condition.process="Processus"
|
||||
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}en cours d'exécution{{focused}}et est au premier plan"
|
||||
AdvSceneSwitcher.condition.process.entry.focus="Processus au premier plan actuel :{{focusProcess}}"
|
||||
AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}en cours d'exécution{{focused}}et est au premier plan"
|
||||
AdvSceneSwitcher.condition.process.layout.focus="Processus au premier plan actuel :{{focusProcess}}"
|
||||
AdvSceneSwitcher.condition.idle="Inactif"
|
||||
AdvSceneSwitcher.condition.idle.entry="Aucune entrée de clavier ou de souris pendant{{duration}}"
|
||||
AdvSceneSwitcher.condition.pluginState="État du plugin"
|
||||
@@ -553,7 +550,7 @@ AdvSceneSwitcher.action.macro="Macro"
|
||||
AdvSceneSwitcher.action.macro.type.pause="Pause"
|
||||
AdvSceneSwitcher.action.macro.type.unpause="Reprendre"
|
||||
AdvSceneSwitcher.action.macro.type.resetCounter="Réinitialiser le compteur"
|
||||
AdvSceneSwitcher.action.macro.type.run="Exécuter les actions"
|
||||
AdvSceneSwitcher.action.macro.type.runActions="Exécuter les actions"
|
||||
AdvSceneSwitcher.action.macro.type.stop="Arrêter les actions"
|
||||
AdvSceneSwitcher.action.macro.type.disableAction="Désactiver l'action"
|
||||
AdvSceneSwitcher.action.macro.type.enableAction="Activer l'action"
|
||||
@@ -622,9 +619,9 @@ AdvSceneSwitcher.action.transition.type.scene="Transition de scène"
|
||||
AdvSceneSwitcher.action.transition.type.sceneOverride="Remplacement de la transition de scène"
|
||||
AdvSceneSwitcher.action.transition.type.sourceShow="Transition d'affichage de la source"
|
||||
AdvSceneSwitcher.action.transition.type.sourceHide="Transition de masquage de la source"
|
||||
AdvSceneSwitcher.action.transition.entry.line1="Modifier{{type}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Définir le type de transition sur{{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Définir la durée de la transition à{{duration}}secondes"
|
||||
AdvSceneSwitcher.action.transition.layout.type="Modifier{{type}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Définir le type de transition sur{{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Définir la durée de la transition à{{duration}}secondes"
|
||||
AdvSceneSwitcher.action.timer="Minuterie"
|
||||
AdvSceneSwitcher.action.timer.type.pause="Mettre en pause"
|
||||
AdvSceneSwitcher.action.timer.type.continue="Continuer"
|
||||
|
||||
@@ -275,7 +275,6 @@ AdvSceneSwitcher.macroList.deleted="削除"
|
||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" はすでに選択されています!"
|
||||
|
||||
# Macro Logic
|
||||
AdvSceneSwitcher.logic.none="入力無視"
|
||||
; AdvSceneSwitcher.logic.and="And"
|
||||
; AdvSceneSwitcher.logic.or="Or"
|
||||
; AdvSceneSwitcher.logic.andNot="And not"
|
||||
@@ -331,14 +330,6 @@ AdvSceneSwitcher.condition.window.entry.text="{{checkText}}ウィンドウにテ
|
||||
AdvSceneSwitcher.condition.window.entry.text.note="このオプションは、ウィンドウに表示されているすべてのテキストに対して機能するとは限りません。\nその場合は、代わりにビデオ条件 OCR チェックの使用を検討してください。"
|
||||
AdvSceneSwitcher.condition.window.entry.currentFocus="現在のフォーカスウィンドウ:{{focusWindow}}"
|
||||
AdvSceneSwitcher.condition.file="ファイル"
|
||||
; AdvSceneSwitcher.condition.file.type.match="matches"
|
||||
AdvSceneSwitcher.condition.file.type.contentChange="内容変更しました"
|
||||
AdvSceneSwitcher.condition.file.type.dateChange="更新日変更"
|
||||
AdvSceneSwitcher.condition.file.remote="リモートファイル"
|
||||
AdvSceneSwitcher.condition.file.local="ローカルファイル"
|
||||
; AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
||||
; AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
; AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||
; AdvSceneSwitcher.condition.media="Media"
|
||||
AdvSceneSwitcher.condition.media.checkType.state="状態が一致"
|
||||
AdvSceneSwitcher.condition.media.checkType.time="時間が一致"
|
||||
@@ -453,8 +444,8 @@ AdvSceneSwitcher.condition.stream.service.tooltip="現在のサービス名: %1"
|
||||
AdvSceneSwitcher.condition.record.state.duration="録音時間が長くなっています。"
|
||||
; AdvSceneSwitcher.condition.record.entry="{{condition}}{{duration}}"
|
||||
AdvSceneSwitcher.condition.process="プロセス"
|
||||
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}が実行中{{focused}}に集中しています"
|
||||
AdvSceneSwitcher.condition.process.entry.focus="現在のフォアグラウンドプロセス:{{focusProcess}}"
|
||||
AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}が実行中{{focused}}に集中しています"
|
||||
AdvSceneSwitcher.condition.process.layout.focus="現在のフォアグラウンドプロセス:{{focusProcess}}"
|
||||
AdvSceneSwitcher.condition.idle="アイドル"
|
||||
AdvSceneSwitcher.condition.idle.entry="{{duration}}の間、キーボードまたはマウスの入力がありません"
|
||||
AdvSceneSwitcher.condition.pluginState="プラグインの状態"
|
||||
@@ -931,7 +922,7 @@ AdvSceneSwitcher.action.macro.type.pause="一時停止"
|
||||
AdvSceneSwitcher.action.macro.type.unpause="一時停止解除"
|
||||
AdvSceneSwitcher.action.macro.type.togglePause="一時停止を切り替え"
|
||||
AdvSceneSwitcher.action.macro.type.resetCounter="カウンターリセット"
|
||||
AdvSceneSwitcher.action.macro.type.run="マクロを実行"
|
||||
AdvSceneSwitcher.action.macro.type.runActions="マクロを実行"
|
||||
AdvSceneSwitcher.action.macro.type.run.conditions.ignore="条件の状態を考慮しない"
|
||||
AdvSceneSwitcher.action.macro.type.run.conditions.true="条件が true と評価された場合のみ"
|
||||
AdvSceneSwitcher.action.macro.type.run.conditions.false="条件が false と評価された場合のみ"
|
||||
@@ -1020,9 +1011,9 @@ AdvSceneSwitcher.action.transition="トランジション"
|
||||
; AdvSceneSwitcher.action.transition.type.sceneOverride="シーントランジションオーバーライド"
|
||||
; AdvSceneSwitcher.action.transition.type.sourceShow="ソース表示トランジション"
|
||||
; AdvSceneSwitcher.action.transition.type.sourceHide="ソース非表示トランジション"
|
||||
; AdvSceneSwitcher.action.transition.entry.line1="{{type}}{{scenes}}{{sources}} の変更"
|
||||
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}トランジションタイプを{{transitions}}に設定します"
|
||||
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}移行時間を {{duration}} 秒に設定します"
|
||||
; AdvSceneSwitcher.action.transition.layout.type="{{type}}{{scenes}}{{sources}} の変更"
|
||||
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}トランジションタイプを{{transitions}}に設定します"
|
||||
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}移行時間を {{duration}} 秒に設定します"
|
||||
AdvSceneSwitcher.action.timer="タイマー"
|
||||
AdvSceneSwitcher.action.timer.type.pause="一時停止"
|
||||
AdvSceneSwitcher.action.timer.type.continue="続行"
|
||||
@@ -1190,8 +1181,15 @@ AdvSceneSwitcher.action.twitch.reward.toggleControl="リワード名/変数選
|
||||
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Twitchアカウントを選択しないとカテゴリを選択できません!"
|
||||
; AdvSceneSwitcher.action.twitch.layout.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.chat="{{channel}}でアカウント{{account}}{{actions}}を使用しています"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.getInfo="{{userInfoQueryType}}{{userLogin}}{{userId}}でアカウント{{account}}{{actions}}を使用"
|
||||
AdvSceneSwitcher.action.twitch.layout.reward.getInfo="チャンネル{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}でアカウント{{account}}{{actions}}を使用"
|
||||
AdvSceneSwitcher.action.twitch.layout.channel.getInfo="チャンネル{{channel}}でアカウント{{account}}{{actions}}を使用"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.getInfo.row1="アカウント{{account}}{{actions}}を使用"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.getInfo.row2="{{userInfoQueryType}}{{userLogin}}{{userId}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.moderation.row1="アカウント{{account}}{{actions}}を使用"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.moderation.row2="チャンネル{{channel}}{{userInfoQueryType}}{{userLogin}}{{userId}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.ban.row1="アカウント{{account}}{{actions}}を使用 タイムアウト{{duration}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.ban.row2="チャンネル{{channel}}{{userInfoQueryType}}{{userLogin}}{{userId}} 理由{{banReason}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.reward.getInfo.row1="チャンネル{{channel}}でアカウント{{account}}{{actions}}を使用"
|
||||
AdvSceneSwitcher.action.twitch.layout.reward.getInfo.row2="{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
|
||||
AdvSceneSwitcher.action.twitch.title.title="タイトルを入力"
|
||||
AdvSceneSwitcher.action.twitch.marker.description="マーカーの説明"
|
||||
AdvSceneSwitcher.action.twitch.clip.hasDelay="クリップをキャプチャする前にわずかな遅延を追加します"
|
||||
|
||||
@@ -239,7 +239,6 @@ AdvSceneSwitcher.macroList.deleted="excluída"
|
||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" já está selecionada!"
|
||||
|
||||
; Macro Logic
|
||||
AdvSceneSwitcher.logic.none="Ignorar entrada"
|
||||
AdvSceneSwitcher.logic.and="E"
|
||||
AdvSceneSwitcher.logic.or="Ou"
|
||||
AdvSceneSwitcher.logic.andNot="E não"
|
||||
@@ -295,11 +294,6 @@ AdvSceneSwitcher.condition.file="Arquivo"
|
||||
AdvSceneSwitcher.condition.file.type.match="corresponde"
|
||||
AdvSceneSwitcher.condition.file.type.contentChange="conteúdo mudou"
|
||||
AdvSceneSwitcher.condition.file.type.dateChange="data de modificação mudou"
|
||||
AdvSceneSwitcher.condition.file.remote="Arquivo remoto"
|
||||
AdvSceneSwitcher.condition.file.local="Arquivo local"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.media="Mídia"
|
||||
AdvSceneSwitcher.condition.media.checkType.state="Estado corresponde"
|
||||
AdvSceneSwitcher.condition.media.checkType.time="Restrição de tempo corresponde"
|
||||
@@ -400,8 +394,8 @@ AdvSceneSwitcher.condition.record.state.stop="Gravação parada"
|
||||
AdvSceneSwitcher.condition.record.state.duration="Duração da gravação é maior que"
|
||||
AdvSceneSwitcher.condition.record.entry="{{condition}}{{duration}}"
|
||||
AdvSceneSwitcher.condition.process="Processo"
|
||||
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}está em execução{{focused}}e está em foco"
|
||||
AdvSceneSwitcher.condition.process.entry.focus="Processo atual em primeiro plano:{{focusProcess}}"
|
||||
AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}está em execução{{focused}}e está em foco"
|
||||
AdvSceneSwitcher.condition.process.layout.focus="Processo atual em primeiro plano:{{focusProcess}}"
|
||||
AdvSceneSwitcher.condition.idle="Inativo"
|
||||
AdvSceneSwitcher.condition.idle.entry="Sem entradas de teclado ou mouse por {{duration}}"
|
||||
AdvSceneSwitcher.condition.pluginState="Estado do plugin"
|
||||
@@ -754,7 +748,6 @@ AdvSceneSwitcher.action.recording.type.changeOutputFolder="Alterar pasta de saí
|
||||
AdvSceneSwitcher.action.recording.type.changeOutputFileFormat="Alterar formatação do nome do arquivo"
|
||||
AdvSceneSwitcher.action.recording.pause.hint="Observe que dependendo das suas configurações de gravação, você pode não conseguir pausar a gravação"
|
||||
AdvSceneSwitcher.action.recording.split.hint="Certifique-se de habilitar a divisão automática de arquivos nas configurações do OBS primeiro!"
|
||||
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
|
||||
AdvSceneSwitcher.action.replay="Buffer de replay"
|
||||
AdvSceneSwitcher.action.replay.saveWarn="Aviso: Salvar com muita frequência pode resultar em o buffer de replay não ser realmente salvo!"
|
||||
AdvSceneSwitcher.action.replay.durationWarn="Aviso: Alterar o tempo máximo de replay só se aplicará na próxima vez que o buffer de replay for iniciado!"
|
||||
@@ -771,7 +764,6 @@ AdvSceneSwitcher.action.streaming.type.server="Definir URL do servidor"
|
||||
AdvSceneSwitcher.action.streaming.type.streamKey="Definir chave de transmissão"
|
||||
AdvSceneSwitcher.action.streaming.type.username="Definir nome de usuário"
|
||||
AdvSceneSwitcher.action.streaming.type.password="Definir senha"
|
||||
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
||||
AdvSceneSwitcher.action.run="Executar"
|
||||
AdvSceneSwitcher.action.run.wait.entry="{{wait}}Aguardar a saída do processo ou no máximo {{timeout}}{{waitHelp}}"
|
||||
AdvSceneSwitcher.action.run.wait.help.tooltip="Observe que as propriedades da macro não funcionarão se você deixar isso desmarcado, pois o processo é iniciado de forma independente do restante da lógica e não há controle sobre ele."
|
||||
@@ -846,7 +838,7 @@ AdvSceneSwitcher.action.macro="Macro"
|
||||
AdvSceneSwitcher.action.macro.type.pause="Pausar"
|
||||
AdvSceneSwitcher.action.macro.type.unpause="Retomar"
|
||||
AdvSceneSwitcher.action.macro.type.resetCounter="Reiniciar contador"
|
||||
AdvSceneSwitcher.action.macro.type.run="Executar macro"
|
||||
AdvSceneSwitcher.action.macro.type.runActions="Executar macro"
|
||||
AdvSceneSwitcher.action.macro.type.run.conditions.ignore="Não considerar o estado da condição"
|
||||
AdvSceneSwitcher.action.macro.type.run.conditions.true="Apenas se as condições forem verdadeiras"
|
||||
AdvSceneSwitcher.action.macro.type.run.conditions.false="Apenas se as condições forem falsas"
|
||||
@@ -933,9 +925,9 @@ AdvSceneSwitcher.action.transition.type.scene="transição de cena"
|
||||
AdvSceneSwitcher.action.transition.type.sceneOverride="substituição de transição de cena"
|
||||
AdvSceneSwitcher.action.transition.type.sourceShow="transição de exibição de fonte"
|
||||
AdvSceneSwitcher.action.transition.type.sourceHide="transição de ocultação de fonte"
|
||||
AdvSceneSwitcher.action.transition.entry.line1="Modificar{{type}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Definir tipo de transição para{{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Definir duração da transição para{{duration}}segundos"
|
||||
AdvSceneSwitcher.action.transition.layout.type="Modificar{{type}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Definir tipo de transição para{{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Definir duração da transição para{{duration}}segundos"
|
||||
AdvSceneSwitcher.action.timer="Temporizador"
|
||||
AdvSceneSwitcher.action.timer.type.pause="Pausar"
|
||||
AdvSceneSwitcher.action.timer.type.continue="Continuar"
|
||||
@@ -1065,7 +1057,7 @@ AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable="Habilitar modo apenas
|
||||
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="Desabilitar modo apenas emotes no chat"
|
||||
AdvSceneSwitcher.action.twitch.type.chat.sendMessage="Enviar mensagem de chat"
|
||||
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Não é possível selecionar categoria sem selecionar uma conta Twitch primeiro!"
|
||||
AdvSceneSwitcher.action.twitch.layout.default="Em{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.default="Em{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}{{nonModDelayDuration}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.chat="Usando conta{{account}}{{actions}}em{{channel}}"
|
||||
AdvSceneSwitcher.action.twitch.title.title="Digite o título"
|
||||
AdvSceneSwitcher.action.twitch.marker.description="Descreva o marcador"
|
||||
|
||||
@@ -70,7 +70,6 @@ AdvSceneSwitcher.macroTab.name="Имя:"
|
||||
AdvSceneSwitcher.macroTab.defaultname="Макрос %1"
|
||||
AdvSceneSwitcher.macroTab.copy="Создать копию"
|
||||
; Macro Logic
|
||||
AdvSceneSwitcher.logic.none="Игнорировать вход"
|
||||
AdvSceneSwitcher.logic.and="И"
|
||||
AdvSceneSwitcher.logic.or="Или"
|
||||
AdvSceneSwitcher.logic.andNot="И не"
|
||||
@@ -85,8 +84,6 @@ AdvSceneSwitcher.condition.scene.type.current="Текущий"
|
||||
AdvSceneSwitcher.condition.scene.type.previous="Предыдущий"
|
||||
AdvSceneSwitcher.condition.window="Окно"
|
||||
AdvSceneSwitcher.condition.file="Файл"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.media="Медиа"
|
||||
AdvSceneSwitcher.condition.video="Видео"
|
||||
AdvSceneSwitcher.condition.video.condition.match="точно соответствует"
|
||||
@@ -105,7 +102,7 @@ AdvSceneSwitcher.condition.record.state.start="Запись запущена"
|
||||
AdvSceneSwitcher.condition.record.state.pause="Запись приостановлена"
|
||||
AdvSceneSwitcher.condition.record.state.stop="Запись остановлена"
|
||||
AdvSceneSwitcher.condition.process="Процесс"
|
||||
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}запущен{{focused}}и сфокусирован"
|
||||
AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}запущен{{focused}}и сфокусирован"
|
||||
AdvSceneSwitcher.condition.idle="Простой"
|
||||
AdvSceneSwitcher.condition.idle.entry="Не было ни клавиатуры, ни мыши в течении{{duration}}"
|
||||
AdvSceneSwitcher.condition.pluginState="Состояние плагина"
|
||||
@@ -130,7 +127,6 @@ AdvSceneSwitcher.action.recording.type.start="Начать запись"
|
||||
AdvSceneSwitcher.action.recording.type.pause="Пауза записи"
|
||||
AdvSceneSwitcher.action.recording.type.unpause="Снять запись с паузы"
|
||||
AdvSceneSwitcher.action.recording.pause.hint="Обратите внимание, что в зависимости от настроек записи вы можете не иметь возможности приостановить запись"
|
||||
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
|
||||
AdvSceneSwitcher.action.replay="Буфер воспроизведения"
|
||||
AdvSceneSwitcher.action.replay.type.stop="Остановить буфер воспроизведения"
|
||||
AdvSceneSwitcher.action.replay.type.start="Начать воспроизведение буфера"
|
||||
@@ -138,7 +134,6 @@ AdvSceneSwitcher.action.replay.type.save="Сохранить буфер восп
|
||||
AdvSceneSwitcher.action.streaming="Потоковое вещание"
|
||||
AdvSceneSwitcher.action.streaming.type.stop="Остановить потоковое вещание"
|
||||
AdvSceneSwitcher.action.streaming.type.start="Начать потоковое вещание"
|
||||
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
||||
AdvSceneSwitcher.action.run="Запустить"
|
||||
|
||||
|
||||
|
||||
@@ -78,7 +78,6 @@ AdvSceneSwitcher.macroTab.expandAll="Hepsini Genişlet"
|
||||
AdvSceneSwitcher.macroTab.collapseAll="Hepsini Küçült"
|
||||
|
||||
; Macro Logic
|
||||
AdvSceneSwitcher.logic.none="Girişi yoksay"
|
||||
AdvSceneSwitcher.logic.and="Ve"
|
||||
AdvSceneSwitcher.logic.or="Ya da"
|
||||
AdvSceneSwitcher.logic.andNot="ve değil"
|
||||
@@ -107,9 +106,7 @@ AdvSceneSwitcher.condition.scene.type.notChanged="Sahne değişmedi"
|
||||
AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour="Geçiş hedefi sahnesi için geçiş kontrolü sırasında"
|
||||
AdvSceneSwitcher.condition.window="Pencere"
|
||||
AdvSceneSwitcher.condition.file="Dosya"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="İçerik{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.file.layout="İçerik{{filePath}}{{conditions}}{{regex}}"
|
||||
AdvSceneSwitcher.condition.media="Medya"
|
||||
AdvSceneSwitcher.condition.media.anyOnScene="Herhangi bir medya kaynağı"
|
||||
AdvSceneSwitcher.condition.media.allOnScene="Tüm medya kaynakları "
|
||||
@@ -152,7 +149,7 @@ AdvSceneSwitcher.condition.record.state.start="Kayıt Çalışıyor"
|
||||
AdvSceneSwitcher.condition.record.state.pause="Kayıt durakladı"
|
||||
AdvSceneSwitcher.condition.record.state.stop="Kayıt durdu"
|
||||
AdvSceneSwitcher.condition.process="İşlem"
|
||||
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}çalışıyor{{focused}}ve odaklandı"
|
||||
AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}çalışıyor{{focused}}ve odaklandı"
|
||||
AdvSceneSwitcher.condition.idle="Boşta"
|
||||
AdvSceneSwitcher.condition.idle.entry="...için klavye veya fare girişi yok {{duration}}"
|
||||
AdvSceneSwitcher.condition.pluginState="Eklenti durumu"
|
||||
@@ -265,7 +262,6 @@ AdvSceneSwitcher.action.recording.type.start="Kayıt Başlat"
|
||||
AdvSceneSwitcher.action.recording.type.pause="Kayıt Duraklat"
|
||||
AdvSceneSwitcher.action.recording.type.unpause="Kayıt Duraklatma"
|
||||
AdvSceneSwitcher.action.recording.pause.hint="Kayıt ayarlarınıza bağlı olarak kaydı duraklatamayabileceğinizi unutmayın."
|
||||
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
|
||||
AdvSceneSwitcher.action.replay="Tekrar arabelleği"
|
||||
AdvSceneSwitcher.action.replay.type.stop="Tekrar arabelleğini durdur"
|
||||
AdvSceneSwitcher.action.replay.type.start="Tekrar arabelleğini başlat"
|
||||
@@ -273,7 +269,6 @@ AdvSceneSwitcher.action.replay.type.save="Tekrar arabelleğini kaydet"
|
||||
AdvSceneSwitcher.action.streaming="Yayın"
|
||||
AdvSceneSwitcher.action.streaming.type.stop="Yayın durdur"
|
||||
AdvSceneSwitcher.action.streaming.type.start="Yayın başlat"
|
||||
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
||||
AdvSceneSwitcher.action.run="Çalıştır"
|
||||
AdvSceneSwitcher.action.sceneVisibility="Sahne öğesi görünürlüğü"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.show="Göster"
|
||||
@@ -304,7 +299,7 @@ AdvSceneSwitcher.action.macro="Makro"
|
||||
AdvSceneSwitcher.action.macro.type.pause="Duraklat"
|
||||
AdvSceneSwitcher.action.macro.type.unpause="Duraklatma"
|
||||
AdvSceneSwitcher.action.macro.type.resetCounter="Sayacı sıfırla"
|
||||
AdvSceneSwitcher.action.macro.type.run="Çalıştır"
|
||||
AdvSceneSwitcher.action.macro.type.runActions="Çalıştır"
|
||||
AdvSceneSwitcher.action.pluginState="Eklenti durumu"
|
||||
AdvSceneSwitcher.action.pluginState.type.stop="Advanced Scene Switcher eklentisini durdurun"
|
||||
AdvSceneSwitcher.action.pluginState.type.noMatch="Eşleşmeme davranışını değiştirin:"
|
||||
@@ -341,8 +336,8 @@ AdvSceneSwitcher.action.file.type.write="Yaz"
|
||||
AdvSceneSwitcher.action.file.type.append="Ekle"
|
||||
AdvSceneSwitcher.action.file.entry="{{actions}} {{filePath}}:"
|
||||
AdvSceneSwitcher.action.transition="Geçiş"
|
||||
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Geçiş türünü ayarla {{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Geçiş süresini şuna ayarla: {{duration}}saniyeler"
|
||||
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Geçiş türünü ayarla {{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Geçiş süresini şuna ayarla: {{duration}}saniyeler"
|
||||
AdvSceneSwitcher.action.timer="Zamanlayıcı"
|
||||
AdvSceneSwitcher.action.timer.type.pause="Duraklat"
|
||||
AdvSceneSwitcher.action.timer.type.continue="Devam et"
|
||||
|
||||
@@ -259,7 +259,6 @@ AdvSceneSwitcher.macroList.deleted="删除"
|
||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" 已选择!"
|
||||
|
||||
; Macro Logic
|
||||
AdvSceneSwitcher.logic.none="忽略条件"
|
||||
AdvSceneSwitcher.logic.and="且"
|
||||
AdvSceneSwitcher.logic.or="或"
|
||||
AdvSceneSwitcher.logic.andNot="且不"
|
||||
@@ -318,11 +317,7 @@ AdvSceneSwitcher.condition.file="文件"
|
||||
AdvSceneSwitcher.condition.file.type.match="内容匹配"
|
||||
AdvSceneSwitcher.condition.file.type.contentChange="内容发生改变"
|
||||
AdvSceneSwitcher.condition.file.type.dateChange="修改日期发生改变"
|
||||
AdvSceneSwitcher.condition.file.remote="网络文件"
|
||||
AdvSceneSwitcher.condition.file.local="本地文件"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.file.layout="{{filePath}}{{conditions}}{{regex}}"
|
||||
AdvSceneSwitcher.condition.media="媒体"
|
||||
AdvSceneSwitcher.condition.media.checkType.state="状态匹配"
|
||||
AdvSceneSwitcher.condition.media.checkType.time="时间限制匹配"
|
||||
@@ -428,8 +423,8 @@ AdvSceneSwitcher.condition.record.state.stop="录制停止"
|
||||
AdvSceneSwitcher.condition.record.state.duration="录制时间长于"
|
||||
AdvSceneSwitcher.condition.record.entry="{{condition}}{{duration}}"
|
||||
AdvSceneSwitcher.condition.process="进程"
|
||||
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}为正在运行中{{focused}}且为焦点"
|
||||
AdvSceneSwitcher.condition.process.entry.focus="当前焦点进程: {{focusProcess}}"
|
||||
AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}为正在运行中{{focused}}且为焦点"
|
||||
AdvSceneSwitcher.condition.process.layout.focus="当前焦点进程: {{focusProcess}}"
|
||||
AdvSceneSwitcher.condition.idle="闲置检测"
|
||||
AdvSceneSwitcher.condition.idle.entry="{{duration}}内没有键盘或鼠标输入"
|
||||
AdvSceneSwitcher.condition.pluginState="插件状态"
|
||||
@@ -813,7 +808,6 @@ AdvSceneSwitcher.action.recording.type.changeOutputFolder="更改输出文件夹
|
||||
AdvSceneSwitcher.action.recording.type.changeOutputFileFormat="改变文件名的格式"
|
||||
AdvSceneSwitcher.action.recording.pause.hint="请注意,根据您的录制设置,您可能无法暂停录制"
|
||||
AdvSceneSwitcher.action.recording.split.hint="注意,请先确保在OBS设置中启用自动分割文件!"
|
||||
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
|
||||
AdvSceneSwitcher.action.replay="回放缓存"
|
||||
AdvSceneSwitcher.action.replay.saveWarn="警告:保存过于频繁可能会导致回放缓存实际上未保存!"
|
||||
AdvSceneSwitcher.action.replay.durationWarn="警告:更改回放缓存时长上限,仅适用于下次回放缓存开启时!"
|
||||
@@ -830,7 +824,6 @@ AdvSceneSwitcher.action.streaming.type.server="设置服务器"
|
||||
AdvSceneSwitcher.action.streaming.type.streamKey="设置推流码"
|
||||
AdvSceneSwitcher.action.streaming.type.username="设置用户名"
|
||||
AdvSceneSwitcher.action.streaming.type.password="设置密码"
|
||||
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
|
||||
AdvSceneSwitcher.action.run="运行"
|
||||
AdvSceneSwitcher.action.run.wait.entry="{{wait}}进程退出或最多等待{{timeout}}{{waitHelp}}"
|
||||
AdvSceneSwitcher.action.run.wait.help.tooltip="请注意,如果不勾选此选项,宏属性将不起作用,因为进程的启动会脱离逻辑的其他部分,因此无法对其进行控制."
|
||||
@@ -905,7 +898,7 @@ AdvSceneSwitcher.action.macro="宏"
|
||||
AdvSceneSwitcher.action.macro.type.pause="停用"
|
||||
AdvSceneSwitcher.action.macro.type.unpause="启用"
|
||||
AdvSceneSwitcher.action.macro.type.resetCounter="重置计数器"
|
||||
AdvSceneSwitcher.action.macro.type.run="运行宏"
|
||||
AdvSceneSwitcher.action.macro.type.runActions="运行宏"
|
||||
AdvSceneSwitcher.action.macro.type.run.conditions.ignore="忽略条件结果"
|
||||
AdvSceneSwitcher.action.macro.type.run.conditions.true="仅当条件结果为真时"
|
||||
AdvSceneSwitcher.action.macro.type.run.conditions.false="仅当条件结果为假时"
|
||||
@@ -994,9 +987,9 @@ AdvSceneSwitcher.action.transition.type.scene="场景转场动画"
|
||||
AdvSceneSwitcher.action.transition.type.sceneOverride="覆盖场景转场动画"
|
||||
AdvSceneSwitcher.action.transition.type.sourceShow="显示源转场动画"
|
||||
AdvSceneSwitcher.action.transition.type.sourceHide="隐藏源转场动画"
|
||||
AdvSceneSwitcher.action.transition.entry.line1="更改 {{type}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}将转场动画类型设置为 {{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}将转场动画时长设置为 {{duration}}秒"
|
||||
AdvSceneSwitcher.action.transition.layout.type="更改 {{type}}{{scenes}}{{sources}}"
|
||||
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}将转场动画类型设置为 {{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}将转场动画时长设置为 {{duration}}秒"
|
||||
AdvSceneSwitcher.action.timer="计时器"
|
||||
AdvSceneSwitcher.action.timer.type.pause="暂停"
|
||||
AdvSceneSwitcher.action.timer.type.continue="继续"
|
||||
@@ -1139,10 +1132,17 @@ AdvSceneSwitcher.action.twitch.type.user.getInfo="获取用户信息"
|
||||
AdvSceneSwitcher.action.twitch.type.reward.getInfo="获取频道积分奖励信息"
|
||||
AdvSceneSwitcher.action.twitch.reward.toggleControl="切换奖励名称/变量选择控制"
|
||||
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="在未选择 Twitch 帐户的情况下无法选择类别!"
|
||||
AdvSceneSwitcher.action.twitch.layout.default="{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.default="{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}{{nonModDelayDuration}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.chat="使用账户{{account}}{{actions}}{{channel}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.getInfo="使用账户{{account}}{{actions}}{{userInfoQueryType}}{{userLogin}}{{userId}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.reward.getInfo="使用账户{{account}}{{actions}} 频道{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.channel.getInfo="使用账户{{account}}{{actions}}频道{{channel}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.getInfo.row1="使用账户{{account}}{{actions}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.getInfo.row2="{{userInfoQueryType}}{{userLogin}}{{userId}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.moderation.row1="使用账户{{account}}{{actions}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.moderation.row2="频道{{channel}}{{userInfoQueryType}}{{userLogin}}{{userId}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.ban.row1="使用账户{{account}}{{actions}}超时{{duration}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.user.ban.row2="频道{{channel}}{{userInfoQueryType}}{{userLogin}}{{userId}}原因{{banReason}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.reward.getInfo.row1="使用账户{{account}}{{actions}} 频道{{channel}}"
|
||||
AdvSceneSwitcher.action.twitch.layout.reward.getInfo.row2="{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
|
||||
AdvSceneSwitcher.action.twitch.title.title="输入标题"
|
||||
AdvSceneSwitcher.action.twitch.marker.description="标记描述"
|
||||
AdvSceneSwitcher.action.twitch.clip.hasDelay="在捕捉视频片段前稍加延迟"
|
||||
|
||||
10
data/res/images/DarkVariable.svg
Normal file
10
data/res/images/DarkVariable.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#fefefe" xmlns="http://www.w3.org/2000/svg"
|
||||
width="800px" height="800px" viewBox="0 0 52 52" enable-background="new 0 0 52 52" xml:space="preserve">
|
||||
<path d="M42.6,17.8c2.4,0,7.2-2,7.2-8.4c0-6.4-4.6-6.8-6.1-6.8c-2.8,0-5.6,2-8.1,6.3c-2.5,4.4-5.3,9.1-5.3,9.1
|
||||
l-0.1,0c-0.6-3.1-1.1-5.6-1.3-6.7c-0.5-2.7-3.6-8.4-9.9-8.4c-6.4,0-12.2,3.7-12.2,3.7l0,0C5.8,7.3,5.1,8.5,5.1,9.9
|
||||
c0,2.1,1.7,3.9,3.9,3.9c0.6,0,1.2-0.2,1.7-0.4l0,0c0,0,4.8-2.7,5.9,0c0.3,0.8,0.6,1.7,0.9,2.7c1.2,4.2,2.4,9.1,3.3,13.5l-4.2,6
|
||||
c0,0-4.7-1.7-7.1-1.7s-7.2,2-7.2,8.4s4.6,6.8,6.1,6.8c2.8,0,5.6-2,8.1-6.3c2.5-4.4,5.3-9.1,5.3-9.1c0.8,4,1.5,7.1,1.9,8.5
|
||||
c1.6,4.5,5.3,7.2,10.1,7.2c0,0,5,0,10.9-3.3c1.4-0.6,2.4-2,2.4-3.6c0-2.1-1.7-3.9-3.9-3.9c-0.6,0-1.2,0.2-1.7,0.4l0,0
|
||||
c0,0-4.2,2.4-5.6,0.5c-1-2-1.9-4.6-2.6-7.8c-0.6-2.8-1.3-6.2-2-9.5l4.3-6.2C35.5,16.1,40.2,17.8,42.6,17.8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 973 B |
10
data/res/images/LightVariable.svg
Normal file
10
data/res/images/LightVariable.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#202020" xmlns="http://www.w3.org/2000/svg"
|
||||
width="800px" height="800px" viewBox="0 0 52 52" enable-background="new 0 0 52 52" xml:space="preserve">
|
||||
<path d="M42.6,17.8c2.4,0,7.2-2,7.2-8.4c0-6.4-4.6-6.8-6.1-6.8c-2.8,0-5.6,2-8.1,6.3c-2.5,4.4-5.3,9.1-5.3,9.1
|
||||
l-0.1,0c-0.6-3.1-1.1-5.6-1.3-6.7c-0.5-2.7-3.6-8.4-9.9-8.4c-6.4,0-12.2,3.7-12.2,3.7l0,0C5.8,7.3,5.1,8.5,5.1,9.9
|
||||
c0,2.1,1.7,3.9,3.9,3.9c0.6,0,1.2-0.2,1.7-0.4l0,0c0,0,4.8-2.7,5.9,0c0.3,0.8,0.6,1.7,0.9,2.7c1.2,4.2,2.4,9.1,3.3,13.5l-4.2,6
|
||||
c0,0-4.7-1.7-7.1-1.7s-7.2,2-7.2,8.4s4.6,6.8,6.1,6.8c2.8,0,5.6-2,8.1-6.3c2.5-4.4,5.3-9.1,5.3-9.1c0.8,4,1.5,7.1,1.9,8.5
|
||||
c1.6,4.5,5.3,7.2,10.1,7.2c0,0,5,0,10.9-3.3c1.4-0.6,2.4-2,2.4-3.6c0-2.1-1.7-3.9-3.9-3.9c-0.6,0-1.2,0.2-1.7,0.4l0,0
|
||||
c0,0-4.2,2.4-5.6,0.5c-1-2-1.9-4.6-2.6-7.8c-0.6-2.8-1.3-6.2-2-9.5l4.3-6.2C35.5,16.1,40.2,17.8,42.6,17.8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 973 B |
2
deps/cpp-httplib
vendored
2
deps/cpp-httplib
vendored
Submodule deps/cpp-httplib updated: c7ed1796a7...811dd0b6f2
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
@@ -251,6 +251,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="suppressCrashRecoveryDialog">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.generalTab.generalBehavior.suppressCrashRecoveryDialog</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="settingsLock">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.generalTab.generalBehavior.settingsLock</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -597,7 +611,7 @@
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<widget class="QGroupBox" name="macroListBox">
|
||||
<property name="title">
|
||||
<string>AdvSceneSwitcher.macroTab.macros</string>
|
||||
</property>
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QAction>
|
||||
#include <QDirIterator>
|
||||
#include <QInputDialog>
|
||||
#include <QLibrary>
|
||||
#include <QMainWindow>
|
||||
#include <QMessageBox>
|
||||
#include <QTextStream>
|
||||
#include <regex>
|
||||
|
||||
@@ -41,9 +43,12 @@ AdvSceneSwitcher::AdvSceneSwitcher(QWidget *parent)
|
||||
{
|
||||
switcher->settingsWindowOpened = true;
|
||||
ui->setupUi(this);
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switcher->Prune();
|
||||
LoadUI();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switcher->Prune();
|
||||
LoadUI();
|
||||
}
|
||||
CheckFirstTimeSetup();
|
||||
}
|
||||
|
||||
AdvSceneSwitcher::~AdvSceneSwitcher()
|
||||
@@ -139,7 +144,6 @@ void AdvSceneSwitcher::LoadUI()
|
||||
SetTabOrder(ui->tabWidget);
|
||||
SetCurrentTab(ui->tabWidget);
|
||||
RestoreWindowGeo();
|
||||
CheckFirstTimeSetup();
|
||||
|
||||
loading = false;
|
||||
}
|
||||
@@ -322,8 +326,7 @@ void SwitcherData::SetPreconditions()
|
||||
{
|
||||
// Window title
|
||||
lastTitle = currentTitle;
|
||||
std::string title;
|
||||
GetCurrentWindowTitle(title);
|
||||
auto title = GetCurrentWindowTitle();
|
||||
for (auto &window : ignoreWindowsSwitches) {
|
||||
bool equals = (title == window);
|
||||
bool matches = false;
|
||||
@@ -343,7 +346,7 @@ void SwitcherData::SetPreconditions()
|
||||
currentTitle = title;
|
||||
|
||||
// Process name
|
||||
GetForegroundProcessName(currentForegroundProcess);
|
||||
currentForegroundProcess = GetForegroundProcessName();
|
||||
|
||||
// Macro
|
||||
InvalidateMacroTempVarValues();
|
||||
@@ -507,6 +510,8 @@ bool SwitcherData::AnySceneTransitionStarted()
|
||||
******************************************************************************/
|
||||
extern "C" EXPORT void FreeSceneSwitcher()
|
||||
{
|
||||
switcher->Stop();
|
||||
|
||||
PlatformCleanup();
|
||||
RunPluginCleanupSteps();
|
||||
|
||||
@@ -628,13 +633,20 @@ static void handleSceneCollectionCleanup()
|
||||
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);
|
||||
}
|
||||
|
||||
// Note to future self:
|
||||
// be careful using switcher->m here as there is potential for deadlocks when using
|
||||
// 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) {
|
||||
return;
|
||||
@@ -708,19 +720,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()
|
||||
{
|
||||
if (switcher->settingsWindowOpened) {
|
||||
AdvSceneSwitcher::window->show();
|
||||
AdvSceneSwitcher::window->raise();
|
||||
AdvSceneSwitcher::window->activateWindow();
|
||||
} else {
|
||||
AdvSceneSwitcher::window =
|
||||
new AdvSceneSwitcher(static_cast<QMainWindow *>(
|
||||
obs_frontend_get_main_window()));
|
||||
AdvSceneSwitcher::window->setAttribute(Qt::WA_DeleteOnClose);
|
||||
AdvSceneSwitcher::window->show();
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -768,8 +815,6 @@ void HighlightMacroSettingsButton(bool enable)
|
||||
enable);
|
||||
}
|
||||
|
||||
void SetupActionQueues();
|
||||
|
||||
extern "C" EXPORT void InitSceneSwitcher(obs_module_t *module,
|
||||
translateFunc translate)
|
||||
{
|
||||
@@ -781,12 +826,11 @@ extern "C" EXPORT void InitSceneSwitcher(obs_module_t *module,
|
||||
PlatformInit();
|
||||
LoadPlugins();
|
||||
SetupDock();
|
||||
SetupActionQueues();
|
||||
|
||||
RunPluginInitSteps();
|
||||
|
||||
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(
|
||||
obs_module_text("AdvSceneSwitcher.pluginName"));
|
||||
|
||||
@@ -66,7 +66,9 @@ public slots:
|
||||
void on_uiHintsDisable_stateChanged(int state);
|
||||
void on_disableComboBoxFilter_stateChanged(int state);
|
||||
void on_warnPluginLoadFailure_stateChanged(int state);
|
||||
void on_suppressCrashRecoveryDialog_stateChanged(int state);
|
||||
void on_hideLegacyTabs_stateChanged(int state);
|
||||
void on_settingsLock_stateChanged(int state);
|
||||
void on_priorityUp_clicked();
|
||||
void on_priorityDown_clicked();
|
||||
void on_threadPriority_currentTextChanged(const QString &text);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "advanced-scene-switcher.hpp"
|
||||
#include "crash-handler.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "filter-combo-box.hpp"
|
||||
#include "first-run-wizard.hpp"
|
||||
@@ -18,6 +19,8 @@
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QFileDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
@@ -194,6 +197,15 @@ void AdvSceneSwitcher::on_warnPluginLoadFailure_stateChanged(int state)
|
||||
switcher->warnPluginLoadFailure = state;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_suppressCrashRecoveryDialog_stateChanged(int state)
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetSuppressCrashDialog(state);
|
||||
}
|
||||
|
||||
static bool isLegacyTab(const QString &name)
|
||||
{
|
||||
return name == obs_module_text(
|
||||
@@ -233,6 +245,64 @@ void AdvSceneSwitcher::on_hideLegacyTabs_stateChanged(int state)
|
||||
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()
|
||||
{
|
||||
QString toolTip =
|
||||
@@ -381,12 +451,16 @@ void AdvSceneSwitcher::CheckFirstTimeSetup()
|
||||
return;
|
||||
}
|
||||
|
||||
auto macro = FirstRunWizard::ShowWizard(this);
|
||||
bool wasSkipped = false;
|
||||
auto macro = FirstRunWizard::ShowWizard(this, &wasSkipped);
|
||||
if (macro) {
|
||||
renameMacroIfNecessary(macro);
|
||||
QTimer::singleShot(0, this,
|
||||
[this, macro]() { ui->macros->Add(macro); });
|
||||
}
|
||||
if (wasSkipped) {
|
||||
ui->alwaysShowFeatureTabs->setChecked(true);
|
||||
}
|
||||
switcher->Start();
|
||||
}
|
||||
|
||||
@@ -458,7 +532,6 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
|
||||
// Needs to be loaded before any entries which might rely on scene group
|
||||
// selections to be available.
|
||||
loadSceneGroups(obj);
|
||||
LoadVariables(obj);
|
||||
|
||||
RunLoadSteps(obj);
|
||||
|
||||
@@ -497,7 +570,6 @@ void SwitcherData::SaveSettings(obs_data_t *obj)
|
||||
saveSceneGroups(obj);
|
||||
SaveMacros(obj);
|
||||
SaveGlobalMacroSettings(obj);
|
||||
SaveVariables(obj);
|
||||
saveWindowTitleSwitches(obj);
|
||||
saveScreenRegionSwitches(obj);
|
||||
savePauseSwitches(obj);
|
||||
@@ -555,6 +627,9 @@ void SwitcherData::SaveGeneralSettings(obs_data_t *obj)
|
||||
disableFilterComboboxFilter);
|
||||
obs_data_set_bool(obj, "warnPluginLoadFailure", warnPluginLoadFailure);
|
||||
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);
|
||||
|
||||
@@ -626,6 +701,8 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
|
||||
warnPluginLoadFailure = obs_data_get_bool(obj, "warnPluginLoadFailure");
|
||||
obs_data_set_default_bool(obj, "hideLegacyTabs", true);
|
||||
hideLegacyTabs = obs_data_get_bool(obj, "hideLegacyTabs");
|
||||
settingsLockEnabled = obs_data_get_bool(obj, "settingsLockEnabled");
|
||||
settingsLockPassword = obs_data_get_string(obj, "settingsLockPassword");
|
||||
|
||||
SetDefaultFunctionPriorities(obj);
|
||||
LoadFunctionPriorities(obj, functionNamesByPriority);
|
||||
@@ -946,7 +1023,9 @@ void AdvSceneSwitcher::SetupGeneralTab()
|
||||
FilterComboBox::SetFilterBehaviourEnabled(
|
||||
!switcher->disableFilterComboboxFilter);
|
||||
ui->warnPluginLoadFailure->setChecked(switcher->warnPluginLoadFailure);
|
||||
ui->suppressCrashRecoveryDialog->setChecked(GetSuppressCrashDialog());
|
||||
ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs);
|
||||
ui->settingsLock->setChecked(switcher->settingsLockEnabled);
|
||||
|
||||
populatePriorityFunctionList(ui->priorityList);
|
||||
populateThreadPriorityList(ui->threadPriority);
|
||||
|
||||
@@ -92,12 +92,11 @@ bool SwitcherData::checkExeSwitch(OBSWeakSource &scene,
|
||||
}
|
||||
|
||||
std::string title = switcher->currentTitle;
|
||||
QStringList runningProcesses;
|
||||
bool ignored = false;
|
||||
bool match = false;
|
||||
|
||||
// Check for match
|
||||
GetProcessList(runningProcesses);
|
||||
const auto runningProcesses = GetProcessList();
|
||||
for (ExecutableSwitch &s : executableSwitches) {
|
||||
if (!s.initialized()) {
|
||||
continue;
|
||||
|
||||
@@ -178,47 +178,19 @@ void AdvSceneSwitcher::on_ignoreWindows_currentRowChanged(int idx)
|
||||
}
|
||||
}
|
||||
|
||||
void checkWindowTitleSwitchDirect(WindowSwitch &s,
|
||||
std::string ¤tWindowTitle, bool &match,
|
||||
OBSWeakSource &scene,
|
||||
OBSWeakSource &transition)
|
||||
static bool windowInfoMatchesSwitch(const WindowInfo &info,
|
||||
const WindowSwitch &s)
|
||||
{
|
||||
bool focus = (!s.focus || s.window == currentWindowTitle);
|
||||
bool fullscreen = (!s.fullscreen || IsFullscreen(s.window));
|
||||
bool max = (!s.maximized || IsMaximized(s.window));
|
||||
|
||||
if (focus && fullscreen && max) {
|
||||
match = true;
|
||||
scene = s.getScene();
|
||||
transition = s.transition;
|
||||
if (s.focus && !info.focused) {
|
||||
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.fullscreen && !info.fullscreen) {
|
||||
return false;
|
||||
}
|
||||
if (s.maximized && !info.maximized) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SwitcherData::checkWindowTitleSwitch(OBSWeakSource &scene,
|
||||
@@ -228,24 +200,37 @@ bool SwitcherData::checkWindowTitleSwitch(OBSWeakSource &scene,
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string currentWindowTitle = switcher->currentTitle;
|
||||
bool match = false;
|
||||
std::vector<std::string> windowList;
|
||||
GetWindowList(windowList);
|
||||
|
||||
WindowQueryOptions options;
|
||||
options.focus = true;
|
||||
options.fullscreen = true;
|
||||
options.maximized = true;
|
||||
const auto windows = GetWindows(options);
|
||||
|
||||
for (WindowSwitch &s : windowSwitches) {
|
||||
if (!s.initialized()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::find(windowList.begin(), windowList.end(), s.window) !=
|
||||
windowList.end()) {
|
||||
checkWindowTitleSwitchDirect(s, currentWindowTitle,
|
||||
match, scene, transition);
|
||||
} else {
|
||||
checkWindowTitleSwitchRegex(s, currentWindowTitle,
|
||||
windowList, match, scene,
|
||||
transition);
|
||||
for (const auto &info : windows) {
|
||||
bool titleMatch = false;
|
||||
try {
|
||||
std::regex expr(s.window);
|
||||
titleMatch = info.title == s.window ||
|
||||
std::regex_match(info.title, expr);
|
||||
} catch (const std::regex_error &) {
|
||||
titleMatch = info.title == s.window;
|
||||
}
|
||||
|
||||
if (!titleMatch || !windowInfoMatchesSwitch(info, s)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match = true;
|
||||
scene = s.getScene();
|
||||
transition = s.transition;
|
||||
break;
|
||||
}
|
||||
|
||||
if (match) {
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
#endif
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <climits>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include "kwin-helpers.h"
|
||||
|
||||
namespace advss {
|
||||
@@ -233,28 +236,65 @@ std::string getWindowName(Window window)
|
||||
return windowTitle;
|
||||
}
|
||||
|
||||
void GetWindowList(std::vector<std::string> &windows)
|
||||
std::vector<WindowInfo> GetWindows(const WindowQueryOptions &options)
|
||||
{
|
||||
windows.resize(0);
|
||||
for (auto window : getTopLevelWindows()) {
|
||||
auto name = getWindowName(window);
|
||||
if (name.empty()) {
|
||||
continue;
|
||||
}
|
||||
windows.emplace_back(name);
|
||||
}
|
||||
}
|
||||
const std::string foregroundTitle = GetCurrentWindowTitle();
|
||||
const auto topLevel = getTopLevelWindows();
|
||||
auto display = disp();
|
||||
|
||||
void GetWindowList(QStringList &windows)
|
||||
{
|
||||
windows.clear();
|
||||
for (auto window : getTopLevelWindows()) {
|
||||
std::vector<WindowInfo> result;
|
||||
result.reserve(topLevel.size());
|
||||
|
||||
for (auto window : topLevel) {
|
||||
auto name = getWindowName(window);
|
||||
if (name.empty()) {
|
||||
continue;
|
||||
}
|
||||
windows << QString::fromStdString(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 result;
|
||||
}
|
||||
|
||||
int getActiveWindow(Window *&window)
|
||||
@@ -278,91 +318,24 @@ int getActiveWindow(Window *&window)
|
||||
&bytes, (uint8_t **)&window);
|
||||
}
|
||||
|
||||
void GetCurrentWindowTitle(std::string &title)
|
||||
std::string GetCurrentWindowTitle()
|
||||
{
|
||||
if (KWin) {
|
||||
title = FocusNotifier::getActiveWindowTitle();
|
||||
return;
|
||||
return FocusNotifier::getActiveWindowTitle();
|
||||
}
|
||||
|
||||
Window *data = 0;
|
||||
if (getActiveWindow(data) != Success || !data) {
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
if (!data[0]) {
|
||||
XFree(data);
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto name = getWindowName(data[0]);
|
||||
XFree(data);
|
||||
|
||||
if (name.empty()) {
|
||||
return;
|
||||
}
|
||||
title = 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 {};
|
||||
return name;
|
||||
}
|
||||
|
||||
static void getProcessListProcps(QStringList &processes)
|
||||
@@ -409,16 +382,17 @@ static void getProcessListProcps2(QStringList &processes)
|
||||
#endif
|
||||
}
|
||||
|
||||
void GetProcessList(QStringList &processes)
|
||||
QStringList GetProcessList()
|
||||
{
|
||||
processes.clear();
|
||||
QStringList processes;
|
||||
if (libprocpsSupported) {
|
||||
getProcessListProcps(processes);
|
||||
return;
|
||||
return processes;
|
||||
}
|
||||
if (libprocps2Supported) {
|
||||
getProcessListProcps2(processes);
|
||||
}
|
||||
return processes;
|
||||
}
|
||||
|
||||
long getForegroundProcessPid()
|
||||
@@ -469,24 +443,83 @@ std::string getProcNameFromPid(long pid)
|
||||
return name;
|
||||
}
|
||||
|
||||
void GetForegroundProcessName(QString &proc)
|
||||
std::string GetForegroundProcessName()
|
||||
{
|
||||
std::string temp;
|
||||
GetForegroundProcessName(temp);
|
||||
proc = QString::fromStdString(temp);
|
||||
auto pid = getForegroundProcessPid();
|
||||
return getProcNameFromPid(pid);
|
||||
}
|
||||
|
||||
void GetForegroundProcessName(std::string &proc)
|
||||
static std::string getProcessPathFromPid(long pid)
|
||||
{
|
||||
std::string linkPath = "/proc/" + std::to_string(pid) + "/exe";
|
||||
char buf[PATH_MAX];
|
||||
ssize_t len = readlink(linkPath.c_str(), buf, sizeof(buf) - 1);
|
||||
if (len <= 0) {
|
||||
return {};
|
||||
}
|
||||
buf[len] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string GetForegroundProcessPath()
|
||||
{
|
||||
proc.resize(0);
|
||||
auto pid = getForegroundProcessPid();
|
||||
proc = getProcNameFromPid(pid);
|
||||
if (pid <= 0) {
|
||||
return {};
|
||||
}
|
||||
return getProcessPathFromPid(pid);
|
||||
}
|
||||
|
||||
QStringList GetProcessPathsFromName(const QString &name)
|
||||
{
|
||||
QStringList paths;
|
||||
const std::string nameStr = name.toStdString();
|
||||
DIR *procDir = opendir("/proc");
|
||||
if (!procDir) {
|
||||
return paths;
|
||||
}
|
||||
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(procDir)) != nullptr) {
|
||||
bool isPid = (entry->d_name[0] != '\0');
|
||||
for (const char *c = entry->d_name; *c; c++) {
|
||||
if (!isdigit(*c)) {
|
||||
isPid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isPid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string pid = entry->d_name;
|
||||
std::string commPath = "/proc/" + pid + "/comm";
|
||||
std::ifstream commFile(commPath);
|
||||
if (!commFile) {
|
||||
continue;
|
||||
}
|
||||
std::string comm;
|
||||
std::getline(commFile, comm);
|
||||
if (comm != nameStr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string path = getProcessPathFromPid(std::stol(pid));
|
||||
if (path.empty()) {
|
||||
continue;
|
||||
}
|
||||
QString qPath = QString::fromStdString(path);
|
||||
if (!paths.contains(qPath)) {
|
||||
paths.append(qPath);
|
||||
}
|
||||
}
|
||||
closedir(procDir);
|
||||
return paths;
|
||||
}
|
||||
|
||||
bool IsInFocus(const QString &executable)
|
||||
{
|
||||
std::string current;
|
||||
GetForegroundProcessName(current);
|
||||
const auto current = GetForegroundProcessName();
|
||||
|
||||
// True if executable switch equals current window
|
||||
bool equals = (executable.toStdString() == current);
|
||||
|
||||
@@ -49,10 +49,12 @@ MacroActionEdit::MacroActionEdit(QWidget *parent,
|
||||
_section->AddHeaderWidget(_enable);
|
||||
_section->AddHeaderWidget(_actionSelection);
|
||||
_section->AddHeaderWidget(_headerInfo);
|
||||
_section->AddHeaderWidget(_varMappingToggle);
|
||||
|
||||
auto actionLayout = new QVBoxLayout;
|
||||
actionLayout->setContentsMargins({7, 7, 7, 7});
|
||||
actionLayout->addWidget(_section);
|
||||
actionLayout->addWidget(_outputMappings);
|
||||
_contentLayout->addLayout(actionLayout);
|
||||
|
||||
auto mainLayout = new QHBoxLayout;
|
||||
@@ -61,7 +63,6 @@ MacroActionEdit::MacroActionEdit(QWidget *parent,
|
||||
mainLayout->addWidget(_frame);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
SetupWidgets(true);
|
||||
|
||||
actionStateTimer->start(300);
|
||||
@@ -90,7 +91,10 @@ void MacroActionEdit::SetupWidgets(bool basicSetup)
|
||||
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
|
||||
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
|
||||
this, SLOT(HeaderInfoChanged(const QString &)));
|
||||
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
|
||||
SLOT(ShowVariableMappings(bool)));
|
||||
_section->SetContent(widget, (*_entryData)->GetCollapsed());
|
||||
SetupVarMappings((*_entryData).get());
|
||||
SetFocusPolicyOfWidgets();
|
||||
|
||||
_allWidgetsAreSetup = true;
|
||||
@@ -121,7 +125,10 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
|
||||
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
|
||||
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
|
||||
this, SLOT(HeaderInfoChanged(const QString &)));
|
||||
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
|
||||
SLOT(ShowVariableMappings(bool)));
|
||||
_section->SetContent(widget);
|
||||
SetupVarMappings((*_entryData).get());
|
||||
SetFocusPolicyOfWidgets();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "macro.hpp"
|
||||
#include "macro-action-factory.hpp"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionMacro::id = "macro";
|
||||
@@ -118,6 +120,45 @@ bool MacroActionMacro::PerformAction()
|
||||
case Action::TOGGLE_ACTION:
|
||||
AdjustActionState(macro);
|
||||
break;
|
||||
case Action::RUN_MACRO: {
|
||||
if (_runOptions.skipWhenPaused && macro->Paused()) {
|
||||
break;
|
||||
}
|
||||
const bool conditionsMatched =
|
||||
macro->CheckConditions(true);
|
||||
macro->PerformActions(conditionsMatched, false, true);
|
||||
break;
|
||||
}
|
||||
case Action::GET_INFO: {
|
||||
SetTempVarValue(
|
||||
"conditionCount",
|
||||
std::to_string(macro->Conditions().size()));
|
||||
SetTempVarValue(
|
||||
"actionCount",
|
||||
std::to_string(macro->Actions().size()));
|
||||
SetTempVarValue(
|
||||
"elseActionCount",
|
||||
std::to_string(macro->ElseActions().size()));
|
||||
SetTempVarValue("paused",
|
||||
macro->Paused() ? "true" : "false");
|
||||
SetTempVarValue("runCount",
|
||||
std::to_string(macro->RunCount()));
|
||||
const auto lastRun = macro->GetLastExecutionTime();
|
||||
const bool hasRun =
|
||||
lastRun.time_since_epoch().count() != 0;
|
||||
const auto secondsSinceLastRun =
|
||||
hasRun ? std::chrono::duration_cast<
|
||||
std::chrono::seconds>(
|
||||
std::chrono::
|
||||
high_resolution_clock::
|
||||
now() -
|
||||
lastRun)
|
||||
.count()
|
||||
: -1;
|
||||
SetTempVarValue("secondsSinceLastRun",
|
||||
std::to_string(secondsSinceLastRun));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -175,6 +216,12 @@ void MacroActionMacro::LogAction() const
|
||||
case Action::NESTED_MACRO:
|
||||
ablog(LOG_INFO, "run nested macro");
|
||||
break;
|
||||
case Action::GET_INFO:
|
||||
ablog(LOG_INFO, "get info for \"%s\"", macro->Name().c_str());
|
||||
break;
|
||||
case Action::RUN_MACRO:
|
||||
ablog(LOG_INFO, "run macro \"%s\"", macro->Name().c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -202,8 +249,8 @@ bool MacroActionMacro::Save(obs_data_t *obj) const
|
||||
bool MacroActionMacro::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_action = static_cast<MacroActionMacro::Action>(
|
||||
obs_data_get_int(obj, "action"));
|
||||
SetAction(static_cast<MacroActionMacro::Action>(
|
||||
obs_data_get_int(obj, "action")));
|
||||
_macro.Load(obj);
|
||||
_actionSelectionType = static_cast<SelectionType>(
|
||||
obs_data_get_int(obj, "actionSelectionType"));
|
||||
@@ -303,6 +350,56 @@ void MacroActionMacro::RunActions(Macro *actionMacro) const
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionMacro::SetAction(Action action)
|
||||
{
|
||||
_action = action;
|
||||
SetupTempVars();
|
||||
}
|
||||
|
||||
void MacroActionMacro::SetupTempVars()
|
||||
{
|
||||
MacroAction::SetupTempVars();
|
||||
|
||||
if (_action != Action::GET_INFO) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddTempvar(
|
||||
"conditionCount",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.macro.info.conditionCount"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.macro.info.conditionCount.description"));
|
||||
AddTempvar(
|
||||
"actionCount",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.macro.info.actionCount"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.macro.info.actionCount.description"));
|
||||
AddTempvar(
|
||||
"elseActionCount",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.macro.info.elseActionCount"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.macro.info.elseActionCount.description"));
|
||||
AddTempvar(
|
||||
"paused",
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.macro.info.paused"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.macro.info.paused.description"));
|
||||
AddTempvar(
|
||||
"runCount",
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.macro.info.runCount"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.macro.info.runCount.description"));
|
||||
AddTempvar(
|
||||
"secondsSinceLastRun",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.macro.info.secondsSinceLastRun"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.macro.info.secondsSinceLastRun.description"));
|
||||
}
|
||||
|
||||
static void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
static const std::vector<std::pair<MacroActionMacro::Action, std::string>>
|
||||
@@ -318,7 +415,9 @@ static void populateActionSelection(QComboBox *list)
|
||||
{MacroActionMacro::Action::NESTED_MACRO,
|
||||
"AdvSceneSwitcher.action.macro.type.nestedMacro"},
|
||||
{MacroActionMacro::Action::RUN_ACTIONS,
|
||||
"AdvSceneSwitcher.action.macro.type.run"},
|
||||
"AdvSceneSwitcher.action.macro.type.runActions"},
|
||||
{MacroActionMacro::Action::RUN_MACRO,
|
||||
"AdvSceneSwitcher.action.macro.type.runMacro"},
|
||||
{MacroActionMacro::Action::STOP,
|
||||
"AdvSceneSwitcher.action.macro.type.stop"},
|
||||
{MacroActionMacro::Action::DISABLE_ACTION,
|
||||
@@ -327,6 +426,8 @@ static void populateActionSelection(QComboBox *list)
|
||||
"AdvSceneSwitcher.action.macro.type.enableAction"},
|
||||
{MacroActionMacro::Action::TOGGLE_ACTION,
|
||||
"AdvSceneSwitcher.action.macro.type.toggleAction"},
|
||||
{MacroActionMacro::Action::GET_INFO,
|
||||
"AdvSceneSwitcher.action.macro.type.getInfo"},
|
||||
};
|
||||
|
||||
for (const auto &[value, name] : actions) {
|
||||
@@ -396,6 +497,10 @@ MacroActionMacroEdit::MacroActionMacroEdit(
|
||||
_actionSections(new QComboBox(this)),
|
||||
_skipWhenPaused(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.action.macro.type.run.skipWhenPaused"))),
|
||||
_noConditionsWarning(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.action.macro.type.runMacro.noConditionsWarning"))),
|
||||
_runMacroHelp(new HelpIcon(obs_module_text(
|
||||
"AdvSceneSwitcher.action.macro.type.runMacro.help"))),
|
||||
_setInputs(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.action.macro.type.run.setInputs"))),
|
||||
_inputs(new MacroInputEdit()),
|
||||
@@ -469,6 +574,7 @@ MacroActionMacroEdit::MacroActionMacroEdit(
|
||||
layout->addLayout(_setInputsLayout);
|
||||
layout->addWidget(_inputs);
|
||||
layout->addWidget(_skipWhenPaused);
|
||||
layout->addWidget(_noConditionsWarning);
|
||||
layout->addWidget(_nestedMacro);
|
||||
setLayout(layout);
|
||||
_entryData = entryData;
|
||||
@@ -496,7 +602,7 @@ void MacroActionMacroEdit::UpdateEntryData()
|
||||
}
|
||||
|
||||
_actions->setCurrentIndex(
|
||||
_actions->findData(static_cast<int>(_entryData->_action)));
|
||||
_actions->findData(static_cast<int>(_entryData->GetAction())));
|
||||
_actionSelectionType->setCurrentIndex(_actionSelectionType->findData(
|
||||
static_cast<int>(_entryData->_actionSelectionType)));
|
||||
_actionIndex->SetValue(_entryData->_actionIndex);
|
||||
@@ -545,8 +651,8 @@ void MacroActionMacroEdit::MacroChanged(const QString &text)
|
||||
void MacroActionMacroEdit::ActionChanged(int idx)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_action = static_cast<MacroActionMacro::Action>(
|
||||
_actions->itemData(idx).toInt());
|
||||
_entryData->SetAction(static_cast<MacroActionMacro::Action>(
|
||||
_actions->itemData(idx).toInt()));
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
@@ -638,6 +744,7 @@ void MacroActionMacroEdit::InputsChanged(const StringList &inputs)
|
||||
void MacroActionMacroEdit::SetWidgetVisibility()
|
||||
{
|
||||
_entryLayout->removeWidget(_actions);
|
||||
_entryLayout->removeWidget(_runMacroHelp);
|
||||
_entryLayout->removeWidget(_actionIndex);
|
||||
_entryLayout->removeWidget(_macros);
|
||||
_entryLayout->removeWidget(_actionSections);
|
||||
@@ -653,6 +760,7 @@ void MacroActionMacroEdit::SetWidgetVisibility()
|
||||
|
||||
const std::unordered_map<std::string, QWidget *> placeholders = {
|
||||
{"{{actions}}", _actions},
|
||||
{"{{runMacroHelp}}", _runMacroHelp},
|
||||
{"{{actionIndex}}", _actionIndex},
|
||||
{"{{macros}}", _macros},
|
||||
{"{{actionSections}}", _actionSections},
|
||||
@@ -665,7 +773,7 @@ void MacroActionMacroEdit::SetWidgetVisibility()
|
||||
|
||||
};
|
||||
|
||||
const auto action = _entryData->_action;
|
||||
const auto action = _entryData->GetAction();
|
||||
const char *layoutText = "";
|
||||
switch (action) {
|
||||
case MacroActionMacro::Action::PAUSE:
|
||||
@@ -674,8 +782,12 @@ void MacroActionMacroEdit::SetWidgetVisibility()
|
||||
case MacroActionMacro::Action::RESET_COUNTER:
|
||||
case MacroActionMacro::Action::STOP:
|
||||
case MacroActionMacro::Action::NESTED_MACRO:
|
||||
case MacroActionMacro::Action::GET_INFO:
|
||||
layoutText = "AdvSceneSwitcher.action.macro.layout.other";
|
||||
break;
|
||||
case MacroActionMacro::Action::RUN_MACRO:
|
||||
layoutText = "AdvSceneSwitcher.action.macro.layout.runMacro";
|
||||
break;
|
||||
case MacroActionMacro::Action::RUN_ACTIONS:
|
||||
layoutText = "AdvSceneSwitcher.action.macro.layout.run";
|
||||
break;
|
||||
@@ -702,7 +814,8 @@ void MacroActionMacroEdit::SetWidgetVisibility()
|
||||
}
|
||||
|
||||
if (action == MacroActionMacro::Action::RUN_ACTIONS ||
|
||||
action == MacroActionMacro::Action::STOP) {
|
||||
action == MacroActionMacro::Action::STOP ||
|
||||
action == MacroActionMacro::Action::RUN_MACRO) {
|
||||
_macros->HideSelectedMacro();
|
||||
} else {
|
||||
_macros->ShowAllMacros();
|
||||
@@ -747,8 +860,19 @@ void MacroActionMacroEdit::SetWidgetVisibility()
|
||||
_actionSections->setVisible(
|
||||
action == MacroActionMacro::Action::RUN_ACTIONS ||
|
||||
isModifyingActionState);
|
||||
_skipWhenPaused->setVisible(action ==
|
||||
MacroActionMacro::Action::RUN_ACTIONS);
|
||||
_skipWhenPaused->setVisible(
|
||||
action == MacroActionMacro::Action::RUN_ACTIONS ||
|
||||
action == MacroActionMacro::Action::RUN_MACRO);
|
||||
|
||||
if (action == MacroActionMacro::Action::RUN_MACRO) {
|
||||
auto macro = _entryData->_macro.GetMacro();
|
||||
_noConditionsWarning->setVisible(!macro ||
|
||||
macro->Conditions().empty());
|
||||
} else {
|
||||
_noConditionsWarning->setVisible(false);
|
||||
}
|
||||
_runMacroHelp->setVisible(action ==
|
||||
MacroActionMacro::Action::RUN_MACRO);
|
||||
|
||||
_nestedMacro->setVisible(action ==
|
||||
MacroActionMacro::Action::NESTED_MACRO);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
|
||||
namespace advss {
|
||||
|
||||
@@ -56,11 +57,15 @@ public:
|
||||
TOGGLE_ACTION,
|
||||
TOGGLE_PAUSE,
|
||||
NESTED_MACRO,
|
||||
GET_INFO,
|
||||
RUN_MACRO,
|
||||
};
|
||||
|
||||
void SetAction(Action);
|
||||
Action GetAction() const { return _action; }
|
||||
|
||||
enum class SelectionType { INDEX, LABEL, ID };
|
||||
|
||||
Action _action = Action::NESTED_MACRO;
|
||||
SelectionType _actionSelectionType = SelectionType::INDEX;
|
||||
bool _useElseSection = false;
|
||||
IntVariable _actionIndex = 1;
|
||||
@@ -72,9 +77,12 @@ public:
|
||||
int _customWidgetHeight = 0;
|
||||
|
||||
private:
|
||||
void SetupTempVars();
|
||||
void RunActions(Macro *actionMacro) const;
|
||||
void AdjustActionState(Macro *) const;
|
||||
|
||||
Action _action = Action::NESTED_MACRO;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
@@ -125,6 +133,8 @@ private:
|
||||
QCheckBox *_reevaluateConditionState;
|
||||
QComboBox *_actionSections;
|
||||
QCheckBox *_skipWhenPaused;
|
||||
QLabel *_noConditionsWarning;
|
||||
HelpIcon *_runMacroHelp;
|
||||
QCheckBox *_setInputs;
|
||||
MacroInputEdit *_inputs;
|
||||
QHBoxLayout *_entryLayout;
|
||||
|
||||
@@ -17,6 +17,15 @@ namespace advss {
|
||||
|
||||
const std::string MacroActionVariable::id = "variable";
|
||||
|
||||
std::vector<TempVariableRef> MacroActionVariable::GetTempVarRefs() const
|
||||
{
|
||||
auto refs = MacroSegment::GetTempVarRefs();
|
||||
if (_tempVar.HasValidID()) {
|
||||
refs.push_back(_tempVar);
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
bool MacroActionVariable::_registered = MacroActionFactory::Register(
|
||||
MacroActionVariable::id,
|
||||
{MacroActionVariable::Create, MacroActionVariableEdit::Create,
|
||||
@@ -1450,6 +1459,7 @@ void MacroActionVariableEdit::SelectionChanged(const TempVariableRef &var)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_tempVar = var;
|
||||
IncrementTempVarInUseGeneration();
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ public:
|
||||
bool PostLoad();
|
||||
std::string GetShortDesc() const;
|
||||
std::string GetId() const { return id; };
|
||||
std::vector<TempVariableRef> GetTempVarRefs() const;
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m);
|
||||
std::shared_ptr<MacroAction> Copy() const;
|
||||
void SetSegmentIndexValue(int);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "advanced-scene-switcher.hpp"
|
||||
#include "macro-signals.hpp"
|
||||
#include "macro-settings.hpp"
|
||||
#include "macro.hpp"
|
||||
#include "path-helpers.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "section.hpp"
|
||||
#include "switch-button.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
@@ -102,12 +104,15 @@ MacroConditionEdit::MacroConditionEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroCondition> *entryData,
|
||||
bool isRootCondition)
|
||||
: MacroSegmentEdit(parent),
|
||||
_enable(new SwitchButton()),
|
||||
_logicSelection(new QComboBox()),
|
||||
_conditionSelection(new FilterComboBox()),
|
||||
_dur(new DurationModifierEdit()),
|
||||
_entryData(entryData),
|
||||
_isRoot(isRootCondition)
|
||||
{
|
||||
QWidget::connect(_enable, SIGNAL(checked(bool)), this,
|
||||
SLOT(ConditionEnableChanged(bool)));
|
||||
QWidget::connect(_logicSelection, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(LogicSelectionChanged(int)));
|
||||
QWidget::connect(_conditionSelection,
|
||||
@@ -122,14 +127,17 @@ MacroConditionEdit::MacroConditionEdit(
|
||||
Logic::PopulateLogicTypeSelection(_logicSelection, isRootCondition);
|
||||
populateConditionSelection(_conditionSelection);
|
||||
|
||||
_section->AddHeaderWidget(_enable);
|
||||
_section->AddHeaderWidget(_logicSelection);
|
||||
_section->AddHeaderWidget(_conditionSelection);
|
||||
_section->AddHeaderWidget(_headerInfo);
|
||||
_section->AddHeaderWidget(_dur);
|
||||
_section->AddHeaderWidget(_varMappingToggle);
|
||||
|
||||
QVBoxLayout *conditionLayout = new QVBoxLayout;
|
||||
conditionLayout->setContentsMargins({7, 7, 7, 7});
|
||||
conditionLayout->addWidget(_section);
|
||||
conditionLayout->addWidget(_outputMappings);
|
||||
_contentLayout->addLayout(conditionLayout);
|
||||
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
@@ -152,8 +160,17 @@ void MacroConditionEdit::LogicSelectionChanged(int idx)
|
||||
const auto logic = static_cast<Logic::Type>(
|
||||
_logicSelection->itemData(idx).toInt());
|
||||
(*_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
|
||||
@@ -166,7 +183,9 @@ void MacroConditionEdit::SetLogicSelection()
|
||||
const auto logic = (*_entryData)->GetLogicType();
|
||||
_logicSelection->setCurrentIndex(
|
||||
_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)
|
||||
@@ -204,7 +223,10 @@ void MacroConditionEdit::SetupWidgets(bool basicSetup)
|
||||
MacroConditionFactory::CreateWidget(id, this, *_entryData);
|
||||
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
|
||||
this, SLOT(HeaderInfoChanged(const QString &)));
|
||||
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
|
||||
SLOT(ShowVariableMappings(bool)));
|
||||
_section->SetContent(widget, (*_entryData)->GetCollapsed());
|
||||
SetupVarMappings((*_entryData).get());
|
||||
SetFocusPolicyOfWidgets();
|
||||
|
||||
_allWidgetsAreSetup = true;
|
||||
@@ -234,10 +256,12 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
|
||||
{
|
||||
auto lock = LockContext();
|
||||
auto logic = (*_entryData)->GetLogicType();
|
||||
const bool enabled = (*_entryData)->Enabled();
|
||||
_entryData->reset();
|
||||
*_entryData = MacroConditionFactory::Create(id, macro);
|
||||
(*_entryData)->SetIndex(idx);
|
||||
(*_entryData)->SetLogicType(logic);
|
||||
(*_entryData)->SetEnabled(enabled);
|
||||
(*_entryData)->PostLoad();
|
||||
RunAndClearPostLoadSteps();
|
||||
}
|
||||
@@ -245,9 +269,15 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
|
||||
MacroConditionFactory::CreateWidget(id, this, *_entryData);
|
||||
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
|
||||
this, SLOT(HeaderInfoChanged(const QString &)));
|
||||
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
|
||||
SLOT(ShowVariableMappings(bool)));
|
||||
_section->SetContent(widget);
|
||||
SetupVarMappings((*_entryData).get());
|
||||
_dur->setVisible(MacroConditionFactory::UsesDurationModifier(id));
|
||||
SetFocusPolicyOfWidgets();
|
||||
|
||||
emit MacroSignalManager::Instance()->ConditionTypeCreated(
|
||||
QString::fromStdString(id));
|
||||
}
|
||||
|
||||
void MacroConditionEdit::DurationChanged(const Duration &seconds)
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
class SwitchButton;
|
||||
|
||||
class DurationModifierEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -47,11 +49,13 @@ private slots:
|
||||
void ConditionSelectionChanged(const QString &text);
|
||||
void DurationChanged(const Duration &value);
|
||||
void DurationModifierChanged(DurationModifier::Type m);
|
||||
void ConditionEnableChanged(bool);
|
||||
|
||||
private:
|
||||
void SetLogicSelection();
|
||||
std::shared_ptr<MacroSegment> Data() const;
|
||||
|
||||
SwitchButton *_enable;
|
||||
QComboBox *_logicSelection;
|
||||
FilterComboBox *_conditionSelection;
|
||||
DurationModifierEdit *_dur;
|
||||
|
||||
@@ -6,6 +6,15 @@ namespace advss {
|
||||
|
||||
const std::string MacroConditionTempVar::id = "temp_var";
|
||||
|
||||
std::vector<TempVariableRef> MacroConditionTempVar::GetTempVarRefs() const
|
||||
{
|
||||
auto refs = MacroSegment::GetTempVarRefs();
|
||||
if (_tempVar.HasValidID()) {
|
||||
refs.push_back(_tempVar);
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
bool MacroConditionTempVar::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionTempVar::id,
|
||||
{MacroConditionTempVar::Create, MacroConditionTempVarEdit::Create,
|
||||
@@ -266,6 +275,7 @@ void MacroConditionTempVarEdit::VariableChanged(const TempVariableRef &var)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_tempVar = var;
|
||||
IncrementTempVarInUseGeneration();
|
||||
}
|
||||
|
||||
void MacroConditionTempVarEdit::Variable2Changed(const QString &text)
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetShortDesc() const;
|
||||
std::string GetId() const { return id; };
|
||||
std::vector<TempVariableRef> GetTempVarRefs() const;
|
||||
static std::shared_ptr<MacroCondition> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroConditionTempVar>(m);
|
||||
|
||||
@@ -31,6 +31,10 @@ bool MacroCondition::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroSegment::Load(obj);
|
||||
_logic.Load(obj, "logic");
|
||||
if (_logic.GetType() == Logic::Type::NONE) {
|
||||
SetEnabled(false);
|
||||
_logic.SetType(Logic::Type::AND);
|
||||
}
|
||||
_durationModifier.Load(obj);
|
||||
return true;
|
||||
}
|
||||
@@ -50,7 +54,8 @@ void MacroCondition::ValidateLogicSelection(bool isRootCondition,
|
||||
return;
|
||||
}
|
||||
|
||||
_logic.SetType(Logic::Type::NONE);
|
||||
_logic.SetType(Logic::Type::AND);
|
||||
SetEnabled(false);
|
||||
blog(LOG_WARNING,
|
||||
"setting invalid logic selection to 'ignore' for macro %s",
|
||||
context);
|
||||
|
||||
@@ -366,7 +366,7 @@ static void runSegmentHighlightChecksHelper(MacroSegmentList *list)
|
||||
// highlight its segments if required
|
||||
auto macroAction = dynamic_cast<MacroActionMacro *>(data.get());
|
||||
if (macroAction &&
|
||||
macroAction->_action ==
|
||||
macroAction->GetAction() ==
|
||||
MacroActionMacro::Action::NESTED_MACRO) {
|
||||
continue;
|
||||
}
|
||||
@@ -1086,6 +1086,11 @@ void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id,
|
||||
HighlightAction(idx);
|
||||
ui->actionsList->SetHelpMsgVisible(false);
|
||||
emit(MacroSegmentOrderChanged());
|
||||
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
ui->actionsList->ensureWidgetVisible(
|
||||
ui->actionsList->WidgetAt(currentActionIdx));
|
||||
});
|
||||
}
|
||||
|
||||
void MacroEdit::AddMacroAction(int idx)
|
||||
@@ -1113,6 +1118,9 @@ void MacroEdit::AddMacroAction(int idx)
|
||||
macro->Actions().at(idx - 1)->Save(data);
|
||||
}
|
||||
AddMacroAction(macro.get(), idx, id, data);
|
||||
if (idx < (int)macro->Actions().size()) {
|
||||
macro->Actions().at(idx)->FixupVarMappingRefs();
|
||||
}
|
||||
}
|
||||
|
||||
void MacroEdit::on_actionAdd_clicked()
|
||||
@@ -1389,6 +1397,11 @@ void MacroEdit::AddMacroElseAction(Macro *macro, int idx, const std::string &id,
|
||||
HighlightElseAction(idx);
|
||||
ui->elseActionsList->SetHelpMsgVisible(false);
|
||||
emit(MacroSegmentOrderChanged());
|
||||
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
ui->elseActionsList->ensureWidgetVisible(
|
||||
ui->elseActionsList->WidgetAt(currentElseActionIdx));
|
||||
});
|
||||
}
|
||||
|
||||
void MacroEdit::AddMacroElseAction(int idx)
|
||||
@@ -1416,6 +1429,9 @@ void MacroEdit::AddMacroElseAction(int idx)
|
||||
macro->ElseActions().at(idx - 1)->Save(data);
|
||||
}
|
||||
AddMacroElseAction(macro.get(), idx, id, data);
|
||||
if (idx < (int)macro->ElseActions().size()) {
|
||||
macro->ElseActions().at(idx)->FixupVarMappingRefs();
|
||||
}
|
||||
}
|
||||
|
||||
void MacroEdit::RemoveMacroElseAction(int idx)
|
||||
@@ -1560,6 +1576,9 @@ void MacroEdit::AddMacroCondition(int idx)
|
||||
macro->Conditions().at(idx - 1)->Save(data);
|
||||
}
|
||||
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,
|
||||
@@ -1591,6 +1610,11 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
|
||||
HighlightCondition(idx);
|
||||
ui->conditionsList->SetHelpMsgVisible(false);
|
||||
emit(MacroSegmentOrderChanged());
|
||||
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
ui->conditionsList->ensureWidgetVisible(
|
||||
ui->conditionsList->WidgetAt(currentConditionIdx));
|
||||
});
|
||||
}
|
||||
|
||||
void MacroEdit::on_conditionAdd_clicked()
|
||||
|
||||
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,160 @@
|
||||
#include "macro-export-import-dialog.hpp"
|
||||
#include "macro-export-extensions.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <QLayout>
|
||||
#include <QLabel>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QLayout>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace advss {
|
||||
|
||||
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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Section *MacroExportImportDialog::BuildExtensionSection()
|
||||
{
|
||||
auto outerSection = new Section(300, this);
|
||||
outerSection->AddHeaderWidget(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();
|
||||
|
||||
// Build a sorted index list (alphabetical by translated display name).
|
||||
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));
|
||||
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.
|
||||
const auto items = ext.getExportItems();
|
||||
if (items.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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, false);
|
||||
outerLayout->addWidget(innerSection);
|
||||
_extensionUIs.append(std::move(ui));
|
||||
}
|
||||
|
||||
outerSection->SetContent(outerContent, true);
|
||||
return outerSection;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
MacroExportImportDialog::MacroExportImportDialog(Type type,
|
||||
const QString &baseJson)
|
||||
: QDialog(nullptr),
|
||||
_baseJson(baseJson),
|
||||
_importExportString(new QPlainTextEdit(this)),
|
||||
_usePlainText(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.macroTab.export.usePlainText")))
|
||||
{
|
||||
_importExportString->setReadOnly(type == Type::EXPORT_MACRO);
|
||||
|
||||
auto label = new QLabel(obs_module_text(
|
||||
type == Type::EXPORT_MACRO
|
||||
? "AdvSceneSwitcher.macroTab.export.info"
|
||||
@@ -39,7 +177,12 @@ MacroExportImportDialog::MacroExportImportDialog(Type type)
|
||||
connect(_usePlainText, &QCheckBox::stateChanged, this,
|
||||
&MacroExportImportDialog::UsePlainTextChanged);
|
||||
|
||||
auto layout = new QVBoxLayout;
|
||||
auto layout = new QVBoxLayout(this);
|
||||
|
||||
if (type == Type::EXPORT_MACRO && !GetMacroExportExtensions().empty()) {
|
||||
layout->addWidget(BuildExtensionSection());
|
||||
}
|
||||
|
||||
layout->addWidget(label);
|
||||
layout->addWidget(_importExportString);
|
||||
layout->addWidget(_usePlainText);
|
||||
@@ -47,32 +190,57 @@ MacroExportImportDialog::MacroExportImportDialog(Type type)
|
||||
setLayout(layout);
|
||||
|
||||
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();
|
||||
auto compressedData = qCompress(inputData);
|
||||
QByteArray encodedData = compressedData.toBase64();
|
||||
return QString::fromUtf8(encodedData);
|
||||
OBSDataAutoRelease data =
|
||||
obs_data_create_from_json(_baseJson.toStdString().c_str());
|
||||
if (!data) {
|
||||
return _baseJson;
|
||||
}
|
||||
|
||||
const auto &extensions = GetMacroExportExtensions();
|
||||
for (int i = 0;
|
||||
i < (int)extensions.size() && i < (int)_extensionUIs.size(); ++i) {
|
||||
if (!_extensionUIs[i].mainCheck->isChecked()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QStringList selectedIds;
|
||||
for (const auto &[id, cb] : _extensionUIs[i].itemChecks) {
|
||||
if (cb->isChecked()) {
|
||||
selectedIds << id;
|
||||
}
|
||||
}
|
||||
extensions[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();
|
||||
QByteArray compressedData = QByteArray::fromBase64(encodedData);
|
||||
auto outputData = qUncompress(compressedData);
|
||||
return QString::fromUtf8(outputData);
|
||||
const QString json = BuildExportJson();
|
||||
if (usePlainText) {
|
||||
_importExportString->setPlainText(json);
|
||||
} else {
|
||||
_importExportString->setPlainText(compressMacroString(json));
|
||||
}
|
||||
}
|
||||
|
||||
void MacroExportImportDialog::ExportMacros(const QString &json)
|
||||
void MacroExportImportDialog::UpdateExportString()
|
||||
{
|
||||
MacroExportImportDialog dialog(
|
||||
MacroExportImportDialog::Type::EXPORT_MACRO);
|
||||
dialog._importExportString->setPlainText(compressMacroString(json));
|
||||
dialog.adjustSize();
|
||||
dialog.updateGeometry();
|
||||
dialog.exec();
|
||||
RefreshExportText();
|
||||
}
|
||||
|
||||
void MacroExportImportDialog::UsePlainTextChanged(int value)
|
||||
@@ -90,26 +258,45 @@ void MacroExportImportDialog::UsePlainTextChanged(int value)
|
||||
usePlainText = value;
|
||||
}
|
||||
|
||||
static bool isValidData(const QString &json)
|
||||
// ---------------------------------------------------------------------------
|
||||
// Static entry points
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void MacroExportImportDialog::ExportMacros(obs_data_t *macroData)
|
||||
{
|
||||
OBSDataAutoRelease data =
|
||||
obs_data_create_from_json(json.toStdString().c_str());
|
||||
return !!data;
|
||||
const char *rawJson = obs_data_get_json(macroData);
|
||||
const QString baseJson = rawJson ? QString::fromUtf8(rawJson)
|
||||
: QString{};
|
||||
|
||||
MacroExportImportDialog dialog(Type::EXPORT_MACRO, baseJson);
|
||||
dialog.adjustSize();
|
||||
dialog.updateGeometry();
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
bool MacroExportImportDialog::ImportMacros(QString &json)
|
||||
{
|
||||
MacroExportImportDialog dialog(
|
||||
MacroExportImportDialog::Type::IMPORT_MACRO);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
json = decompressMacroString(
|
||||
dialog._importExportString->toPlainText());
|
||||
if (!isValidData(json)) { // Fallback to support raw json format
|
||||
json = dialog._importExportString->toPlainText();
|
||||
}
|
||||
return true;
|
||||
MacroExportImportDialog dialog(Type::IMPORT_MACRO);
|
||||
if (dialog.exec() != QDialog::Accepted) {
|
||||
return false;
|
||||
}
|
||||
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
|
||||
|
||||
@@ -1,25 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "section.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
#include <QList>
|
||||
#include <QPlainTextEdit>
|
||||
|
||||
#include <obs-data.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroExportImportDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
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);
|
||||
|
||||
private slots:
|
||||
void UsePlainTextChanged(int);
|
||||
void UpdateExportString();
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
Section *BuildExtensionSection();
|
||||
QString BuildExportJson() const;
|
||||
void RefreshExportText();
|
||||
|
||||
QString _baseJson;
|
||||
QPlainTextEdit *_importExportString;
|
||||
QCheckBox *_usePlainText;
|
||||
QList<ExtensionUI> _extensionUIs;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -214,6 +214,20 @@ bool MacroIsPaused(const Macro *macro)
|
||||
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(
|
||||
const Macro *macro,
|
||||
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));
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -58,6 +58,8 @@ LastMacroConditionCheckTime(const Macro *);
|
||||
|
||||
EXPORT bool MacroIsStopped(const Macro *);
|
||||
EXPORT bool MacroIsPaused(const Macro *);
|
||||
EXPORT void SetMacroPaused(Macro *, bool paused);
|
||||
EXPORT void StopMacro(Macro *);
|
||||
EXPORT bool
|
||||
MacroWasPausedSince(const Macro *,
|
||||
const std::chrono::high_resolution_clock::time_point &);
|
||||
@@ -66,10 +68,14 @@ EXPORT bool MacroWasCheckedSinceLastStart(const Macro *);
|
||||
EXPORT void AddMacroHelperThread(Macro *, std::thread &&);
|
||||
|
||||
EXPORT bool CheckMacros();
|
||||
EXPORT bool CheckMacroConditions(Macro *, bool ignorePause = false);
|
||||
|
||||
EXPORT bool RunMacroActions(Macro *);
|
||||
bool RunMacroElseActions(Macro *);
|
||||
EXPORT bool RunMacroActions(Macro *, bool forceParallel = false,
|
||||
bool ignorePause = false);
|
||||
EXPORT bool RunMacroElseActions(Macro *, bool forceParallel = false,
|
||||
bool ignorePause = false);
|
||||
EXPORT bool RunMacros();
|
||||
|
||||
void StopAllMacros();
|
||||
|
||||
EXPORT void LoadMacros(obs_data_t *obj);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "macro-ref.hpp"
|
||||
#include "macro.hpp"
|
||||
|
||||
#include "plugin-state-helpers.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
MacroRef::MacroRef(std::string name)
|
||||
@@ -19,6 +21,7 @@ void MacroRef::Load(obs_data_t *obj)
|
||||
auto name = obs_data_get_string(obj, "macro");
|
||||
_postLoadName = name;
|
||||
_macro = GetWeakMacroByName(name);
|
||||
AddPostLoadStep([this]() { MacroRef::PostLoad(); });
|
||||
}
|
||||
|
||||
void MacroRef::PostLoad()
|
||||
|
||||
@@ -72,15 +72,24 @@ void MacroEdit::PasteMacroSegment()
|
||||
}
|
||||
AddMacroCondition(macro.get(), macro->Conditions().size(),
|
||||
copyInfo.segment->GetId(), data.Get(), logic);
|
||||
if (!macro->Conditions().empty()) {
|
||||
macro->Conditions().back()->FixupVarMappingRefs();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MacroSegmentCopyInfo::Type::ACTION:
|
||||
AddMacroAction(macro.get(), macro->Actions().size(),
|
||||
copyInfo.segment->GetId(), data.Get());
|
||||
if (!macro->Actions().empty()) {
|
||||
macro->Actions().back()->FixupVarMappingRefs();
|
||||
}
|
||||
break;
|
||||
case MacroSegmentCopyInfo::Type::ELSE:
|
||||
AddMacroElseAction(macro.get(), macro->ElseActions().size(),
|
||||
copyInfo.segment->GetId(), data.Get());
|
||||
if (!macro->ElseActions().empty()) {
|
||||
macro->ElseActions().back()->FixupVarMappingRefs();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "macro-segment.hpp"
|
||||
#include "macro.hpp"
|
||||
#include "mouse-wheel-guard.hpp"
|
||||
#include "path-helpers.hpp"
|
||||
#include "section.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
#include "variable.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QEvent>
|
||||
@@ -13,20 +15,101 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
std::vector<TempVariableRef> MacroSegment::GetTempVarRefs() const
|
||||
{
|
||||
std::vector<TempVariableRef> refs;
|
||||
for (const auto &mapping : _varMappings) {
|
||||
if (mapping.tempVar.HasValidID()) {
|
||||
refs.push_back(mapping.tempVar);
|
||||
}
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
void MacroSegment::ApplyVarMappings()
|
||||
{
|
||||
auto macro = GetMacro();
|
||||
for (const auto &mapping : _varMappings) {
|
||||
auto var = mapping.variable.lock();
|
||||
if (!var) {
|
||||
continue;
|
||||
}
|
||||
const auto tempVar = mapping.tempVar.GetTempVariable(macro);
|
||||
if (!tempVar) {
|
||||
continue;
|
||||
}
|
||||
const auto value = tempVar->Value();
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
var->SetValue(*value);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<VarMapping> &MacroSegment::GetVarMappings() const
|
||||
{
|
||||
return _varMappings;
|
||||
}
|
||||
|
||||
void MacroSegment::SetVarMappings(const std::vector<VarMapping> &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
|
||||
{
|
||||
return _tempVariables;
|
||||
}
|
||||
|
||||
MacroSegment::MacroSegment(Macro *m, bool supportsVariableValue)
|
||||
: _macro(m),
|
||||
_supportsVariableValue(supportsVariableValue)
|
||||
{
|
||||
}
|
||||
|
||||
void MacroSegment::SetVarMappingExpanded(bool expanded)
|
||||
{
|
||||
_varMappingExpanded = expanded;
|
||||
}
|
||||
|
||||
bool MacroSegment::Save(obs_data_t *obj) const
|
||||
{
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_bool(data, "collapsed", _collapsed);
|
||||
obs_data_set_bool(data, "varMappingExpanded", _varMappingExpanded);
|
||||
obs_data_set_bool(data, "useCustomLabel", _useCustomLabel);
|
||||
obs_data_set_string(data, "customLabel", _customLabel.c_str());
|
||||
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();
|
||||
for (const auto &mapping : _varMappings) {
|
||||
OBSDataAutoRelease item = obs_data_create();
|
||||
mapping.tempVar.Save(item, GetMacro(), "tempVar");
|
||||
obs_data_set_string(
|
||||
item, "variable",
|
||||
GetWeakVariableName(mapping.variable).c_str());
|
||||
obs_data_array_push_back(mappingsArray, item);
|
||||
}
|
||||
obs_data_set_array(data, "varMappings", mappingsArray);
|
||||
|
||||
obs_data_set_obj(obj, "segmentSettings", data);
|
||||
return true;
|
||||
}
|
||||
@@ -35,6 +118,7 @@ bool MacroSegment::Load(obs_data_t *obj)
|
||||
{
|
||||
OBSDataAutoRelease data = obs_data_get_obj(obj, "segmentSettings");
|
||||
_collapsed = obs_data_get_bool(data, "collapsed");
|
||||
_varMappingExpanded = obs_data_get_bool(data, "varMappingExpanded");
|
||||
_useCustomLabel = obs_data_get_bool(data, "useCustomLabel");
|
||||
_customLabel = obs_data_get_string(data, "customLabel");
|
||||
obs_data_set_default_bool(data, "enabled", true);
|
||||
@@ -45,6 +129,26 @@ bool MacroSegment::Load(obs_data_t *obj)
|
||||
_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();
|
||||
OBSDataArrayAutoRelease mappingsArray =
|
||||
obs_data_get_array(data, "varMappings");
|
||||
const size_t count = obs_data_array_count(mappingsArray);
|
||||
_varMappings.reserve(count);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
OBSDataAutoRelease item = obs_data_array_item(mappingsArray, i);
|
||||
VarMapping mapping;
|
||||
mapping.variable = GetWeakVariableByName(
|
||||
obs_data_get_string(item, "variable"));
|
||||
_varMappings.push_back(std::move(mapping));
|
||||
_varMappings.back().tempVar.Load(item, GetMacro(), "tempVar");
|
||||
}
|
||||
|
||||
ClearAvailableTempvars();
|
||||
return true;
|
||||
}
|
||||
@@ -152,6 +256,16 @@ void MacroSegment::AddTempvar(const std::string &id, const std::string &name,
|
||||
NotifyUIAboutTempVarChange(this);
|
||||
}
|
||||
|
||||
bool MacroSegment::IsTempVarInUse(const std::string &id) const
|
||||
{
|
||||
for (const auto &var : _tempVariables) {
|
||||
if (var.ID() == id) {
|
||||
return var.IsInUse();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MacroSegment::SetTempVarValue(const std::string &id,
|
||||
const std::string &value)
|
||||
{
|
||||
@@ -211,11 +325,38 @@ MacroSegmentEdit::MacroSegmentEdit(QWidget *parent)
|
||||
_headerInfo(new QLabel()),
|
||||
_frame(new QWidget),
|
||||
_contentLayout(new QVBoxLayout),
|
||||
_outputMappings(new TempVarOutputMappingsWidget(this)),
|
||||
_varMappingToggle(new QPushButton(this)),
|
||||
_noBorderframe(new QFrame),
|
||||
_borderFrame(new QFrame),
|
||||
_dropLineAbove(new QFrame),
|
||||
_dropLineBelow(new QFrame)
|
||||
{
|
||||
const auto iconPath = QString::fromStdString(GetDataFilePath(
|
||||
"res/images/" + GetThemeTypeName() + "Variable.svg"));
|
||||
_varMappingToggle->setIcon(QIcon(iconPath));
|
||||
_varMappingToggle->setMaximumWidth(22);
|
||||
_varMappingToggle->setFlat(true);
|
||||
_varMappingToggle->setCheckable(true);
|
||||
_varMappingToggle->setVisible(false);
|
||||
_varMappingToggle->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.outputMappings.toggle"));
|
||||
|
||||
QWidget::connect(_varMappingToggle, &QPushButton::toggled,
|
||||
_outputMappings,
|
||||
&TempVarOutputMappingsWidget::SetPanelExpanded);
|
||||
QWidget::connect(_varMappingToggle, &QPushButton::toggled, this,
|
||||
[this](bool checked) {
|
||||
if (Data()) {
|
||||
Data()->SetVarMappingExpanded(checked);
|
||||
}
|
||||
});
|
||||
QWidget::connect(_outputMappings,
|
||||
&TempVarOutputMappingsWidget::ExpandableChanged,
|
||||
_varMappingToggle, &QPushButton::setVisible);
|
||||
QWidget::connect(_section, &Section::Collapsed, _outputMappings,
|
||||
&TempVarOutputMappingsWidget::SetSectionCollapsed);
|
||||
|
||||
_dropLineAbove->setLineWidth(3);
|
||||
_dropLineAbove->setFixedHeight(11);
|
||||
_dropLineBelow->setLineWidth(3);
|
||||
@@ -277,6 +418,21 @@ MacroSegmentEdit::MacroSegmentEdit(QWidget *parent)
|
||||
_headerInfo->installEventFilter(this);
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::SetupVarMappings(MacroSegment *segment)
|
||||
{
|
||||
_outputMappings->SetSegment(segment);
|
||||
_varMappingToggle->setChecked(segment &&
|
||||
segment->GetVarMappingExpanded());
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::ShowVariableMappings(bool show)
|
||||
{
|
||||
_varMappingToggle->setChecked(show);
|
||||
if (Data()) {
|
||||
Data()->SetVarMappingExpanded(show);
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroSegmentEdit::eventFilter(QObject *obj, QEvent *ev)
|
||||
{
|
||||
if (obj == _headerInfo && ev->type() == QEvent::MouseMove) {
|
||||
|
||||
@@ -9,15 +9,23 @@
|
||||
|
||||
#include <QWidget>
|
||||
#include <QFrame>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QTimer>
|
||||
#include <obs-data.h>
|
||||
#include <memory>
|
||||
|
||||
class QLabel;
|
||||
|
||||
namespace advss {
|
||||
|
||||
class Macro;
|
||||
class Variable;
|
||||
|
||||
struct VarMapping {
|
||||
TempVariableRef tempVar;
|
||||
std::weak_ptr<Variable> variable;
|
||||
};
|
||||
|
||||
class EXPORT MacroSegment : public Lockable {
|
||||
public:
|
||||
@@ -28,12 +36,15 @@ public:
|
||||
int GetIndex() const { return _idx; }
|
||||
void SetCollapsed(bool collapsed) { _collapsed = collapsed; }
|
||||
bool GetCollapsed() const { return _collapsed; }
|
||||
void SetVarMappingExpanded(bool expanded);
|
||||
bool GetVarMappingExpanded() const { return _varMappingExpanded; }
|
||||
void SetUseCustomLabel(bool enable) { _useCustomLabel = enable; }
|
||||
bool GetUseCustomLabel() const { return _useCustomLabel; }
|
||||
void SetCustomLabel(const std::string &label) { _customLabel = label; }
|
||||
std::string GetCustomLabel() const { return _customLabel; }
|
||||
virtual bool Save(obs_data_t *obj) const = 0;
|
||||
virtual bool Load(obs_data_t *obj) = 0;
|
||||
virtual std::vector<TempVariableRef> GetTempVarRefs() const;
|
||||
virtual bool PostLoad();
|
||||
virtual std::string GetShortDesc() const;
|
||||
virtual std::string GetId() const = 0;
|
||||
@@ -42,6 +53,11 @@ public:
|
||||
void SetEnabled(bool);
|
||||
bool Enabled() const;
|
||||
virtual std::string GetVariableValue() const;
|
||||
void ApplyVarMappings();
|
||||
const std::vector<VarMapping> &GetVarMappings() const;
|
||||
void SetVarMappings(const std::vector<VarMapping> &mappings);
|
||||
std::vector<TempVariable> GetOwnTempVars() const;
|
||||
void FixupVarMappingRefs();
|
||||
|
||||
protected:
|
||||
friend bool SupportsVariableValue(MacroSegment *);
|
||||
@@ -54,6 +70,7 @@ protected:
|
||||
void AddTempvar(const std::string &id, const std::string &name,
|
||||
const std::string &description = "");
|
||||
|
||||
bool IsTempVarInUse(const std::string &id) const;
|
||||
void SetTempVarValue(const std::string &id, const std::string &value);
|
||||
|
||||
template<typename T, typename = std::enable_if_t<
|
||||
@@ -64,6 +81,14 @@ protected:
|
||||
: std::string("false"));
|
||||
}
|
||||
|
||||
template<typename F, typename = std::enable_if_t<std::is_invocable_v<F>>>
|
||||
void SetTempVarValue(const std::string &id, F &&valueProvider)
|
||||
{
|
||||
if (IsTempVarInUse(id)) {
|
||||
SetTempVarValue(id, valueProvider());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void ClearAvailableTempvars();
|
||||
std::optional<const TempVariable>
|
||||
@@ -77,6 +102,7 @@ private:
|
||||
// UI helper
|
||||
bool _highlight = false;
|
||||
bool _collapsed = false;
|
||||
bool _varMappingExpanded = false;
|
||||
bool _enabled = true;
|
||||
|
||||
// Custom header labels
|
||||
@@ -89,6 +115,7 @@ private:
|
||||
int _variableRefs = 0;
|
||||
std::string _variableValue;
|
||||
std::vector<TempVariable> _tempVariables;
|
||||
std::vector<VarMapping> _varMappings;
|
||||
|
||||
friend class Macro;
|
||||
};
|
||||
@@ -108,8 +135,11 @@ public:
|
||||
virtual std::shared_ptr<MacroSegment> Data() const = 0;
|
||||
virtual void SetupWidgets(bool basicSetup = false) = 0;
|
||||
|
||||
void SetupVarMappings(MacroSegment *segment);
|
||||
|
||||
public slots:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
void ShowVariableMappings(bool show);
|
||||
|
||||
protected slots:
|
||||
void Collapsed(bool) const;
|
||||
@@ -124,6 +154,8 @@ protected:
|
||||
QLabel *_headerInfo;
|
||||
QWidget *_frame;
|
||||
QVBoxLayout *_contentLayout;
|
||||
TempVarOutputMappingsWidget *_outputMappings;
|
||||
QPushButton *_varMappingToggle;
|
||||
bool _allWidgetsAreSetup = false;
|
||||
|
||||
private:
|
||||
|
||||
@@ -18,6 +18,7 @@ signals:
|
||||
void Remove(const QString &);
|
||||
void SegmentOrderChanged();
|
||||
void HighlightChanged(bool value);
|
||||
void ConditionTypeCreated(const QString &typeId);
|
||||
|
||||
private:
|
||||
MacroSignalManager(QObject *parent = nullptr);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "advanced-scene-switcher.hpp"
|
||||
#include "action-queue.hpp"
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "macro-export-import-dialog.hpp"
|
||||
@@ -316,13 +315,9 @@ void AdvSceneSwitcher::ExportMacros() const
|
||||
obs_data_array_push_back(macroArray, obj);
|
||||
}
|
||||
obs_data_set_array(data, "macros", macroArray);
|
||||
SaveVariables(data);
|
||||
SaveActionQueues(data);
|
||||
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(
|
||||
@@ -384,8 +379,6 @@ void AdvSceneSwitcher::ImportMacros()
|
||||
ImportMacros();
|
||||
return;
|
||||
}
|
||||
ImportVariables(data);
|
||||
ImportQueues(data);
|
||||
|
||||
auto version = obs_data_get_string(data, "version");
|
||||
if (strcmp(version, g_GIT_TAG) != 0) {
|
||||
@@ -557,6 +550,10 @@ void AdvSceneSwitcher::HighlightOnChange() const
|
||||
return;
|
||||
}
|
||||
|
||||
if (macro->Paused()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (macro->ActionTriggerModePreventedActionsSince(
|
||||
lastOnChangeHighlightCheckTime)) {
|
||||
HighlightWidget(ui->actionTriggerMode, Qt::yellow,
|
||||
@@ -670,18 +667,6 @@ void AdvSceneSwitcher::SetupMacroTab()
|
||||
SLOT(HighlightOnChange()));
|
||||
onChangeHighlightTimer.start();
|
||||
|
||||
// Reserve more space for macro edit area than for the macro list
|
||||
ui->macroListMacroEditSplitter->setStretchFactor(0, 1);
|
||||
ui->macroListMacroEditSplitter->setStretchFactor(1, 4);
|
||||
|
||||
if (switcher->saveWindowGeo) {
|
||||
if (shouldRestoreSplitter(
|
||||
switcher->macroListMacroEditSplitterPosition)) {
|
||||
ui->macroListMacroEditSplitter->setSizes(
|
||||
switcher->macroListMacroEditSplitterPosition);
|
||||
}
|
||||
}
|
||||
|
||||
SetupMacroSearchWidgets(ui->macroSearchLayout, ui->macroSearchText,
|
||||
ui->macroSearchClear, ui->macroSearchType,
|
||||
ui->macroSearchRegex,
|
||||
@@ -705,6 +690,26 @@ void AdvSceneSwitcher::SetupMacroTab()
|
||||
ui->actionTriggerMode->addItem(obs_module_text(name),
|
||||
static_cast<int>(mode));
|
||||
}
|
||||
|
||||
ui->macroListBox->setSizePolicy(QSizePolicy::Ignored,
|
||||
QSizePolicy::Preferred);
|
||||
ui->macroListBox->setMinimumWidth(0);
|
||||
ui->macroEditGroup->setSizePolicy(QSizePolicy::Ignored,
|
||||
QSizePolicy::Preferred);
|
||||
ui->macroEditGroup->setMinimumWidth(0);
|
||||
|
||||
if (shouldRestoreSplitter(
|
||||
switcher->macroListMacroEditSplitterPosition)) {
|
||||
ui->macroListMacroEditSplitter->setSizes(
|
||||
switcher->macroListMacroEditSplitterPosition);
|
||||
} else {
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
const auto totalWidth =
|
||||
ui->macroListMacroEditSplitter->width();
|
||||
ui->macroListMacroEditSplitter->setSizes(
|
||||
{totalWidth / 5, totalWidth * 4 / 5});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
|
||||
|
||||
@@ -17,6 +17,17 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
static bool setup()
|
||||
{
|
||||
AddPluginCleanupStep([]() {
|
||||
GetTopLevelMacros().clear();
|
||||
GetTemporaryMacros().clear();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setupDone = setup();
|
||||
|
||||
Macro::Macro(const std::string &name) : _dockSettings(this)
|
||||
{
|
||||
SetName(name);
|
||||
@@ -113,16 +124,16 @@ static bool checkCondition(const std::shared_ptr<MacroCondition> &condition)
|
||||
condition->WithLock([&condition, &conditionMatched]() {
|
||||
conditionMatched = condition->EvaluateCondition();
|
||||
});
|
||||
condition->ApplyVarMappings();
|
||||
const auto endTime = std::chrono::high_resolution_clock::now();
|
||||
const auto timeSpent = endTime - startTime;
|
||||
|
||||
if (timeSpent >= perfLogThreshold) {
|
||||
const long int ms =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
timeSpent)
|
||||
.count();
|
||||
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->GetMacro()->Name().c_str());
|
||||
}
|
||||
@@ -145,6 +156,15 @@ bool Macro::CheckConditionHelper(
|
||||
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();
|
||||
if (logicType == Logic::Type::NONE) {
|
||||
vblog(LOG_INFO, "ignoring condition '%s' for '%s'",
|
||||
@@ -269,7 +289,7 @@ bool Macro::CheckConditions(bool ignorePause)
|
||||
|
||||
const bool hasActionsToExecute = _matched ? (_actions.size() > 0)
|
||||
: (_elseActions.size() > 0);
|
||||
if (!_actionModeMatch && hasActionsToExecute) {
|
||||
if (!_actionModeMatch && hasActionsToExecute && !_paused) {
|
||||
_lastActionRunModePreventTime =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
@@ -368,7 +388,7 @@ bool Macro::ShouldRunActions() const
|
||||
!_paused && (_matched || _elseActions.size() > 0) &&
|
||||
_actionModeMatch;
|
||||
|
||||
if (VerboseLoggingEnabled() && !_actionModeMatch) {
|
||||
if (VerboseLoggingEnabled() && !_actionModeMatch && !_paused) {
|
||||
if (_matched && _actions.size() > 0) {
|
||||
blog(LOG_INFO, "skip actions for Macro %s (on change)",
|
||||
_name.c_str());
|
||||
@@ -432,6 +452,7 @@ bool Macro::RunActionsHelper(
|
||||
action->WithLock([&action, &actionResult]() {
|
||||
actionResult = action->PerformAction();
|
||||
});
|
||||
action->ApplyVarMappings();
|
||||
actionsExecutedSuccessfully =
|
||||
actionsExecutedSuccessfully && actionResult;
|
||||
} else {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#import <Carbon/Carbon.h>
|
||||
#include <Carbon/Carbon.h>
|
||||
#include <util/platform.h>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <QStringList>
|
||||
#include <QRegularExpression>
|
||||
@@ -14,56 +15,18 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
void GetWindowList(std::vector<std::string> &windows)
|
||||
static std::string nsStringToStdString(NSString *str)
|
||||
{
|
||||
windows.resize(0);
|
||||
|
||||
@autoreleasepool {
|
||||
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);
|
||||
if (!str) {
|
||||
return "";
|
||||
}
|
||||
const char *utf8 = [str UTF8String];
|
||||
return utf8 ? utf8 : "";
|
||||
}
|
||||
|
||||
void GetWindowList(QStringList &windows)
|
||||
std::string GetCurrentWindowTitle()
|
||||
{
|
||||
windows.clear();
|
||||
std::vector<std::string> temp;
|
||||
GetWindowList(temp);
|
||||
for (auto &w : temp) {
|
||||
windows << QString::fromStdString(w);
|
||||
}
|
||||
}
|
||||
|
||||
void GetCurrentWindowTitle(std::string &title)
|
||||
{
|
||||
title.resize(0);
|
||||
std::string title;
|
||||
@autoreleasepool {
|
||||
CFArrayRef cfApps = CGWindowListCopyWindowInfo(
|
||||
kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
|
||||
@@ -107,102 +70,52 @@ void GetCurrentWindowTitle(std::string &title)
|
||||
apps = nil;
|
||||
CFRelease(cfApps);
|
||||
}
|
||||
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)
|
||||
{
|
||||
double maximizedTolerance = 0.99;
|
||||
NSRect screenFrame = [screen frame];
|
||||
NSRect windowBounds;
|
||||
CGRectMakeWithDictionaryRepresentation(
|
||||
(CFDictionaryRef)[app objectForKey:@"kCGWindowBounds"],
|
||||
&windowBounds);
|
||||
|
||||
int sumX = windowBounds.origin.x + windowBounds.size.width;
|
||||
int sumY = windowBounds.origin.y + windowBounds.size.height;
|
||||
// CGWindowList: flipped coords, origin at top-left of main display.
|
||||
// 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
|
||||
if (sumX > screenFrame.size.width) {
|
||||
// Convert visible area to CGWindow coordinates.
|
||||
// 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;
|
||||
}
|
||||
if (sumY > screenFrame.size.height) {
|
||||
if (fabs((windowBounds.origin.x + windowBounds.size.width) -
|
||||
screenRight) > tolerance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((double)sumX / (double)screenFrame.size.width) >
|
||||
maximizedTolerance &&
|
||||
((double)sumY / (double)screenFrame.size.height) >
|
||||
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);
|
||||
// Bottom edge must align with the visible bottom (above dock).
|
||||
if (fabs((windowBounds.origin.y + windowBounds.size.height) -
|
||||
screenBottom) > tolerance) {
|
||||
return false;
|
||||
}
|
||||
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)
|
||||
@@ -216,52 +129,176 @@ bool isWindowFullscreenOnScreen(NSDictionary *app, NSScreen *screen)
|
||||
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 {
|
||||
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) {
|
||||
CFArrayRef cfApps = CGWindowListCopyWindowInfo(
|
||||
kCGWindowListExcludeDesktopElements, kCGNullWindowID);
|
||||
NSMutableArray *apps = (__bridge NSMutableArray *)cfApps;
|
||||
|
||||
// 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) {
|
||||
if (isWindowOriginOnScreen(app, screen,
|
||||
true) &&
|
||||
isWindowFullscreenOnScreen(
|
||||
app, screen)) {
|
||||
apps = nil;
|
||||
CFRelease(cfApps);
|
||||
return true;
|
||||
if (isWindowFullscreenOnScreen(app,
|
||||
screen)) {
|
||||
int pid = [[app objectForKey:
|
||||
@"kCGWindowOwnerPID"]
|
||||
intValue];
|
||||
fullscreenPIDs.insert(pid);
|
||||
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;
|
||||
CFRelease(cfApps);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<std::string> GetTextInWindow(const std::string &)
|
||||
{
|
||||
// Not implemented
|
||||
return {};
|
||||
return result;
|
||||
}
|
||||
|
||||
int SecondsSinceLastInput()
|
||||
@@ -273,9 +310,9 @@ int SecondsSinceLastInput()
|
||||
return (int)time;
|
||||
}
|
||||
|
||||
void GetProcessList(QStringList &list)
|
||||
QStringList GetProcessList()
|
||||
{
|
||||
list.clear();
|
||||
QStringList list;
|
||||
@autoreleasepool {
|
||||
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
|
||||
NSArray *array = [ws runningApplications];
|
||||
@@ -291,11 +328,11 @@ void GetProcessList(QStringList &list)
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void GetForegroundProcessName(std::string &proc)
|
||||
std::string GetForegroundProcessName()
|
||||
{
|
||||
proc.resize(0);
|
||||
@autoreleasepool {
|
||||
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
|
||||
NSArray *array = [ws runningApplications];
|
||||
@@ -308,23 +345,72 @@ void GetForegroundProcessName(std::string &proc)
|
||||
break;
|
||||
}
|
||||
const char *str = name.UTF8String;
|
||||
proc = std::string(str);
|
||||
if (str) {
|
||||
return str;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void GetForegroundProcessName(QString &proc)
|
||||
std::string GetForegroundProcessPath()
|
||||
{
|
||||
std::string temp;
|
||||
GetForegroundProcessName(temp);
|
||||
proc = QString::fromStdString(temp);
|
||||
@autoreleasepool {
|
||||
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
|
||||
for (NSRunningApplication *app in [ws runningApplications]) {
|
||||
if (!app.isActive) {
|
||||
continue;
|
||||
}
|
||||
NSURL *url = app.executableURL;
|
||||
if (!url) {
|
||||
break;
|
||||
}
|
||||
const char *str = url.path.UTF8String;
|
||||
if (str) {
|
||||
return str;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QStringList GetProcessPathsFromName(const QString &name)
|
||||
{
|
||||
QStringList paths;
|
||||
@autoreleasepool {
|
||||
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
|
||||
for (NSRunningApplication *app in [ws runningApplications]) {
|
||||
NSString *appName = app.localizedName;
|
||||
if (!appName) {
|
||||
continue;
|
||||
}
|
||||
const char *nameStr = appName.UTF8String;
|
||||
if (!nameStr || name != QString::fromUtf8(nameStr)) {
|
||||
continue;
|
||||
}
|
||||
NSURL *url = app.executableURL;
|
||||
if (!url) {
|
||||
continue;
|
||||
}
|
||||
const char *pathStr = url.path.UTF8String;
|
||||
if (!pathStr) {
|
||||
continue;
|
||||
}
|
||||
QString path = QString::fromUtf8(pathStr);
|
||||
if (!paths.contains(path)) {
|
||||
paths.append(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
bool IsInFocus(const QString &executable)
|
||||
{
|
||||
std::string current;
|
||||
GetForegroundProcessName(current);
|
||||
const auto current = GetForegroundProcessName();
|
||||
|
||||
// True if executable switch equals current window
|
||||
bool equals = (executable.toStdString() == current);
|
||||
|
||||
@@ -11,15 +11,36 @@ namespace advss {
|
||||
|
||||
enum class HotkeyType;
|
||||
|
||||
EXPORT void GetWindowList(std::vector<std::string> &windows);
|
||||
EXPORT void GetWindowList(QStringList &windows);
|
||||
EXPORT void GetCurrentWindowTitle(std::string &title);
|
||||
EXPORT bool IsFullscreen(const std::string &title);
|
||||
EXPORT bool IsMaximized(const std::string &title);
|
||||
EXPORT std::optional<std::string> GetTextInWindow(const std::string &window);
|
||||
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 int SecondsSinceLastInput();
|
||||
EXPORT void GetProcessList(QStringList &processes);
|
||||
EXPORT void GetForegroundProcessName(std::string &name);
|
||||
EXPORT QStringList GetProcessList();
|
||||
EXPORT std::string GetForegroundProcessName();
|
||||
EXPORT std::string GetForegroundProcessPath();
|
||||
EXPORT QStringList GetProcessPathsFromName(const QString &name);
|
||||
EXPORT bool IsInFocus(const QString &executable);
|
||||
void PlatformInit();
|
||||
void PlatformCleanup();
|
||||
|
||||
@@ -16,14 +16,17 @@ static void setupTab(QTabWidget *);
|
||||
|
||||
static ActionQueueTable *tabWidget = nullptr;
|
||||
|
||||
void RegisterActionQueueTab()
|
||||
static bool setup()
|
||||
{
|
||||
AddPluginInitStep([]() {
|
||||
AddPluginInitStep([] {
|
||||
AddSetupTabCallback("actionQueueTab", ActionQueueTable::Create,
|
||||
setupTab);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setupDone = setup();
|
||||
|
||||
static void setTabVisible(QTabWidget *tabWidget, bool visible)
|
||||
{
|
||||
SetTabVisibleByName(
|
||||
|
||||
@@ -12,20 +12,16 @@ std::deque<std::shared_ptr<Item>> &GetActionQueues()
|
||||
return queues;
|
||||
}
|
||||
|
||||
void RegisterActionQueueTab();
|
||||
|
||||
void SetupActionQueues()
|
||||
static bool setup()
|
||||
{
|
||||
static bool done = false;
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
AddSaveStep(SaveActionQueues);
|
||||
AddLoadStep(LoadActionQueues);
|
||||
RegisterActionQueueTab();
|
||||
done = true;
|
||||
AddPluginCleanupStep([]() { queues.clear(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setupDone = setup();
|
||||
|
||||
ActionQueue::ActionQueue() : Item()
|
||||
{
|
||||
_lastEmpty = std::chrono::high_resolution_clock::now();
|
||||
|
||||
@@ -101,7 +101,6 @@ signals:
|
||||
};
|
||||
|
||||
std::deque<std::shared_ptr<Item>> &GetActionQueues();
|
||||
void SetupActionQueues();
|
||||
void SaveActionQueues(obs_data_t *);
|
||||
void LoadActionQueues(obs_data_t *);
|
||||
void ImportQueues(obs_data_t *);
|
||||
|
||||
@@ -163,6 +163,8 @@ public:
|
||||
bool disableHints = false;
|
||||
bool disableFilterComboboxFilter = false;
|
||||
bool hideLegacyTabs = true;
|
||||
bool settingsLockEnabled = false;
|
||||
std::string settingsLockPassword;
|
||||
bool saveWindowGeo = false;
|
||||
QPoint windowPos = {};
|
||||
QSize windowSize = {};
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
namespace advss {
|
||||
|
||||
const std::map<Logic::Type, const char *> Logic::localeMap = {
|
||||
{Logic::Type::NONE, {"AdvSceneSwitcher.logic.none"}},
|
||||
{Logic::Type::AND, {"AdvSceneSwitcher.logic.and"}},
|
||||
{Logic::Type::OR, {"AdvSceneSwitcher.logic.or"}},
|
||||
{Logic::Type::AND_NOT, {"AdvSceneSwitcher.logic.andNot"}},
|
||||
@@ -67,7 +66,7 @@ void Logic::PopulateLogicTypeSelection(QComboBox *list, bool isRootCondition)
|
||||
return typeValue < rootOffset;
|
||||
}}
|
||||
: std::function<bool(int)>{[](int typeValue) {
|
||||
return typeValue >= rootOffset;
|
||||
return typeValue > rootOffset;
|
||||
}};
|
||||
for (const auto &[type, name] : localeMap) {
|
||||
const int typeValue = static_cast<int>(type);
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
#include "log-helper.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <obs-module.h>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QLabel>
|
||||
#include <QMainWindow>
|
||||
#include <QTimer>
|
||||
|
||||
#include <thread>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
@@ -25,10 +26,31 @@ static constexpr bool handleUncleanShutdown = true;
|
||||
#endif
|
||||
|
||||
static bool wasCleanShutdown = false;
|
||||
static bool suppressCrashDialog = false;
|
||||
|
||||
static char *sentinelFile = nullptr;
|
||||
|
||||
bool GetSuppressCrashDialog()
|
||||
{
|
||||
return suppressCrashDialog;
|
||||
}
|
||||
|
||||
void SetSuppressCrashDialog(bool suppress)
|
||||
{
|
||||
suppressCrashDialog = suppress;
|
||||
}
|
||||
|
||||
static void setup();
|
||||
static bool setupDone = []() {
|
||||
AddPluginInitStep(setup);
|
||||
AddSaveStep([](obs_data_t *obj) {
|
||||
obs_data_set_bool(obj, "suppressCrashDialog",
|
||||
suppressCrashDialog);
|
||||
});
|
||||
AddLoadStep([](obs_data_t *obj) {
|
||||
suppressCrashDialog =
|
||||
obs_data_get_bool(obj, "suppressCrashDialog");
|
||||
});
|
||||
return true;
|
||||
}();
|
||||
|
||||
@@ -38,7 +60,6 @@ static void handleShutdown(enum obs_frontend_event event, void *)
|
||||
return;
|
||||
}
|
||||
|
||||
char *sentinelFile = obs_module_config_path(sentinel.data());
|
||||
if (!sentinelFile) {
|
||||
return;
|
||||
}
|
||||
@@ -58,7 +79,8 @@ static void handleShutdown(enum obs_frontend_event event, void *)
|
||||
|
||||
static void setup()
|
||||
{
|
||||
char *sentinelFile = obs_module_config_path(sentinel.data());
|
||||
// Freed in handleShutdown()
|
||||
sentinelFile = obs_module_config_path(sentinel.data());
|
||||
if (!sentinelFile) {
|
||||
return;
|
||||
}
|
||||
@@ -86,7 +108,6 @@ static void setup()
|
||||
|
||||
file.write("running");
|
||||
file.close();
|
||||
bfree(sentinelFile);
|
||||
|
||||
obs_frontend_add_event_callback(handleShutdown, nullptr);
|
||||
return;
|
||||
@@ -108,8 +129,39 @@ static bool wasUncleanShutdown()
|
||||
|
||||
static void askForStartupSkip()
|
||||
{
|
||||
bool skipStart = DisplayMessage(
|
||||
obs_module_text("AdvSceneSwitcher.crashDetected"), true, false);
|
||||
auto mainWindow =
|
||||
static_cast<QMainWindow *>(obs_frontend_get_main_window());
|
||||
auto dialog = new QDialog(mainWindow);
|
||||
dialog->setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
|
||||
dialog->setWindowFlags(dialog->windowFlags() &
|
||||
~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
auto layout = new QVBoxLayout(dialog);
|
||||
auto label = new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.crashDetected"), dialog);
|
||||
label->setWordWrap(true);
|
||||
layout->addWidget(label);
|
||||
|
||||
auto checkbox = new QCheckBox(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.crashDetected.suppressCheckbox"),
|
||||
dialog);
|
||||
layout->addWidget(checkbox);
|
||||
|
||||
auto buttonbox = new QDialogButtonBox(
|
||||
QDialogButtonBox::Yes | QDialogButtonBox::No, dialog);
|
||||
QObject::connect(buttonbox, &QDialogButtonBox::accepted, dialog,
|
||||
&QDialog::accept);
|
||||
QObject::connect(buttonbox, &QDialogButtonBox::rejected, dialog,
|
||||
&QDialog::reject);
|
||||
layout->addWidget(buttonbox);
|
||||
|
||||
dialog->setLayout(layout);
|
||||
|
||||
bool skipStart = dialog->exec() == QDialog::Accepted;
|
||||
suppressCrashDialog = checkbox->isChecked();
|
||||
dialog->deleteLater();
|
||||
|
||||
if (!skipStart) {
|
||||
StartPlugin();
|
||||
}
|
||||
@@ -121,6 +173,10 @@ bool ShouldSkipPluginStartOnUncleanShutdown()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (suppressCrashDialog) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This function is called while the plugin settings are being loaded.
|
||||
// Blocking at this stage can cause issues such as OBS failing to start
|
||||
// or crashing.
|
||||
|
||||
@@ -4,4 +4,7 @@ namespace advss {
|
||||
|
||||
bool ShouldSkipPluginStartOnUncleanShutdown();
|
||||
|
||||
bool GetSuppressCrashDialog();
|
||||
void SetSuppressCrashDialog(bool suppress);
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -11,6 +11,7 @@ static void populateUnits(QComboBox *list)
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.unit.seconds"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.unit.minutes"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.unit.hours"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.unit.days"));
|
||||
}
|
||||
|
||||
DurationSelection::DurationSelection(QWidget *parent, bool showUnitSelection,
|
||||
@@ -66,6 +67,8 @@ static int durationUnitToMultiplier(Duration::Unit u)
|
||||
return 60;
|
||||
case Duration::Unit::HOURS:
|
||||
return 3600;
|
||||
case Duration::Unit::DAYS:
|
||||
return 86400;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -121,6 +121,8 @@ static std::string durationUnitToString(Duration::Unit u)
|
||||
return obs_module_text("AdvSceneSwitcher.unit.minutes");
|
||||
case Duration::Unit::HOURS:
|
||||
return obs_module_text("AdvSceneSwitcher.unit.hours");
|
||||
case Duration::Unit::DAYS:
|
||||
return obs_module_text("AdvSceneSwitcher.unit.days");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -151,6 +153,8 @@ static int durationUnitToMultiplier(Duration::Unit u)
|
||||
return 60;
|
||||
case Duration::Unit::HOURS:
|
||||
return 3600;
|
||||
case Duration::Unit::DAYS:
|
||||
return 86400;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public:
|
||||
SECONDS,
|
||||
MINUTES,
|
||||
HOURS,
|
||||
DAYS,
|
||||
};
|
||||
EXPORT Unit GetUnit() const { return _unit; }
|
||||
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
FileSelection::FileSelection(FileSelection::Type type, QWidget *parent)
|
||||
FileSelection::FileSelection(FileSelection::Type type, QWidget *parent,
|
||||
const QString &browseTitle)
|
||||
: QWidget(parent),
|
||||
_type(type),
|
||||
_browseTitle(browseTitle),
|
||||
_filePath(new VariableLineEdit(this)),
|
||||
_browseButton(
|
||||
new QPushButton(obs_module_text("AdvSceneSwitcher.browse")))
|
||||
@@ -55,11 +57,14 @@ void FileSelection::BrowseButtonClicked()
|
||||
QString defaultPath = ValidPathOrDesktop(_filePath->text());
|
||||
QString path;
|
||||
if (_type == FileSelection::Type::WRITE) {
|
||||
path = QFileDialog::getSaveFileName(this, "", defaultPath);
|
||||
path = QFileDialog::getSaveFileName(this, _browseTitle,
|
||||
defaultPath);
|
||||
} else if (_type == FileSelection::Type::READ) {
|
||||
path = QFileDialog::getOpenFileName(this, "", defaultPath);
|
||||
path = QFileDialog::getOpenFileName(this, _browseTitle,
|
||||
defaultPath);
|
||||
} else {
|
||||
path = QFileDialog::getExistingDirectory(this, "", defaultPath);
|
||||
path = QFileDialog::getExistingDirectory(this, _browseTitle,
|
||||
defaultPath);
|
||||
}
|
||||
|
||||
if (path.isEmpty()) {
|
||||
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
|
||||
EXPORT
|
||||
FileSelection(FileSelection::Type type = FileSelection::Type::READ,
|
||||
QWidget *parent = 0);
|
||||
QWidget *parent = 0, const QString &browseTitle = "");
|
||||
EXPORT void SetPath(const StringVariable &);
|
||||
EXPORT void SetPath(const QString &);
|
||||
EXPORT QString GetPath() const;
|
||||
@@ -34,6 +34,7 @@ signals:
|
||||
|
||||
private:
|
||||
Type _type;
|
||||
QString _browseTitle;
|
||||
VariableLineEdit *_filePath;
|
||||
QPushButton *_browseButton;
|
||||
};
|
||||
|
||||
@@ -55,9 +55,7 @@ static void WriteFirstRun(bool value)
|
||||
|
||||
static QString DetectFocusedWindow()
|
||||
{
|
||||
std::string title;
|
||||
GetCurrentWindowTitle(title);
|
||||
return QString::fromStdString(title);
|
||||
return QString::fromStdString(GetCurrentWindowTitle());
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
@@ -400,10 +398,14 @@ void FirstRunWizard::markFirstRunComplete()
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<Macro> FirstRunWizard::ShowWizard(QWidget *parent)
|
||||
std::shared_ptr<Macro> FirstRunWizard::ShowWizard(QWidget *parent,
|
||||
bool *wasSkipped)
|
||||
{
|
||||
auto wizard = new FirstRunWizard(parent);
|
||||
wizard->exec();
|
||||
if (wasSkipped) {
|
||||
*wasSkipped = (wizard->result() == QDialog::Rejected);
|
||||
}
|
||||
wizard->deleteLater();
|
||||
return wizard->_macro;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,8 @@ class FirstRunWizard : public QWizard {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FirstRunWizard(QWidget *parent = nullptr);
|
||||
static std::shared_ptr<Macro> ShowWizard(QWidget *parent);
|
||||
static std::shared_ptr<Macro> ShowWizard(QWidget *parent,
|
||||
bool *wasSkipped = nullptr);
|
||||
static bool
|
||||
CreateMacro(std::shared_ptr<Macro> ¯o, const std::string ¯oName,
|
||||
const std::string &conditionId, obs_data_t *conditionData,
|
||||
|
||||
@@ -32,6 +32,17 @@ ListControls::ListControls(QWidget *parent, bool reorder) : QToolBar(parent)
|
||||
}
|
||||
}
|
||||
|
||||
void ListControls::AddWidget(QWidget *widget)
|
||||
{
|
||||
addSeparator();
|
||||
addWidget(widget);
|
||||
}
|
||||
|
||||
void ListControls::AddSeparator()
|
||||
{
|
||||
addSeparator();
|
||||
}
|
||||
|
||||
void ListControls::AddActionHelper(const char *theme, const char *className,
|
||||
const char *tooltip,
|
||||
const std::function<void()> &signal)
|
||||
|
||||
@@ -10,6 +10,8 @@ class ADVSS_EXPORT ListControls final : public QToolBar {
|
||||
|
||||
public:
|
||||
ListControls(QWidget *parent = nullptr, bool reorder = true);
|
||||
void AddWidget(QWidget *widget);
|
||||
void AddSeparator();
|
||||
|
||||
signals:
|
||||
void Add();
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
#include "list-editor.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
|
||||
#include <QEvent>
|
||||
|
||||
namespace advss {
|
||||
|
||||
ListEditor::ListEditor(QWidget *parent, bool reorder)
|
||||
: QWidget(parent),
|
||||
_list(new QListWidget()),
|
||||
_controls(new ListControls(this, reorder)),
|
||||
_mainLayout(new QVBoxLayout())
|
||||
_mainLayout(new QVBoxLayout()),
|
||||
_placeholder(new QLabel(_list->viewport()))
|
||||
{
|
||||
QWidget::connect(_controls, SIGNAL(Add()), this, SLOT(Add()));
|
||||
QWidget::connect(_controls, SIGNAL(Remove()), this, SLOT(Remove()));
|
||||
@@ -15,6 +18,19 @@ ListEditor::ListEditor(QWidget *parent, bool reorder)
|
||||
QWidget::connect(_controls, SIGNAL(Down()), this, SLOT(Down()));
|
||||
QWidget::connect(_list, SIGNAL(itemDoubleClicked(QListWidgetItem *)),
|
||||
this, SLOT(Clicked(QListWidgetItem *)));
|
||||
QWidget::connect(_list->model(),
|
||||
SIGNAL(rowsInserted(QModelIndex, int, int)), this,
|
||||
SLOT(UpdatePlaceholder()));
|
||||
QWidget::connect(_list->model(),
|
||||
SIGNAL(rowsRemoved(QModelIndex, int, int)), this,
|
||||
SLOT(UpdatePlaceholder()));
|
||||
QWidget::connect(_list->model(), SIGNAL(modelReset()), this,
|
||||
SLOT(UpdatePlaceholder()));
|
||||
|
||||
_placeholder->setAlignment(Qt::AlignCenter);
|
||||
_placeholder->setWordWrap(true);
|
||||
_placeholder->hide();
|
||||
_list->viewport()->installEventFilter(this);
|
||||
|
||||
_mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
_mainLayout->addWidget(_list);
|
||||
@@ -22,6 +38,40 @@ ListEditor::ListEditor(QWidget *parent, bool reorder)
|
||||
setLayout(_mainLayout);
|
||||
}
|
||||
|
||||
void ListEditor::SetPlaceholderText(const QString &text)
|
||||
{
|
||||
_placeholder->setText(text);
|
||||
UpdatePlaceholder();
|
||||
}
|
||||
|
||||
void ListEditor::SetMinListHeight(int value)
|
||||
{
|
||||
_minHeight = value;
|
||||
}
|
||||
|
||||
void ListEditor::SetMaxListHeight(int value)
|
||||
{
|
||||
_maxHeight = value;
|
||||
}
|
||||
|
||||
void ListEditor::UpdatePlaceholder()
|
||||
{
|
||||
bool visible = !_placeholder->text().isEmpty() && _list->count() == 0;
|
||||
_placeholder->setVisible(visible);
|
||||
if (visible) {
|
||||
_placeholder->setGeometry(_list->viewport()->rect());
|
||||
}
|
||||
}
|
||||
|
||||
bool ListEditor::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (obj == _list->viewport() && event->type() == QEvent::Resize &&
|
||||
_placeholder->isVisible()) {
|
||||
_placeholder->setGeometry(_list->viewport()->rect());
|
||||
}
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
void ListEditor::showEvent(QShowEvent *e)
|
||||
{
|
||||
QWidget::showEvent(e);
|
||||
@@ -64,6 +114,24 @@ int ListEditor::GetIndexOfSignal() const
|
||||
void ListEditor::UpdateListSize()
|
||||
{
|
||||
SetHeightToContentHeight(_list);
|
||||
|
||||
if (_list->count() == 0 && !_placeholder->text().isEmpty()) {
|
||||
auto height = _list->fontMetrics().height() * 3;
|
||||
_list->setMinimumHeight(height);
|
||||
_list->setMaximumHeight(height);
|
||||
}
|
||||
|
||||
if (_minHeight >= 0 && _list->minimumHeight() != _minHeight) {
|
||||
_list->setMinimumHeight(_minHeight);
|
||||
}
|
||||
|
||||
if (_maxHeight >= 0 && _list->maximumHeight() != _maxHeight) {
|
||||
if (_list->minimumHeight() > _maxHeight) {
|
||||
_list->setMinimumHeight(_maxHeight);
|
||||
}
|
||||
_list->setMaximumHeight(_maxHeight);
|
||||
}
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "export-symbol-helper.hpp"
|
||||
#include "list-controls.hpp"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QLayout>
|
||||
|
||||
@@ -13,9 +14,13 @@ class ADVSS_EXPORT ListEditor : public QWidget {
|
||||
public:
|
||||
ListEditor(QWidget *parent = nullptr, bool reorder = true);
|
||||
int count() const { return _list->count(); };
|
||||
void SetPlaceholderText(const QString &text);
|
||||
void SetMinListHeight(int);
|
||||
void SetMaxListHeight(int);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *);
|
||||
bool eventFilter(QObject *, QEvent *);
|
||||
|
||||
private slots:
|
||||
virtual void Add() = 0;
|
||||
@@ -23,6 +28,7 @@ private slots:
|
||||
virtual void Up(){};
|
||||
virtual void Down(){};
|
||||
virtual void Clicked(QListWidgetItem *) {}
|
||||
void UpdatePlaceholder();
|
||||
|
||||
protected:
|
||||
void UpdateListSize();
|
||||
@@ -31,6 +37,11 @@ protected:
|
||||
QListWidget *_list;
|
||||
ListControls *_controls;
|
||||
QVBoxLayout *_mainLayout;
|
||||
|
||||
private:
|
||||
QLabel *_placeholder;
|
||||
int _minHeight = -1;
|
||||
int _maxHeight = -1;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -9,19 +9,15 @@ namespace advss {
|
||||
|
||||
std::variant<double, std::string> EvalMathExpression(const std::string &expr)
|
||||
{
|
||||
static bool setupDone = false;
|
||||
static exprtk::symbol_table<double> symbolTable;
|
||||
static std::random_device rd;
|
||||
static std::mt19937 gen(rd());
|
||||
static std::uniform_real_distribution<double> dis(0.0, 1.0);
|
||||
static auto randomFunc = []() {
|
||||
thread_local std::mt19937 gen(std::random_device{}());
|
||||
thread_local std::uniform_real_distribution<double> dis(0.0,
|
||||
1.0);
|
||||
return dis(gen);
|
||||
};
|
||||
|
||||
if (!setupDone) {
|
||||
symbolTable.add_function("random", randomFunc);
|
||||
setupDone = true;
|
||||
}
|
||||
exprtk::symbol_table<double> symbolTable;
|
||||
symbolTable.add_function("random", randomFunc);
|
||||
|
||||
exprtk::expression<double> expression;
|
||||
expression.register_symbol_table(symbolTable);
|
||||
|
||||
@@ -75,6 +75,18 @@ static std::vector<std::function<void()>> &getStopSteps()
|
||||
return steps;
|
||||
}
|
||||
|
||||
static std::vector<std::function<void(obs_data_t *)>> &getEarlySaveSteps()
|
||||
{
|
||||
static std::vector<std::function<void(obs_data_t *)>> steps;
|
||||
return steps;
|
||||
}
|
||||
|
||||
static std::vector<std::function<void(obs_data_t *)>> &getEarlyLoadSteps()
|
||||
{
|
||||
static std::vector<std::function<void(obs_data_t *)>> steps;
|
||||
return steps;
|
||||
}
|
||||
|
||||
static std::vector<std::function<void(obs_data_t *)>> &getSaveSteps()
|
||||
{
|
||||
static std::vector<std::function<void(obs_data_t *)>> steps;
|
||||
@@ -109,6 +121,18 @@ void LoadPluginSettings(obs_data_t *obj)
|
||||
GetSwitcher()->LoadSettings(obj);
|
||||
}
|
||||
|
||||
void AddEarlySaveStep(std::function<void(obs_data_t *)> step)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
getEarlySaveSteps().emplace_back(step);
|
||||
}
|
||||
|
||||
void AddEarlyLoadStep(std::function<void(obs_data_t *)> step)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
getEarlyLoadSteps().emplace_back(step);
|
||||
}
|
||||
|
||||
void AddSaveStep(std::function<void(obs_data_t *)> step)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
@@ -136,6 +160,9 @@ void AddIntervalResetStep(std::function<void()> step)
|
||||
void RunSaveSteps(obs_data_t *obj)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
for (const auto &func : getEarlySaveSteps()) {
|
||||
func(obj);
|
||||
}
|
||||
for (const auto &func : getSaveSteps()) {
|
||||
func(obj);
|
||||
}
|
||||
@@ -144,6 +171,9 @@ void RunSaveSteps(obs_data_t *obj)
|
||||
void RunLoadSteps(obs_data_t *obj)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
for (const auto &func : getEarlyLoadSteps()) {
|
||||
func(obj);
|
||||
}
|
||||
for (const auto &func : getLoadSteps()) {
|
||||
func(obj);
|
||||
}
|
||||
@@ -310,7 +340,7 @@ bool HighlightUIElementsEnabled()
|
||||
|
||||
bool OBSIsShuttingDown()
|
||||
{
|
||||
return GetSwitcher() && GetSwitcher()->obsIsShuttingDown;
|
||||
return !GetSwitcher() || GetSwitcher()->obsIsShuttingDown;
|
||||
}
|
||||
|
||||
bool InitialLoadIsComplete()
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace advss {
|
||||
void SavePluginSettings(obs_data_t *);
|
||||
EXPORT void LoadPluginSettings(obs_data_t *);
|
||||
|
||||
void AddEarlySaveStep(std::function<void(obs_data_t *)>);
|
||||
void AddEarlyLoadStep(std::function<void(obs_data_t *)>);
|
||||
EXPORT void AddSaveStep(std::function<void(obs_data_t *)>);
|
||||
EXPORT void AddLoadStep(std::function<void(obs_data_t *)>);
|
||||
EXPORT void AddPostLoadStep(std::function<void()>);
|
||||
|
||||
@@ -173,11 +173,10 @@ void PopulateTransitionSelection(QComboBox *sel, bool addCurrent, bool addAny,
|
||||
void PopulateWindowSelection(QComboBox *sel, bool addSelect)
|
||||
{
|
||||
|
||||
std::vector<std::string> windows;
|
||||
GetWindowList(windows);
|
||||
const auto windows = GetWindows();
|
||||
|
||||
for (std::string &window : windows) {
|
||||
sel->addItem(window.c_str());
|
||||
for (const auto &info : windows) {
|
||||
sel->addItem(info.title.c_str());
|
||||
}
|
||||
|
||||
sel->model()->sort(0);
|
||||
@@ -257,8 +256,7 @@ void PopulateMediaSelection(QComboBox *sel, bool addSelect)
|
||||
|
||||
void PopulateProcessSelection(QComboBox *sel, bool addSelect)
|
||||
{
|
||||
QStringList processes;
|
||||
GetProcessList(processes);
|
||||
auto processes = GetProcessList();
|
||||
processes.sort();
|
||||
for (QString &process : processes) {
|
||||
sel->addItem(process);
|
||||
|
||||
@@ -162,4 +162,48 @@ bool IsMediaSource(obs_source_t *source)
|
||||
return (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) != 0;
|
||||
}
|
||||
|
||||
SourceActiveKeeper::~SourceActiveKeeper()
|
||||
{
|
||||
ReleaseRef();
|
||||
}
|
||||
|
||||
void SourceActiveKeeper::SetActive(bool active)
|
||||
{
|
||||
if (_active == active) {
|
||||
return;
|
||||
}
|
||||
if (active) {
|
||||
_active = true;
|
||||
AcquireRef();
|
||||
} else {
|
||||
ReleaseRef();
|
||||
_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SourceActiveKeeper::SetSource(obs_source_t *source)
|
||||
{
|
||||
if (_source == source) {
|
||||
return;
|
||||
}
|
||||
ReleaseRef();
|
||||
_source = source;
|
||||
AcquireRef();
|
||||
}
|
||||
|
||||
void SourceActiveKeeper::AcquireRef()
|
||||
{
|
||||
if (_active && _source) {
|
||||
obs_source_inc_active(_source);
|
||||
}
|
||||
}
|
||||
|
||||
void SourceActiveKeeper::ReleaseRef()
|
||||
{
|
||||
if (_active && _source) {
|
||||
obs_source_dec_active(_source);
|
||||
_source = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -20,4 +20,25 @@ EXPORT OBSWeakSource GetWeakFilterByQString(OBSWeakSource source,
|
||||
EXPORT int GetSceneItemCount(const OBSWeakSource &);
|
||||
EXPORT bool IsMediaSource(obs_source_t *source);
|
||||
|
||||
// RAII helper that keeps an OBS source active (via obs_source_inc_active /
|
||||
// obs_source_dec_active) for as long as the keeper is enabled.
|
||||
class EXPORT SourceActiveKeeper {
|
||||
public:
|
||||
SourceActiveKeeper() = default;
|
||||
~SourceActiveKeeper();
|
||||
|
||||
SourceActiveKeeper(const SourceActiveKeeper &) = delete;
|
||||
SourceActiveKeeper &operator=(const SourceActiveKeeper &) = delete;
|
||||
|
||||
void SetActive(bool active);
|
||||
void SetSource(obs_source_t *source);
|
||||
|
||||
private:
|
||||
void AcquireRef();
|
||||
void ReleaseRef();
|
||||
|
||||
OBSSource _source;
|
||||
bool _active = false;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -66,4 +66,15 @@ void Lockable::WithLock(const std::function<void()> &func)
|
||||
func();
|
||||
}
|
||||
|
||||
SuspendLock::SuspendLock(Lockable &lockable)
|
||||
: _mtx(static_cast<std::mutex &>(lockable._mtx))
|
||||
{
|
||||
_mtx.unlock();
|
||||
}
|
||||
|
||||
SuspendLock::~SuspendLock()
|
||||
{
|
||||
_mtx.lock();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -51,6 +51,24 @@ public:
|
||||
|
||||
private:
|
||||
PerInstanceMutex _mtx;
|
||||
|
||||
friend class SuspendLock;
|
||||
};
|
||||
|
||||
// RAII guard that temporarily releases a Lockable's per-segment lock.
|
||||
// Use this inside PerformAction() / CheckCondition() to unblock the UI
|
||||
// during long-running operations while still running on the original segment.
|
||||
// The caller MUST be holding the lock (i.e. be inside WithLock) when
|
||||
// constructing this object; the lock is re-acquired on destruction.
|
||||
class EXPORT SuspendLock {
|
||||
public:
|
||||
SuspendLock(Lockable &lockable);
|
||||
~SuspendLock();
|
||||
SuspendLock(const SuspendLock &) = delete;
|
||||
SuspendLock &operator=(const SuspendLock &) = delete;
|
||||
|
||||
private:
|
||||
std::mutex &_mtx;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -5,14 +5,26 @@
|
||||
#include "macro-edit.hpp"
|
||||
#include "macro-segment.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "sync-helpers.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "variable.hpp"
|
||||
|
||||
#include <QVariant>
|
||||
#include <QAbstractItemView>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QPushButton>
|
||||
#include <QToolButton>
|
||||
#include <QVariant>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
Q_DECLARE_METATYPE(advss::TempVariableRef);
|
||||
|
||||
static std::atomic<uint64_t> tempVarInUseGeneration{0};
|
||||
|
||||
namespace advss {
|
||||
|
||||
TempVariable::TempVariable(const std::string &id, const std::string &name,
|
||||
@@ -120,6 +132,60 @@ TempVariableRef TempVariable::GetRef() const
|
||||
return ref;
|
||||
}
|
||||
|
||||
static bool refsContain(const std::vector<TempVariableRef> &refs,
|
||||
const TempVariableRef &ref)
|
||||
{
|
||||
for (const auto &r : refs) {
|
||||
if (r == ref) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static bool segmentsReferTo(const T &segments, const TempVariableRef &ref)
|
||||
{
|
||||
for (const auto &segment : segments) {
|
||||
if (refsContain(segment->GetTempVarRefs(), ref)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TempVariable::IsInUse() const
|
||||
{
|
||||
const auto currentGen =
|
||||
tempVarInUseGeneration.load(std::memory_order_relaxed);
|
||||
if (_isInUseCacheGeneration == currentGen) {
|
||||
return _isInUseCache;
|
||||
}
|
||||
|
||||
bool inUse = false;
|
||||
const auto ref = GetRef();
|
||||
|
||||
if (ref.HasValidID()) {
|
||||
for (const auto ¯o : GetAllMacros()) {
|
||||
if (segmentsReferTo(macro->Conditions(), ref) ||
|
||||
segmentsReferTo(macro->Actions(), ref) ||
|
||||
segmentsReferTo(macro->ElseActions(), ref)) {
|
||||
inUse = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_isInUseCache = inUse;
|
||||
_isInUseCacheGeneration = currentGen;
|
||||
return inUse;
|
||||
}
|
||||
|
||||
void IncrementTempVarInUseGeneration()
|
||||
{
|
||||
tempVarInUseGeneration.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
TempVariableRef::SegmentType TempVariableRef::GetType() const
|
||||
{
|
||||
auto segment = _segment.lock();
|
||||
@@ -665,13 +731,302 @@ TempVarSignalManager *TempVarSignalManager::Instance()
|
||||
|
||||
void NotifyUIAboutTempVarChange(MacroSegment *segment)
|
||||
{
|
||||
obs_queue_task(
|
||||
OBS_TASK_UI,
|
||||
IncrementTempVarInUseGeneration();
|
||||
QueueUITask(
|
||||
[](void *segment) {
|
||||
TempVarSignalManager::Instance()->SegmentTempVarsChanged(
|
||||
(MacroSegment *)segment);
|
||||
},
|
||||
segment, false);
|
||||
segment);
|
||||
}
|
||||
|
||||
TempVarOutputMappingsWidget::TempVarOutputMappingsWidget(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_rowsLayout(new QVBoxLayout()),
|
||||
_addButton(new QPushButton(
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.outputMappings.add"),
|
||||
this)),
|
||||
_animation(new QPropertyAnimation(this, "maximumHeight", this))
|
||||
{
|
||||
_rowsLayout->setContentsMargins(0, 0, 0, 0);
|
||||
_rowsLayout->setSpacing(2);
|
||||
auto rowsContainer = new QWidget(this);
|
||||
rowsContainer->setLayout(_rowsLayout);
|
||||
|
||||
auto label = new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.outputMappings"),
|
||||
this);
|
||||
|
||||
auto mainLayout = new QVBoxLayout();
|
||||
mainLayout->addWidget(label);
|
||||
mainLayout->addWidget(rowsContainer);
|
||||
mainLayout->addWidget(_addButton);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_animation->setDuration(300);
|
||||
_animation->setEasingCurve(QEasingCurve::InOutQuad);
|
||||
connect(_animation, &QPropertyAnimation::finished, this,
|
||||
&TempVarOutputMappingsWidget::AnimationFinished);
|
||||
|
||||
hide();
|
||||
|
||||
connect(_addButton, &QPushButton::clicked, this,
|
||||
&TempVarOutputMappingsWidget::Add);
|
||||
connect(TempVarSignalManager::Instance(),
|
||||
SIGNAL(SegmentTempVarsChanged(MacroSegment *)), this,
|
||||
SLOT(SegmentTempVarsChanged(MacroSegment *)));
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::SetSegment(MacroSegment *segment)
|
||||
{
|
||||
_segment = segment;
|
||||
_panelExpanded = false;
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::Add()
|
||||
{
|
||||
if (!_segment) {
|
||||
return;
|
||||
}
|
||||
auto mappings = _segment->GetVarMappings();
|
||||
mappings.push_back(VarMapping{});
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_segment->SetVarMappings(mappings);
|
||||
}
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::Remove(int rowIdx)
|
||||
{
|
||||
if (!_segment) {
|
||||
return;
|
||||
}
|
||||
auto mappings = _segment->GetVarMappings();
|
||||
if (rowIdx < 0 || rowIdx >= (int)mappings.size()) {
|
||||
return;
|
||||
}
|
||||
mappings.erase(mappings.begin() + rowIdx);
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_segment->SetVarMappings(mappings);
|
||||
}
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::SegmentTempVarsChanged(MacroSegment *segment)
|
||||
{
|
||||
if (segment != _segment) {
|
||||
return;
|
||||
}
|
||||
// Re-populate each temp var combobox in case temp vars were added/removed
|
||||
_loading = true;
|
||||
const auto &mappings = _segment->GetVarMappings();
|
||||
for (int i = 0; i < (int)_rows.size(); i++) {
|
||||
auto combo = _rows[i].tempVarCombo;
|
||||
const QSignalBlocker blocker(combo);
|
||||
combo->clear();
|
||||
PopulateTempVarCombo(combo);
|
||||
if (i < (int)mappings.size() &&
|
||||
mappings[i].tempVar.HasValidID()) {
|
||||
QVariant v;
|
||||
v.setValue(mappings[i].tempVar);
|
||||
combo->setCurrentIndex(combo->findData(v));
|
||||
}
|
||||
}
|
||||
_loading = false;
|
||||
UpdateVisibility();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::WriteBackMappings()
|
||||
{
|
||||
if (_loading || !_segment) {
|
||||
return;
|
||||
}
|
||||
std::vector<VarMapping> mappings;
|
||||
for (const auto &row : _rows) {
|
||||
VarMapping m;
|
||||
const int idx = row.tempVarCombo->currentIndex();
|
||||
if (idx >= 0) {
|
||||
m.tempVar = row.tempVarCombo->itemData(idx)
|
||||
.value<TempVariableRef>();
|
||||
}
|
||||
|
||||
auto item = row.varSelection->GetCurrentItem();
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m.variable = GetWeakVariableByName(item->Name());
|
||||
mappings.push_back(std::move(m));
|
||||
}
|
||||
auto lock = LockContext();
|
||||
_segment->SetVarMappings(mappings);
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::Rebuild()
|
||||
{
|
||||
_loading = true;
|
||||
|
||||
// Remove all existing row widgets from layout and delete them
|
||||
for (auto &row : _rows) {
|
||||
_rowsLayout->removeWidget(row.container);
|
||||
delete row.container;
|
||||
}
|
||||
_rows.clear();
|
||||
|
||||
if (!_segment) {
|
||||
_loading = false;
|
||||
UpdateVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &mappings = _segment->GetVarMappings();
|
||||
for (int i = 0; i < (int)mappings.size(); i++) {
|
||||
const auto &mapping = mappings[i];
|
||||
|
||||
auto container = new QWidget(this);
|
||||
auto rowLayout = new QHBoxLayout();
|
||||
rowLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto tempVarCombo = new FilterComboBox(
|
||||
container,
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.select"));
|
||||
tempVarCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
tempVarCombo->setMaximumWidth(350);
|
||||
tempVarCombo->setDuplicatesEnabled(true);
|
||||
PopulateTempVarCombo(tempVarCombo);
|
||||
if (mapping.tempVar.HasValidID()) {
|
||||
QVariant v;
|
||||
v.setValue(mapping.tempVar);
|
||||
tempVarCombo->setCurrentIndex(
|
||||
tempVarCombo->findData(v));
|
||||
}
|
||||
|
||||
auto arrowLabel = new QLabel(container);
|
||||
{
|
||||
QIcon icon;
|
||||
const auto path = (GetThemeTypeName() == "Light")
|
||||
? "theme:Light/right.svg"
|
||||
: "theme:Dark/right.svg";
|
||||
icon.addFile(QString::fromUtf8(path), QSize(),
|
||||
QIcon::Normal, QIcon::Off);
|
||||
arrowLabel->setPixmap(icon.pixmap(16, 16));
|
||||
}
|
||||
|
||||
auto varSel = new VariableSelection(container);
|
||||
varSel->SetVariable(mapping.variable);
|
||||
|
||||
auto removeBtn = new QToolButton(container);
|
||||
removeBtn->setProperty("themeID", QVariant(QString::fromUtf8(
|
||||
"removeIconSmall")));
|
||||
removeBtn->setProperty(
|
||||
"class", QVariant(QString::fromUtf8("icon-trash")));
|
||||
removeBtn->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.outputMappings.remove"));
|
||||
|
||||
rowLayout->addWidget(tempVarCombo, 1);
|
||||
rowLayout->addWidget(arrowLabel);
|
||||
rowLayout->addWidget(varSel, 1);
|
||||
rowLayout->addWidget(removeBtn);
|
||||
container->setLayout(rowLayout);
|
||||
|
||||
_rowsLayout->addWidget(container);
|
||||
_rows.push_back({container, tempVarCombo, varSel});
|
||||
|
||||
const int capturedIdx = i;
|
||||
connect(removeBtn, &QPushButton::clicked, this,
|
||||
[this, capturedIdx]() { Remove(capturedIdx); });
|
||||
connect(tempVarCombo,
|
||||
QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
this, &TempVarOutputMappingsWidget::WriteBackMappings);
|
||||
connect(varSel, &VariableSelection::SelectionChanged, this,
|
||||
[this](const QString &) { WriteBackMappings(); });
|
||||
}
|
||||
|
||||
_loading = false;
|
||||
UpdateVisibility();
|
||||
}
|
||||
|
||||
bool TempVarOutputMappingsWidget::IsExpandable() const
|
||||
{
|
||||
return _segment && !_segment->GetOwnTempVars().empty();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::SetPanelExpanded(bool expanded)
|
||||
{
|
||||
_panelExpanded = expanded;
|
||||
UpdateHeight();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::SetSectionCollapsed(bool collapsed)
|
||||
{
|
||||
_sectionCollapsed = collapsed;
|
||||
UpdateHeight();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::AnimateTo(int targetHeight)
|
||||
{
|
||||
if (_animation->state() == QAbstractAnimation::Running) {
|
||||
_animation->stop();
|
||||
}
|
||||
|
||||
if (targetHeight > 0 && !isVisible()) {
|
||||
setMinimumHeight(0);
|
||||
setMaximumHeight(0);
|
||||
show();
|
||||
}
|
||||
|
||||
const int currentMax = maximumHeight();
|
||||
const int currentHeight = (currentMax >= QWIDGETSIZE_MAX) ? height()
|
||||
: currentMax;
|
||||
|
||||
_animationTargetHeight = targetHeight;
|
||||
_animation->setStartValue(currentHeight);
|
||||
_animation->setEndValue(targetHeight);
|
||||
_animation->start();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::AnimationFinished()
|
||||
{
|
||||
if (_animationTargetHeight > 0) {
|
||||
setMaximumHeight(QWIDGETSIZE_MAX);
|
||||
} else {
|
||||
hide();
|
||||
setMaximumHeight(QWIDGETSIZE_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::UpdateHeight()
|
||||
{
|
||||
const bool shouldShow = _panelExpanded && !_sectionCollapsed &&
|
||||
IsExpandable();
|
||||
if (shouldShow) {
|
||||
AnimateTo(sizeHint().height());
|
||||
} else {
|
||||
AnimateTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::UpdateVisibility()
|
||||
{
|
||||
const bool expandable = IsExpandable();
|
||||
emit ExpandableChanged(expandable);
|
||||
UpdateHeight();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::PopulateTempVarCombo(
|
||||
FilterComboBox *combo) const
|
||||
{
|
||||
if (!_segment) {
|
||||
return;
|
||||
}
|
||||
for (const auto &var : _segment->GetOwnTempVars()) {
|
||||
QVariant v;
|
||||
v.setValue(var.GetRef());
|
||||
combo->addItem(QString::fromStdString(var.Name()), v);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
#include <obs-data.h>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <QEnterEvent>
|
||||
#include <QEvent>
|
||||
#include <QStringList>
|
||||
#include <string>
|
||||
|
||||
class QPropertyAnimation;
|
||||
class QPushButton;
|
||||
class QVBoxLayout;
|
||||
|
||||
namespace advss {
|
||||
|
||||
class Macro;
|
||||
@@ -17,6 +18,9 @@ class MacroEdit;
|
||||
class MacroSegment;
|
||||
class TempVariableRef;
|
||||
class TempVariableSelection;
|
||||
class TempVarOutputMappingsWidget;
|
||||
class VariableSelection;
|
||||
struct VarMapping;
|
||||
|
||||
// TempVariables are variables that are local to a given macro.
|
||||
// They can be created and used by macro segments.
|
||||
@@ -44,6 +48,7 @@ public:
|
||||
void SetValue(const std::string &val);
|
||||
void InvalidateValue();
|
||||
TempVariableRef GetRef() const;
|
||||
EXPORT bool IsInUse() const;
|
||||
|
||||
private:
|
||||
std::string _id = "";
|
||||
@@ -53,6 +58,8 @@ private:
|
||||
mutable std::mutex _lastValuesMutex;
|
||||
std::vector<std::string> _lastValues;
|
||||
bool _valueIsValid = false;
|
||||
mutable bool _isInUseCache = false;
|
||||
mutable uint64_t _isInUseCacheGeneration = UINT64_MAX;
|
||||
|
||||
std::weak_ptr<MacroSegment> _segment;
|
||||
friend TempVariableSelection;
|
||||
@@ -67,6 +74,7 @@ public:
|
||||
EXPORT std::optional<const TempVariable> GetTempVariable(Macro *) const;
|
||||
EXPORT bool operator==(const TempVariableRef &other) const;
|
||||
bool HasValidID() const { return !_id.empty(); }
|
||||
std::string GetID() const { return _id; }
|
||||
|
||||
private:
|
||||
enum class SegmentType { NONE, CONDITION, ACTION, ELSEACTION };
|
||||
@@ -122,5 +130,50 @@ private:
|
||||
};
|
||||
|
||||
void NotifyUIAboutTempVarChange(MacroSegment *);
|
||||
EXPORT void IncrementTempVarInUseGeneration();
|
||||
|
||||
class ADVSS_EXPORT TempVarOutputMappingsWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TempVarOutputMappingsWidget(QWidget *parent);
|
||||
void SetSegment(MacroSegment *segment);
|
||||
void SetPanelExpanded(bool expanded);
|
||||
void SetSectionCollapsed(bool collapsed);
|
||||
bool IsExpandable() const;
|
||||
|
||||
signals:
|
||||
void ExpandableChanged(bool);
|
||||
|
||||
private slots:
|
||||
void Add();
|
||||
void Remove(int rowIdx);
|
||||
void SegmentTempVarsChanged(MacroSegment *segment);
|
||||
void WriteBackMappings();
|
||||
void AnimationFinished();
|
||||
|
||||
private:
|
||||
void Rebuild();
|
||||
void UpdateVisibility();
|
||||
void UpdateHeight();
|
||||
void AnimateTo(int targetHeight);
|
||||
void PopulateTempVarCombo(FilterComboBox *combo) const;
|
||||
|
||||
struct MappingRow {
|
||||
QWidget *container;
|
||||
FilterComboBox *tempVarCombo;
|
||||
VariableSelection *varSelection;
|
||||
};
|
||||
|
||||
MacroSegment *_segment = nullptr;
|
||||
QVBoxLayout *_rowsLayout;
|
||||
QPushButton *_addButton;
|
||||
std::vector<MappingRow> _rows;
|
||||
bool _loading = false;
|
||||
bool _panelExpanded = false;
|
||||
bool _sectionCollapsed = false;
|
||||
int _animationTargetHeight = 0;
|
||||
QPropertyAnimation *_animation;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -9,11 +9,12 @@ void StringVariable::Resolve() const
|
||||
_resolvedValue = _value;
|
||||
return;
|
||||
}
|
||||
if (_lastResolve == GetLastVariableChangeTime()) {
|
||||
const auto lastChange = GetLastVariableChangeTime();
|
||||
if (_lastResolve == lastChange) {
|
||||
return;
|
||||
}
|
||||
_resolvedValue = SubstitueVariables(_value);
|
||||
_lastResolve = GetLastVariableChangeTime();
|
||||
_lastResolve = lastChange;
|
||||
}
|
||||
|
||||
StringVariable::operator std::string() const
|
||||
@@ -81,7 +82,7 @@ std::string SubstitueVariables(std::string str)
|
||||
const auto &variable = std::dynamic_pointer_cast<Variable>(v);
|
||||
const std::string pattern = "${" + variable->Name() + "}";
|
||||
if (ReplaceAll(str, pattern, variable->Value(false))) {
|
||||
variable->UpdateLastUsed();
|
||||
variable->MarkAsUsed();
|
||||
}
|
||||
}
|
||||
return str;
|
||||
|
||||
@@ -29,6 +29,7 @@ static void save(obs_data_t *data)
|
||||
{
|
||||
tabSettings.Save(data, "tabSettings");
|
||||
dockSettings.Save(data, "dockSettings");
|
||||
|
||||
obs_data_set_bool(data, "addVariablesDock", addDock);
|
||||
}
|
||||
|
||||
@@ -63,7 +64,6 @@ static void load(obs_data_t *data)
|
||||
{
|
||||
tabSettings.Load(data, "tabSettings");
|
||||
dockSettings.Load(data, "dockSettings");
|
||||
|
||||
enableDock(obs_data_get_bool(data, "addVariablesDock"));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#include "variable.hpp"
|
||||
#include "math-helpers.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <obs.hpp>
|
||||
|
||||
#include <QGridLayout>
|
||||
|
||||
namespace advss {
|
||||
@@ -12,16 +15,32 @@ static std::deque<std::shared_ptr<Item>> variables;
|
||||
|
||||
// Keep track of the last time a variable was changed to save some work when
|
||||
// when resolving strings containing variables, etc.
|
||||
static std::mutex lastVariableChangeMutex;
|
||||
static std::chrono::high_resolution_clock::time_point lastVariableChange{};
|
||||
|
||||
static bool setup()
|
||||
{
|
||||
AddEarlySaveStep(SaveVariables);
|
||||
AddEarlyLoadStep(LoadVariables);
|
||||
AddPluginCleanupStep([]() { variables.clear(); });
|
||||
return true;
|
||||
}
|
||||
static bool setupDone = setup();
|
||||
|
||||
static void setLastVariableChangeTime()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lastVariableChangeMutex);
|
||||
lastVariableChange = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
Variable::Variable() : Item()
|
||||
{
|
||||
lastVariableChange = std::chrono::high_resolution_clock::now();
|
||||
setLastVariableChangeTime();
|
||||
}
|
||||
|
||||
Variable::~Variable()
|
||||
{
|
||||
lastVariableChange = std::chrono::high_resolution_clock::now();
|
||||
setLastVariableChangeTime();
|
||||
}
|
||||
|
||||
void Variable::Load(obs_data_t *obj)
|
||||
@@ -37,7 +56,7 @@ void Variable::Load(obs_data_t *obj)
|
||||
SetValue(_defaultValue);
|
||||
}
|
||||
|
||||
lastVariableChange = std::chrono::high_resolution_clock::now();
|
||||
setLastVariableChangeTime();
|
||||
}
|
||||
|
||||
void Variable::Save(obs_data_t *obj) const
|
||||
@@ -45,8 +64,11 @@ void Variable::Save(obs_data_t *obj) const
|
||||
Item::Save(obj);
|
||||
obs_data_set_int(obj, "saveAction", static_cast<int>(_saveAction));
|
||||
|
||||
if (_saveAction == SaveAction::SAVE) {
|
||||
obs_data_set_string(obj, "value", _value.c_str());
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (_saveAction == SaveAction::SAVE) {
|
||||
obs_data_set_string(obj, "value", _value.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
obs_data_set_string(obj, "defaultValue", _defaultValue.c_str());
|
||||
@@ -62,6 +84,18 @@ std::string Variable::Value(bool updateLastUsed) const
|
||||
return _value;
|
||||
}
|
||||
|
||||
std::string Variable::GetPreviousValue() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
return _previousValue;
|
||||
}
|
||||
|
||||
int Variable::GetValueChangeCount() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
return _valueChangeCount;
|
||||
}
|
||||
|
||||
std::optional<double> Variable::DoubleValue() const
|
||||
{
|
||||
return GetDouble(Value());
|
||||
@@ -79,8 +113,11 @@ void Variable::SetValue(const std::string &value)
|
||||
_value = value;
|
||||
|
||||
UpdateLastUsed();
|
||||
UpdateLastChanged();
|
||||
lastVariableChange = std::chrono::high_resolution_clock::now();
|
||||
if (_previousValue != _value) {
|
||||
_lastChanged = std::chrono::high_resolution_clock::now();
|
||||
++_valueChangeCount;
|
||||
}
|
||||
setLastVariableChangeTime();
|
||||
}
|
||||
|
||||
void Variable::SetValue(double value)
|
||||
@@ -90,6 +127,7 @@ void Variable::SetValue(double value)
|
||||
|
||||
std::optional<uint64_t> Variable::GetSecondsSinceLastUse() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (_lastUsed.time_since_epoch().count() == 0) {
|
||||
return {};
|
||||
}
|
||||
@@ -101,6 +139,7 @@ std::optional<uint64_t> Variable::GetSecondsSinceLastUse() const
|
||||
|
||||
std::optional<uint64_t> Variable::GetSecondsSinceLastChange() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (_lastChanged.time_since_epoch().count() == 0) {
|
||||
return {};
|
||||
}
|
||||
@@ -116,12 +155,10 @@ void Variable::UpdateLastUsed() const
|
||||
_lastUsed = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void Variable::UpdateLastChanged()
|
||||
void Variable::MarkAsUsed() const
|
||||
{
|
||||
if (_previousValue != _value) {
|
||||
_lastChanged = std::chrono::high_resolution_clock::now();
|
||||
++_valueChangeCount;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
UpdateLastUsed();
|
||||
}
|
||||
|
||||
static void populateSaveActionSelection(QComboBox *list)
|
||||
@@ -206,7 +243,7 @@ bool VariableSettingsDialog::AskForSettings(QWidget *parent, Variable &settings)
|
||||
dialog._defaultValue->toPlainText().toStdString();
|
||||
settings._saveAction =
|
||||
static_cast<Variable::SaveAction>(dialog._save->currentIndex());
|
||||
lastVariableChange = std::chrono::high_resolution_clock::now();
|
||||
setLastVariableChangeTime();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -393,34 +430,31 @@ static bool variableWithNameExists(const std::string &name)
|
||||
|
||||
void SaveVariables(obs_data_t *obj)
|
||||
{
|
||||
obs_data_array_t *variablesArray = obs_data_array_create();
|
||||
OBSDataArrayAutoRelease variablesArray = obs_data_array_create();
|
||||
for (const auto &v : variables) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
OBSDataAutoRelease array_obj = obs_data_create();
|
||||
v->Save(array_obj);
|
||||
obs_data_array_push_back(variablesArray, array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
|
||||
obs_data_set_array(obj, "variables", variablesArray);
|
||||
obs_data_array_release(variablesArray);
|
||||
}
|
||||
|
||||
void LoadVariables(obs_data_t *obj)
|
||||
{
|
||||
variables.clear();
|
||||
|
||||
obs_data_array_t *variablesArray = obs_data_get_array(obj, "variables");
|
||||
OBSDataArrayAutoRelease variablesArray =
|
||||
obs_data_get_array(obj, "variables");
|
||||
size_t count = obs_data_array_count(variablesArray);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *array_obj = obs_data_array_item(variablesArray, i);
|
||||
OBSDataAutoRelease array_obj =
|
||||
obs_data_array_item(variablesArray, i);
|
||||
auto var = Variable::Create();
|
||||
variables.emplace_back(var);
|
||||
variables.back()->Load(array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
|
||||
obs_data_array_release(variablesArray);
|
||||
}
|
||||
|
||||
static void signalImportedVariables(void *varsPtr)
|
||||
@@ -435,16 +469,15 @@ static void signalImportedVariables(void *varsPtr)
|
||||
|
||||
void ImportVariables(obs_data_t *data)
|
||||
{
|
||||
obs_data_array_t *array = obs_data_get_array(data, "variables");
|
||||
OBSDataArrayAutoRelease array = obs_data_get_array(data, "variables");
|
||||
size_t count = obs_data_array_count(array);
|
||||
|
||||
auto importedVars = new std::vector<std::shared_ptr<Item>>;
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *arrayElement = obs_data_array_item(array, i);
|
||||
OBSDataAutoRelease arrayElement = obs_data_array_item(array, i);
|
||||
auto var = Variable::Create();
|
||||
var->Load(arrayElement);
|
||||
obs_data_release(arrayElement);
|
||||
|
||||
if (variableWithNameExists(var->Name())) {
|
||||
continue;
|
||||
@@ -454,13 +487,12 @@ void ImportVariables(obs_data_t *data)
|
||||
importedVars->emplace_back(var);
|
||||
}
|
||||
|
||||
obs_data_array_release(array);
|
||||
|
||||
QueueUITask(signalImportedVariables, importedVars);
|
||||
}
|
||||
|
||||
std::chrono::high_resolution_clock::time_point GetLastVariableChangeTime()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lastVariableChangeMutex);
|
||||
return lastVariableChange;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,16 +34,15 @@ public:
|
||||
EXPORT std::string Value(bool updateLastUsed = true) const;
|
||||
EXPORT std::optional<double> DoubleValue() const;
|
||||
EXPORT std::optional<int> IntValue() const;
|
||||
std::string GetPreviousValue() const { return _previousValue; };
|
||||
std::string GetPreviousValue() const;
|
||||
std::string GetDefaultValue() const { return _defaultValue; }
|
||||
EXPORT void SetValue(const std::string &value);
|
||||
void SetValue(double value);
|
||||
SaveAction GetSaveAction() const { return _saveAction; }
|
||||
int GetValueChangeCount() const { return _valueChangeCount; }
|
||||
int GetValueChangeCount() const;
|
||||
std::optional<uint64_t> GetSecondsSinceLastUse() const;
|
||||
std::optional<uint64_t> GetSecondsSinceLastChange() const;
|
||||
void UpdateLastUsed() const;
|
||||
void UpdateLastChanged();
|
||||
void MarkAsUsed() const;
|
||||
|
||||
private:
|
||||
SaveAction _saveAction = SaveAction::DONT_SAVE;
|
||||
@@ -55,6 +54,8 @@ private:
|
||||
mutable std::chrono::high_resolution_clock::time_point _lastChanged;
|
||||
mutable std::mutex _mutex;
|
||||
|
||||
void UpdateLastUsed() const;
|
||||
|
||||
friend VariableSelection;
|
||||
friend VariableSettingsDialog;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user