Compare commits

..

71 Commits

Author SHA1 Message Date
WarmUpTill
9bcced524b Move transition behaviour related settings to General tab 2022-11-09 09:57:55 -08:00
WarmUpTill
3bb2ddfaac Update GitHub Actions for set-output deprecation
GitHub Actions has deprecated set-output. Replace usages of set-output
in stdout with the new syntax to save the output to the new environment
variable.

See:
https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
2022-11-06 09:53:25 -08:00
WarmUpTill
50e26aba72 Improve focus handling
* Display current focus window / process
 * Match against app name instead of window name on MacOS when using
   Process condition
 * Clean up
2022-11-06 09:53:25 -08:00
WarmUpTill
267ab6a7c1 Set Windows installer version to git tag 2022-11-05 12:24:50 -07:00
WarmUpTill
65ea7987c7 Add special handling for the "between" case when ignoring the date
In this case the left time value will be treated as the start of the
time range and the right one as the end.

This now enables specifying ranges that span over the 24h boundary.
E.g. 23:00:00 to 01:00:00.
This would have previously been treated as 01:00:00 to 23:00:00 instead.
2022-11-02 07:57:01 -07:00
WarmUpTill
a6839666ce Fix scene item selection not saving index 2022-11-02 07:56:37 -07:00
WarmUpTill
e5843de4fc Add option to check for average brightness 2022-10-21 13:15:11 -07:00
WarmUpTill
a330378c16 Improve option to check multiple media sources
* Added the option to select current, previous, variables, ...
 * Refresh the source list on scene change
 * General clean up of media condition
2022-10-21 13:15:00 -07:00
WarmUpTill
ddb7888e61 Enable word wrap for "on change" and "parallel" options on macro tab 2022-10-21 13:14:35 -07:00
WarmUpTill
73aef4f0b2 Workaround for macro drag / drop not working as expected
See:
 * QTBUG-106395
 * https://github.com/obsproject/obs-studio/issues/7321
2022-10-13 10:29:39 -07:00
WarmUpTill
67c3b73e10 Fix date condition showing incorrect date
This is only affecting the displayed value when reopening a macro.
The entered values were stored / used correctly.
2022-10-08 15:59:15 -07:00
WarmUpTill
9a3d381cf0 Improve macro examples 2022-10-08 15:59:07 -07:00
WarmUpTill
8b8829f7df Fix scene item selection not returning expected number of items 2022-10-04 23:35:58 -07:00
WarmUpTill
6fff3b54e6 Remove version number from dep package name 2022-10-04 12:01:47 -07:00
WarmUpTill
dd69ca1b35 Add libprocps-dev 2022-10-04 02:59:59 -07:00
WarmUpTill
66ddf3b4ef Rebase volume control widgets to new OBS version 2022-10-04 02:59:59 -07:00
WarmUpTill
c9b5bbfc14 Fix multi state count not being saved 2022-10-04 02:59:59 -07:00
WarmUpTill
3e1e8d01b6 Add option to check for value change 2022-10-04 02:59:59 -07:00
WarmUpTill
c22f40e333 Set SOVERSION to resolve Debian warning 2022-10-04 02:59:36 -07:00
WarmUpTill
b83acaca79 Set rpath for Debian package 2022-10-04 02:59:36 -07:00
Joao Eriberto Mota Filho
53bef18d15 Fix spelling and double word 2022-10-01 10:14:49 -07:00
WarmUpTill
ced1c7605a Add option to set default value on load 2022-10-01 08:24:11 -07:00
WarmUpTill
f78e91eec4 Add compatibility note 2022-10-01 08:24:11 -07:00
WarmUpTill
7fdff72a92 Only show duration selection if dynamic length transition is selected 2022-10-01 08:24:11 -07:00
WarmUpTill
d2d042bae3 Move transition type definition 2022-10-01 08:24:11 -07:00
WarmUpTill
f70ae803a3 Enable creating screenshots of scenes 2022-10-01 01:58:57 -07:00
WarmUpTill
cdefbf383c Add preview scene support 2022-10-01 01:58:57 -07:00
ubuntu
5f8641b18a Adjust cmake to support LIB_OUT_DIR and DATA_OUT_DIR for debian build 2022-10-01 01:52:22 -07:00
ubuntu
d107f653de Fix warnings 2022-10-01 01:52:22 -07:00
WarmUpTill
22f4c11ee3 Hide Scene Groups tab
The tabs functionality is often confused with the sequence tab.
With the introduction of variables / macros this tab is unlikely to find
much use so also hide when legacy-tabs are hidden.
2022-09-29 10:59:08 -07:00
WarmUpTill
7683957267 Add variable support for scene item selection 2022-09-28 02:21:05 -07:00
WarmUpTill
e0d4c7244d Add variable support for scene selection 2022-09-28 02:21:05 -07:00
WarmUpTill
b2ccd30f68 Clean up SceneSelection
* Adjust save and load functions to avoid conflicts with other values
 * Move enum definition
2022-09-28 02:21:05 -07:00
WarmUpTill
1dea2e5f5d Adjust to new Item, ItemSelection, and ItemSettingsDialog classes 2022-09-28 02:21:05 -07:00
WarmUpTill
c95fb6c2ea Add macro action "Variables" 2022-09-28 02:21:05 -07:00
WarmUpTill
d779ced27f Add macro condition "Variable" 2022-09-28 02:21:05 -07:00
WarmUpTill
c84c1638bf Add support for variables 2022-09-28 02:21:05 -07:00
WarmUpTill
3ac4e22600 Add generic item selection and settings configuration widgets
To be used for configuring variables and connections
2022-09-28 02:21:05 -07:00
WarmUpTill
a59d15a77a Add option to disable hotkey registration for new macros 2022-09-28 02:20:22 -07:00
Joao Eriberto Mota Filho
c2edc7c612 Fix spelling errors in final binary 2022-09-27 11:07:31 -07:00
WarmUpTill
77fe75f738 Fix websocket condition layout when enabling regular expressions 2022-09-24 00:45:58 -07:00
WarmUpTill
fdb70a32c6 Fix splitter position save data size growing on scene collection change 2022-09-24 00:45:58 -07:00
WarmUpTill
487db7302e Update OBS version 2022-09-21 10:04:09 -07:00
ubuntu
12a74caf62 Add obs-websocket-api.h to deps/
Using a submodule which is not available as a package on Debian makes it
difficult to package the plugin for Debian.

Instead add the obs-websocket header file directly to the repo.
2022-09-21 10:04:09 -07:00
ubuntu
742996fc83 Remove submodule deps/obs-websocket
Using a submodule which is not available as a package on Debian makes it
difficult to package the plugin for Debian.

Instead add the obs-websocket header file directly to the repo.
2022-09-21 10:04:09 -07:00
WarmUpTill
f5a0b2e91a Reintroduce debian build workflow 2022-09-20 14:01:22 -07:00
WarmUpTill
114ca7b545 Remove unused file 2022-09-20 14:01:22 -07:00
WarmUpTill
a8953f23f4 Add backwards compatability to at least OBS 26.1.2
Required for Debian Stable
2022-09-20 14:01:22 -07:00
WarmUpTill
106be642aa Set CPACK_DEBIAN_PACKAGE_DEPENDS to OBS 28 and correct Qt version 2022-09-19 11:36:28 -07:00
WarmUpTill
4c20ac605d Fix UI not showing correct logic type if removing root condition 2022-09-19 11:36:28 -07:00
WarmUpTill
75cdd3c80d Add option to reduce video matching latency 2022-09-19 11:36:28 -07:00
WarmUpTill
b2585ae176 Fix potential crash in video condition 2022-09-19 11:36:28 -07:00
WarmUpTill
d1e912cb24 Clean up video condition widget
* only show area selection widget if option is enabled
 * fix slight alignment issue
 * move "Show match" button to the bottom of the widget
2022-09-19 11:36:28 -07:00
WarmUpTill
bd5ecb3b3b Add "slideshow" to list of supported media sources 2022-09-17 00:40:10 -07:00
WarmUpTill
1a8a4584c1 Add support for websocket vendor events 2022-09-16 23:55:36 -07:00
WarmUpTill
6d755ad570 Add content / modification date changed as separate options
Previously this would require knowledge of regular expressions to match
any content.
2022-09-15 12:25:37 -07:00
WarmUpTill
99ceb6a42a Style changes
* Move FileType definition
* Change capitalization
2022-09-15 12:25:37 -07:00
WarmUpTill
7ddfdcf5be Switch to RegexConfig 2022-09-15 12:25:37 -07:00
WarmUpTill
a243e026c8 Add RegexConfig class and widget
This allow more control over regular expressions, when they are used
throughout the plugin, as it enables configuration of additional
QRegularExpression::PatternOptions.
2022-09-15 12:25:37 -07:00
WarmUpTill
f40f98c702 Move setButtonIcon to utility 2022-09-15 12:25:37 -07:00
WarmUpTill
d99618d2f6 Enable multiline regex pattern matching 2022-09-15 12:25:37 -07:00
WarmUpTill
a93305e16e Convert line endings to "\n" to allow easier matching 2022-09-15 12:25:37 -07:00
WarmUpTill
fa3ae607b5 Fix missing icon on macro up / down buttons 2022-09-07 12:08:01 -07:00
WarmUpTill
bddbcebece Slight layout adjustments to better support new OBS themes 2022-09-05 12:19:07 -07:00
WarmUpTill
aeaa13955c Hide password 2022-09-05 12:19:07 -07:00
WarmUpTill
f99eb6c3c8 Set sensible default paths when browsing using FileSelection
Previously FileSelection would default to the OBS run dir
2022-09-03 10:31:47 -07:00
WarmUpTill
c16b97e5cd Add option to save screenshot to custom path 2022-09-03 10:31:47 -07:00
WarmUpTill
0d29fb2ee1 Add support to save screenshots to file 2022-09-03 10:31:47 -07:00
WarmUpTill
0169f6c445 Drop 32 bit Windows build 2022-09-02 16:00:18 -07:00
WarmUpTill
886639fe24 Drop version number in name of generated build artifacts 2022-09-02 16:00:18 -07:00
WarmUpTill
7304fa349e Build both portable and non-portable artifacts for Linux 2022-09-02 16:00:18 -07:00
112 changed files with 5401 additions and 1610 deletions

View File

@@ -20,6 +20,10 @@ inputs:
description: 'Visual Studio version (Windows only)'
required: false
default: 'Visual Studio 16 2019'
portable:
description: 'Set portable mode (Linux only)'
required: false
default: 'false'
workingDirectory:
description: 'Working directory for packaging'
required: false
@@ -56,6 +60,10 @@ runs:
build_args+=(--debug)
fi
if [[ '${{ inputs.portable }}' == 'true' ]]; then
build_args+=(-p)
fi
${{ inputs.workingDirectory }}/.github/scripts/build-linux.sh "${build_args[@]}"
- name: Run Windows Build

View File

@@ -36,6 +36,10 @@ inputs:
description: 'Create InnoSetup installer (Windows only)'
required: false
default: 'false'
portable:
description: 'Create deb package / portable archive (Linux only)'
required: false
default: 'false'
workingDirectory:
description: 'Working directory for packaging'
required: false
@@ -75,6 +79,10 @@ runs:
build_args+=(--debug)
fi
if [[ '${{ inputs.portable }}' == 'true' ]]; then
package_args+=(-z)
fi
${{ inputs.workingDirectory }}/.github/scripts/package-linux.sh "${package_args[@]}"
- name: Run Windows packaging

View File

@@ -76,6 +76,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
%B-t | --target%b Specify target - default: %B%F{green}${host_os}-${CPUTYPE}%f%b
%B-c | --config%b Build configuration - default: %B%F{green}RelWithDebInfo%f%b
%B-s | --codesign%b Enable codesigning (macOS only)
%B-p | --portable%b Enable portable mode (Linux only)
%B--generator%b Specify build system to generate - default: %B%F{green}Ninja%f%b
Available generators:
- Ninja
@@ -130,6 +131,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
shift 2
;;
-s|--codesign) CODESIGN=1; shift ;;
-p|--portable) typeset -g PORTABLE=1; shift ;;
-q|--quiet) (( _verbosity -= 1 )) || true; shift ;;
-v|--verbose) (( _verbosity += 1 )); shift ;;
-h|--help) log_output ${_usage}; exit 0 ;;
@@ -245,7 +247,11 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
;;
linux-*)
if (( ${+CI} )) {
if (( ${+PORTABLE} )) {
cmake_args+=(
-DLINUX_PORTABLE=ON
)
} else {
cmake_args+=(
-DCMAKE_INSTALL_PREFIX=/usr
-DLINUX_PORTABLE=OFF

View File

@@ -61,6 +61,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
%B-c | --config%b Build configuration - default: %B%F{green}RelWithDebInfo%f%b
%B-s | --codesign%b Enable codesigning (macOS only)
%B-n | --notarize%b Enable notarization (macOS only)
%B-z | --zip%b Zip only (Linux only)
%F{yellow} Output options%f
-----------------------------------------------------------------------------
@@ -110,6 +111,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
;;
-s|--codesign) typeset -g CODESIGN=1; shift ;;
-n|--notarize) typeset -g NOTARIZE=1; typeset -g CODESIGN=1; shift ;;
-z|--zip) typeset -g ZIP=1; shift ;;
-q|--quiet) (( _verbosity -= 1 )) || true; shift ;;
-v|--verbose) (( _verbosity += 1 )); shift ;;
-h|--help) log_output ${_usage}; exit 0 ;;
@@ -132,7 +134,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
if [[ ${host_os} == 'macos' ]] {
autoload -Uz check_packages read_codesign read_codesign_installer read_codesign_pass
local output_name="${product_name}-${product_version}-${host_os}-${target##*-}.pkg"
local output_name="${product_name}-${host_os}-${target##*-}.pkg"
if [[ ! -d ${project_root}/release/${product_name}.plugin ]] {
log_error 'No release artifact found. Run the build script or the CMake install procedure first.'
@@ -180,12 +182,19 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
}
popd
} elif [[ ${host_os} == 'linux' ]] {
local -a cmake_args=()
if (( _loglevel > 1 )) cmake_args+=(--verbose)
if (( ${+ZIP} )) {
local output_name="${product_name}-${host_os}-${target##*-}.zip"
pushd ${project_root}/release
zip -r "${output_name}" *
popd
} else {
local -a cmake_args=()
if (( _loglevel > 1 )) cmake_args+=(--verbose)
pushd ${project_root}
cmake --build build_${target##*-} --config ${BUILD_CONFIG:-RelWithDebInfo} -t package ${cmake_args}
popd
pushd ${project_root}
cmake --build build_${target##*-} --config ${BUILD_CONFIG:-RelWithDebInfo} -t package ${cmake_args}
popd
}
}
}

View File

@@ -38,9 +38,8 @@ function Package {
$BuildSpec = Get-Content -Path ${BuildSpecFile} -Raw | ConvertFrom-Json
$ProductName = $BuildSpec.name
$ProductVersion = $BuildSpec.version
$OutputName = "${ProductName}-${ProductVersion}-windows-${Target}"
$OutputName = "${ProductName}-windows-${Target}"
Install-BuildDependencies -WingetFile "${ScriptHome}/.Wingetfile"

69
.github/workflows/build-debian.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: debian-build
on:
push:
paths-ignore:
- '**.md'
- 'data/locale/**'
branches:
- master
tags:
- '*'
pull_request:
paths-ignore:
- '**.md'
- 'data/locale/**'
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: "recursive"
- name: check_libobs_revision
run: |
sudo apt update
sudo apt install libobs-dev
mkdir source
cd source
dpkg -l libobs-dev | tr -s " "| grep libobs | cut -d" " -f3 > libobs.rev
- name: install_frontend_header
run: |
[ -e /usr/include/obs/obs-frontend-api.h ] && { echo "ERROR: obs-frontend-api.h already in system. Maybe the package libobs-dev is installing it."; exit 1; }
cd source
LIBOBSREV=$(cat libobs.rev)
sudo apt update
sudo apt install devscripts
dget -u http://archive.ubuntu.com/ubuntu/pool/universe/o/obs-studio/obs-studio_$LIBOBSREV.dsc
cd ..
sudo find -name obs-frontend-api.h -exec cp {} /usr/include/obs/ \;
egrep '#include <obs.h>' /usr/include/obs/obs-frontend-api.h || { echo "ERROR: check if the sed commands are needed from now."; exit 1; }
sudo sed -i 's/#include <obs.h>/#include <obs\/obs.h>/' /usr/include/obs/obs-frontend-api.h
sudo sed -i 's/#include <util\/darray.h>/#include <obs\/util\/darray.h>/' /usr/include/obs/obs-frontend-api.h
- name: create_tarball
run: |
cd ..
tar --exclude=.git -cvzf obs-scene-switcher_0.1+testonly.orig.tar.gz SceneSwitcher
- name: create_debian_dir
run: |
cp -a CI/linux/debian .
- name: install_dependencies
run: |
# devscripts and libobs-dev are needed but they were already installed
# from check_libobs_revision and install_frontend_header sections.
sudo apt update
sudo apt install cmake debhelper libcurl4-openssl-dev libxss-dev libxtst-dev qtbase5-dev libopencv-dev libprocps-dev
- name: build
run: |
debuild --no-lintian --no-sign
mv ../*.deb .
- name: Publish
if: success()
uses: actions/upload-artifact@v2.2.1
with:
name: "obs-scene-switcher.deb"
path: ${{ github.workspace }}/*.deb

View File

@@ -92,21 +92,21 @@ jobs:
if [[ '${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}' != '' && \
'${{ secrets.MACOS_SIGNING_INSTALLER_IDENTITY }}' != '' && \
'${{ secrets.MACOS_SIGNING_CERT }}' != '' ]] {
print '::set-output name=haveCodesignIdent::true'
print 'haveCodesignIdent=true' >> $GITHUB_OUTPUT
} else {
print '::set-output name=haveCodesignIdent::false'
print 'haveCodesignIdent=false' >> $GITHUB_OUTPUT
}
if [[ '${{ secrets.MACOS_NOTARIZATION_USERNAME }}' != '' && \
'${{ secrets.MACOS_NOTARIZATION_PASSWORD }}' != '' ]] {
print '::set-output name=haveNotarizationUser::true'
print 'haveNotarizationUser=true' >> $GITHUB_OUTPUT
} else {
print '::set-output name=haveNotarizationUser::false'
print 'haveNotarizationUser=false' >> $GITHUB_OUTPUT
}
print '::endgroup::'
print "::set-output name=ccacheDate::$(date +"%Y-%m-%d")"
print "::set-output name=commitHash::${"$(git rev-parse HEAD)"[0,9]}"
print "ccacheDate=$(date +"%Y-%m-%d")" >> $GITHUB_OUTPUT
print "commitHash=${"$(git rev-parse HEAD)"[0,9]}" >> $GITHUB_OUTPUT
echo "$PWD/.github/scripts" >> $GITHUB_PATH
- name: Restore Compilation Cache
@@ -123,9 +123,9 @@ jobs:
if: ${{ github.event_name == 'pull_request' }}
run: |
if [[ -n "$(curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -s "${{ github.event.pull_request.url }}" | jq -e '.labels[] | select(.name == "Seeking Testers")')" ]] {
print '::set-output name=found::true'
print 'found=true' >> $GITHUB_OUTPUT
} else {
print '::set-output name=found::false'
print 'found=false' >> $GITHUB_OUTPUT
}
- name: Install Apple Developer Certificate
@@ -169,7 +169,7 @@ jobs:
uses: actions/upload-artifact@v3
with:
name: ${{ env.PLUGIN_NAME }}-macos-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }}
path: ${{ github.workspace }}/plugin/release/${{ env.LIB_NAME }}-*-macos-${{ matrix.arch }}.pkg
path: ${{ github.workspace }}/plugin/release/${{ env.LIB_NAME }}*-macos-${{ matrix.arch }}.pkg
linux_build:
name: 02 - Linux
@@ -178,6 +178,7 @@ jobs:
fail-fast: true
matrix:
arch: [x86_64]
portable: [true, false]
if: always()
needs: [clang_check]
outputs:
@@ -205,8 +206,8 @@ jobs:
id: setup
run: |
## SETUP ENVIRONMENT SCRIPT
echo "::set-output name=ccacheDate::$(date +"%Y-%m-%d")"
echo "::set-output name=commitHash::$(git rev-parse HEAD | cut -c1-9)"
echo "ccacheDate=$(date +"%Y-%m-%d")" >> $GITHUB_OUTPUT
echo "commitHash=$(git rev-parse HEAD | cut -c1-9)" >> $GITHUB_OUTPUT
echo "$PWD/.github/scripts" >> $GITHUB_PATH
- name: Restore Compilation Cache
@@ -224,9 +225,9 @@ jobs:
run: |
## GITHUB LABEL SCRIPT
if [[ -n "$(curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -s "${{ github.event.pull_request.url }}" | jq -e '.labels[] | select(.name == "Seeking Testers")')" ]]; then
echo '::set-output name=found::true'
echo 'found=true' >> $GITHUB_OUTPUT
else
echo '::set-output name=found::false'
echo 'found=false' >> $GITHUB_OUTPUT
fi
- name: Build Plugin
@@ -235,13 +236,7 @@ jobs:
workingDirectory: ${{ github.workspace }}/plugin
target: ${{ matrix.arch }}
config: RelWithDebInfo
- name: Upload Build Artifact
if: ${{ success() }}
uses: actions/upload-artifact@v3
with:
name: ${{ env.PLUGIN_NAME }}-linux-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }}
path: ${{ github.workspace }}/plugin/release/*
portable: ${{ matrix.portable }}
- name: Package Plugin
uses: ./plugin/.github/actions/package-plugin
@@ -249,13 +244,21 @@ jobs:
workingDirectory: ${{ github.workspace }}/plugin
target: ${{ matrix.arch }}
config: RelWithDebInfo
portable: ${{ matrix.portable }}
- name: Upload Package Artifact
if: ${{ success() }}
if: ${{ matrix.portable }}
uses: actions/upload-artifact@v3
with:
name: ${{ env.PLUGIN_NAME }}-linux-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }}-portable
path: ${{ github.workspace }}/plugin/release/${{ env.LIB_NAME }}*-linux-${{ matrix.arch }}.*
- name: Upload Build Artifact
if: ${{ ! matrix.portable }}
uses: actions/upload-artifact@v3
with:
name: ${{ env.PLUGIN_NAME }}-linux-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }}
path: ${{ github.workspace }}/plugin/release/${{ env.LIB_NAME }}-*-linux-${{ matrix.arch }}.*
path: ${{ github.workspace }}/plugin/release/${{ env.LIB_NAME }}*-linux-${{ matrix.arch }}.*
windows_build:
name: 02 - Windows
@@ -263,7 +266,7 @@ jobs:
strategy:
fail-fast: true
matrix:
arch: [x86, x64]
arch: [x64]
if: always()
needs: [clang_check]
outputs:
@@ -292,7 +295,7 @@ jobs:
run: |
## SETUP ENVIRONMENT SCRIPT
$CommitHash = (git rev-parse HEAD)[0..8] -join ''
Write-Output "::set-output name=commitHash::${CommitHash}"
"commitHash=${CommitHash}" >> $env:GITHUB_OUTPUT
- name: Check for GitHub Labels
id: seekingTesters
@@ -313,7 +316,7 @@ jobs:
$false
}
Write-Output "::set-output name=found::$(([string]${LabelFound}).ToLower())"
"found=$(([string]${LabelFound}).ToLower())" >> $env:GITHUB_OUTPUT
- name: Build Plugin
uses: ./plugin/.github/actions/build-plugin
@@ -364,7 +367,7 @@ jobs:
id: metadata
run: |
## METADATA SCRIPT
echo "::set-output name=version::${GITHUB_REF/refs\/tags\//}"
echo "version=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
- name: Download build artifacts
uses: actions/download-artifact@v3

3
.gitmodules vendored
View File

@@ -10,6 +10,3 @@
[submodule "deps/openvr"]
path = deps/openvr
url = https://github.com/ValveSoftware/openvr.git
[submodule "deps/obs-websocket"]
path = deps/obs-websocket
url = https://github.com/obsproject/obs-websocket.git

View File

@@ -6,6 +6,8 @@ You have the option to ...
As both methods require you to have a working [OBS Studio development environment](https://obsproject.com/wiki/Building-OBS-Studio) and [CMake](https://cmake.org/download/) it is recommended to build the plugin in tree as it is easier to set up and will enable straightforward debugging.
The plugin can be compiled for OBS 25 and above, although using the latest version of OBS is recommended to support all features.
## Compiling in tree (recommended for development)
Add the "SceneSwitcher" source directory to your obs-studio source directory under obs-studio/UI/frontend-plugins/:
```
@@ -13,7 +15,7 @@ cd obs-studio/UI/frontend-plugins/
git clone --recursive https://github.com/WarmUpTill/SceneSwitcher.git
```
Then modify the obs-studio/UI/frontend-plugins/CMakeLists.txt file and add an entry for the scene switcher:
Then modify the obs-studio/UI/frontend-plugins/CMakeLists.txt Example and add an entry for the scene switcher:
```
add_subdirectory(SceneSwitcher)
```
@@ -25,6 +27,7 @@ Note that on Linux systems it might be necessary to additionally install the fol
sudo apt-get install \
libxtst-dev \
libxss-dev \
libprocps-dev \
libopencv-dev
```
@@ -63,11 +66,123 @@ Finally, start the plugin build using your provided generator. (E.g. Ninja on Li
Contributions to the plugin are always welcome and if you need any assistance do not hesitate to reach out.
If you would like to expand upon the macro system by adding a new condition or action type have a loot at the examples in `src/macro-core`.
In general changes in the `src/legacy` folder should be avoided.
If you would like to expand upon the macro system by adding a new condition or action type have a loot at the examples in `src/macro-core`.
The key functions to add conditions or are the Register() functions.
## Macro condition
Macro conditions should inherit from the `MacroCondition` class and must implement the following functions:
```
class MacroConditionExample : public MacroCondition {
public:
MacroConditionExample(Macro *m) : MacroCondition(m) {}
// This function should perfrom the condition check
bool CheckCondition();
// This function should store the required condition data to "obj"
// For example called on OBS shutdown
bool Save(obs_data_t *obj);
// This function should load the condition data from "obj"
// For example called on OBS startup
bool Load(obs_data_t *obj);
// This function should return a unique id for this condition type
// The _id is defined below
std::string GetId() { return _id; };
// Helper function called when new conditions are created
// Will be used later when regeistering the new condition type
static std::shared_ptr<MacroCondition> Create(Macro *m)
{
return std::make_shared<MacroConditionExample>(m);
}
private:
// Used to register new condition type
static bool _registered;
// Unique id identifying this condition type
static const std::string _id;
};
```
When defining the widget used to control the settings of the condition type, it is important to add a static `Create()` method.
It will be called whenever a new condition MacroConditionExample is created. (See `MacroConditionFactory::Register()`)
```
class MacroConditionExampleEdit : public QWidget {
Q_OBJECT
public:
// ...
MacroConditionExampleEdit(
QWidget *parent,
std::shared_ptr<MacroConditionExample> cond = nullptr);
// Function will be used to create the widget for editing the settings of the condition
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionExampleEdit(
parent,
std::dynamic_pointer_cast<MacroConditionExample>(cond));
}
// ...
};
```
To finally register the new condition type you will have to add the following call to `MacroConditionFactory::Register()`.
```
bool MacroConditionExample::_registered = MacroConditionFactory::Register(
MacroConditionExample::id, // Unique string identifying this condition type
{
MacroConditionExample::Create, // Function called to create the object performing the condition
MacroConditionExampleEdit::Create, // Function called to create the widget configure the condition
"AdvSceneSwitcher.condition.example", // User facing name of the condition type (will be translated)
true // Condition type supports duration modifiers (default true)
}
);
```
## Macro action
The process of adding a new action type is very similar to adding a new condition.
The differences are highlighted in the comments below.
```
class MacroActionExample : public MacroAction {
public:
MacroActionExample(Macro *m) : MacroAction(m) {}
// This function should perfrom the action
// If false is returned the macro will aborted
bool PerformAction();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
std::string GetId() { return _id; };
static std::shared_ptr<MacroAction> Create(Macro *m)
{
return std::make_shared<MacroActionExample>(m);
}
private:
static bool _registered;
static const std::string _id;
};
```
```
class MacroActionExampleEdit : public QWidget {
Q_OBJECT
public:
// ...
MacroActionExampleEdit(
QWidget *parent,
std::shared_ptr<MacroActionExample> entryData = nullptr);
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionExampleEdit(
parent,
std::dynamic_pointer_cast<MacroActionExample>(action));
}
// ...
};
```
```
MacroActionFactory::Register(
MacroActionExample::id, // Unique string identifying this action type
@@ -78,6 +193,6 @@ MacroActionFactory::Register(
}
);
```
## External dependencies
If your intention is to add macro functionality which depends on external libraries, which is likely not to exist on all user setups, try to follow the examples under `src/macro-external`.
These are basically plugins themselves that get attempted to be loaded on startup of the advanced scene switcher.

View File

@@ -1,75 +0,0 @@
Note that you will have to use OBS version 27 or newer!
--- Windows ---
Recommended: Run the provided installer. (You might have to click 'More info' and select 'Run anyway' if it is blocked by Windows)
Alternatively manually copy the 'obs-plugins' and 'data' folders in the respective OBS Studio installation directory.
It is usually located at 'C:\Program Files (x86)\obs-studio\'.
Remember to install the Visual C++ Redistributable for Visual Studio 2019, if you have not done so already. (See plugin overview page)
--- macOS ---
Run the provided installer. (You might have to right-click or control-click and select 'open' if it is blocked)
--- Linux ---
Note that the plugin has dependencies to:
* libXss
* libopencv-imgproc
* libopencv-objdetect
Optional:
* libXtst
* libcurl
If you are using snap you can install the following package for an OBS installation which comes bundled with the plugin:
sudo snap install obs-studio
The plugin is also available via the Flatpak package manager for users who installed OBS via Flatpak:
flatpak install com.obsproject.Studio.Plugin.SceneSwitcher
If you have installed OBS via other means, it is most likely necessary to install the plugin manually.
To do so copy the advanced-scene-switcher.so file and into the OBS Studio plugin folder.
The location of this folder can vary, so you might have to look around a bit.
Examples are ...
/usr/lib/obs-plugins/
/usr/lib/x86_64-linux-gnu/obs-plugins/
/usr/share/obs/obs-plugins/
~/.config/obs-studio/plugins/advanced-scene-switcher/bin/64bit/
~/.local/share/flatpak/app/com.obsproject.Studio/x86_64/stable/active/files/lib/obs-plugins/
/var/lib/flatpak/app/com.obsproject.Studio/x86_64/stable/active/files/lib/obs-plugins/
Unfortunately the expected location of the "locale" and "res" folder, which can be found in the data folder, can vary also.
The data folder usually should be copied to "~/.config/obs-studio/plugins/advanced-scene-switcher/" and reflect the following structure:
/home/user/.config/obs-studio/plugins/
└── advanced-scene-switcher
├── bin
│ └── 64bit
│ └── advanced-scene-switcher.so
└── data
├── locale
│ ├── de-DE.ini
│ ├── en-US.ini
│ ├── ru-RU.ini
│ └── zh-CN.ini
└── res
├── cascadeClassifiers
│ ├── haarcascade_eye_tree_eyeglasses.xml
│ ├── haarcascade_eye.xml
│ ├── haarcascade_frontalface_alt2.xml
│ ├── haarcascade_frontalface_alt_tree.xml
│ ├── haarcascade_frontalface_alt.xml
│ ├── haarcascade_frontalface_default.xml
│ ├── haarcascade_fullbody.xml
│ ├── haarcascade_lefteye_2splits.xml
│ ├── haarcascade_lowerbody.xml
│ ├── haarcascade_profileface.xml
│ ├── haarcascade_righteye_2splits.xml
│ └── haarcascade_upperbody.xml
└── time.svg
If this does not work you can try to copy the "locale" folder found inside the data folder to:
/usr/share/obs/obs-plugins/advanced-scene-switcher/locale
~/.local/share/flatpak/app/com.obsproject.Studio/x86_64/stable/active/files/share/obs/obs-plugins/advanced-scene-switcher/locale
/var/lib/flatpak/app/com.obsproject.Studio/x86_64/stable/active/files/share/obs/obs-plugins/advanced-scene-switcher/locale
In doubt, please check where other "en-US.ini" files are located on your system.

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.21)
cmake_minimum_required(VERSION 3.16.3)
project(advanced-scene-switcher VERSION 1.0.0)
set(LIB_NAME "${PROJECT_NAME}-lib")
@@ -147,6 +147,8 @@ target_sources(
src/macro-core/macro-action-timer.hpp
src/macro-core/macro-action-transition.cpp
src/macro-core/macro-action-transition.hpp
src/macro-core/macro-action-variable.cpp
src/macro-core/macro-action-variable.hpp
src/macro-core/macro-action-virtual-cam.cpp
src/macro-core/macro-action-virtual-cam.hpp
src/macro-core/macro-action-wait.cpp
@@ -205,6 +207,8 @@ target_sources(
src/macro-core/macro-condition-timer.hpp
src/macro-core/macro-condition-transition.cpp
src/macro-core/macro-condition-transition.hpp
src/macro-core/macro-condition-variable.cpp
src/macro-core/macro-condition-variable.hpp
src/macro-core/macro-condition-virtual-cam.cpp
src/macro-core/macro-condition-virtual-cam.hpp
src/macro-core/macro-condition-websocket.cpp
@@ -238,12 +242,16 @@ target_sources(
src/utils/curl-helper.hpp
src/utils/duration-control.cpp
src/utils/duration-control.hpp
src/utils/item-selection-helpers.cpp
src/utils/item-selection-helpers.hpp
src/utils/file-selection.cpp
src/utils/file-selection.hpp
src/utils/macro-list.cpp
src/utils/macro-list.hpp
src/utils/name-dialog.cpp
src/utils/name-dialog.hpp
src/utils/regex-config.cpp
src/utils/regex-config.hpp
src/utils/resizing-text-edit.cpp
src/utils/resizing-text-edit.hpp
src/utils/scene-item-selection.cpp
@@ -258,29 +266,27 @@ target_sources(
src/utils/transition-selection.hpp
src/utils/utility.cpp
src/utils/utility.hpp
src/utils/variable.cpp
src/utils/variable.hpp
src/utils/volume-control.cpp
src/utils/volume-control.hpp
src/utils/websocket-helpers.cpp
src/utils/websocket-helpers.hpp
src/utils/volume-control.hpp)
src/utils/websocket-helpers.hpp)
# --- End of section ---
target_link_libraries(${PROJECT_NAME} PUBLIC ${LIB_NAME})
if(BUILD_OUT_OF_TREE)
find_package(libobs REQUIRED)
find_package(obs-frontend-api REQUIRED)
include(cmake/ObsPluginHelpers.cmake)
target_link_libraries(${LIB_NAME} PUBLIC OBS::libobs OBS::obs-frontend-api)
else()
target_link_libraries(${LIB_NAME} PUBLIC OBS::libobs OBS::frontend-api)
endif()
include(cmake/AdvSSHelpers.cmake)
setup_obs_lib_dependency(${LIB_NAME})
setup_obs_lib_dependency(${PROJECT_NAME})
target_link_libraries(${PROJECT_NAME} PUBLIC ${LIB_NAME})
find_qt(COMPONENTS Widgets Core)
target_link_libraries(${LIB_NAME} PUBLIC Qt::Core Qt::Widgets)
include(cmake/AdvSSHelpers.cmake)
# --- Platform-independent build settings ---
target_include_directories(
@@ -357,9 +363,10 @@ elseif(OS_MACOS)
# --- Linux-specific build settings and tasks ---
else()
set_target_properties(${PROJECT_NAME} PROPERTIES INSTALL_RPATH "$ORIGIN")
target_compile_options(${LIB_NAME} PRIVATE -Wall -Wextra)
set_target_properties(${LIB_NAME} PROPERTIES PREFIX "")
set_target_properties(${LIB_NAME} PROPERTIES SOVERSION 1)
find_package(X11 REQUIRED COMPONENTS Xtst Xss)
find_path(PROCPS_INCLUDE_DIR NAMES proc/procps.h)
@@ -392,31 +399,26 @@ endif()
# --- End of section ---
target_link_libraries(${LIB_NAME} PUBLIC ${${PROJECT_NAME}_PLATFORM_LIBS})
setup_plugin_target(${PROJECT_NAME})
install_advss_lib(${LIB_NAME})
# --- Helpers for debian package creation ---
if(DEB_INSTALL)
# Additional commands to install the module in the correct place. Find all the
# translation files so we can copy them to the correct place later on.
file(GLOB ASS_TRANSLATION_FILES "data/locale/*.ini")
# Linux
if(UNIX AND NOT APPLE)
if(NOT LIB_OUT_DIR)
set(LIB_OUT_DIR "/lib/obs-plugins")
endif()
if(NOT DATA_OUT_DIR)
set(DATA_OUT_DIR "/share/obs/obs-plugins/${PROJECT_NAME}")
endif()
install(TARGETS ${PROJECT_NAME}
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${LIB_OUT_DIR})
install(DIRECTORY data/locale
DESTINATION ${CMAKE_INSTALL_PREFIX}/${DATA_OUT_DIR})
install(DIRECTORY data/res
DESTINATION ${CMAKE_INSTALL_PREFIX}/${DATA_OUT_DIR})
if(NOT LIB_OUT_DIR)
set(LIB_OUT_DIR "/lib/obs-plugins")
endif()
if(NOT DATA_OUT_DIR)
set(DATA_OUT_DIR "/share/obs/obs-plugins/${PROJECT_NAME}")
endif()
install(TARGETS ${PROJECT_NAME}
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${LIB_OUT_DIR})
install(TARGETS ${LIB_NAME}
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${LIB_OUT_DIR})
install(DIRECTORY data/locale
DESTINATION ${CMAKE_INSTALL_PREFIX}/${DATA_OUT_DIR})
install(DIRECTORY data/res
DESTINATION ${CMAKE_INSTALL_PREFIX}/${DATA_OUT_DIR})
else()
setup_plugin_target(${PROJECT_NAME})
install_advss_lib(${LIB_NAME})
endif()
# --- End of section ---

View File

@@ -26,6 +26,7 @@ If `apt` is supported on your system they can be installed using:
sudo apt-get install \
libxtst-dev \
libxss-dev \
libprocps-dev \
libopencv-dev
```

View File

@@ -1,10 +1,10 @@
{
"dependencies": {
"obs-studio": {
"version": "28.0.0-beta1",
"version": "28.0.1",
"repository": "https://github.com/obsproject/obs-studio.git",
"branch": "master",
"hash": "43a49dca47344a5170159ef99b86b97f90d4e4ad"
"hash": "e8dc70d0eef4503378d6ac300e680215eb5c9379"
},
"prebuilt": {
"version": "2022-08-02",

View File

@@ -67,18 +67,27 @@ if(OS_MACOS)
else()
# --- Windows / Linux section ---
function(plugin_install_helper what where where_deb)
install(
TARGETS ${what}
RUNTIME DESTINATION "${where}" COMPONENT ${what}_Runtime
LIBRARY DESTINATION "${where}"
COMPONENT ${what}_Runtime
NAMELINK_COMPONENT ${what}_Development)
install(
FILES $<TARGET_FILE:${what}>
DESTINATION $<CONFIG>/${where}
COMPONENT ${what}_rundir
EXCLUDE_FROM_ALL)
if(DEB_INSTALL)
if(NOT LIB_OUT_DIR)
set(LIB_OUT_DIR "/lib/obs-plugins")
endif()
install(
TARGETS ${what}
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${LIB_OUT_DIR}/${where_deb})
else()
install(
TARGETS ${what}
RUNTIME DESTINATION "${where}" COMPONENT ${what}_Runtime
LIBRARY DESTINATION "${where}"
COMPONENT ${what}_Runtime
NAMELINK_COMPONENT ${what}_Development)
install(
FILES $<TARGET_FILE:${what}>
DESTINATION $<CONFIG>/${where}
COMPONENT ${what}_rundir
EXCLUDE_FROM_ALL)
endif()
if(OS_WINDOWS)
install(
FILES $<TARGET_PDB_FILE:${what}>
@@ -93,7 +102,6 @@ else()
COMPONENT ${what}_rundir
OPTIONAL EXCLUDE_FROM_ALL)
endif()
add_custom_command(
TARGET ${what}
POST_BUILD
@@ -104,25 +112,23 @@ else()
${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake
COMMENT "Installing ${what} to plugin rundir ${OBS_OUTPUT_DIR}/${where}\n"
VERBATIM)
if(OS_POSIX AND DEB_INSTALL)
if(NOT LIB_OUT_DIR)
set(LIB_OUT_DIR "/lib/obs-plugins")
endif()
install(
TARGETS ${what}
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${LIB_OUT_DIR}/${where_deb})
endif()
endfunction()
function(install_advss_lib target)
plugin_install_helper("${target}" "${OBS_PLUGIN_DESTINATION}" "")
if(OS_POSIX)
set_target_properties(${target} PROPERTIES INSTALL_RPATH "$ORIGIN")
endif()
endfunction()
function(install_advss_plugin target)
plugin_install_helper(
"${target}" "${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
"${_PLUGIN_FOLDER}")
if(OS_POSIX)
set_target_properties(${target} PROPERTIES INSTALL_RPATH
"$ORIGIN:$ORIGIN/..")
endif()
message(STATUS "ADVSS: ENABLED PLUGIN ${target}")
endfunction()
@@ -205,14 +211,50 @@ else()
endif()
# --- End of section ---
function(setup_advss_plugin target)
function(setup_obs_lib_dependency target)
if(BUILD_OUT_OF_TREE)
target_link_libraries(${target} PUBLIC OBS::libobs OBS::obs-frontend-api)
find_package(libobs)
if(libobs_FOUND)
target_link_libraries(${target} PUBLIC OBS::libobs)
else()
if(NOT LIBOBS_LIB)
message(FATAL_ERROR "obs library not found - please set LIBOBS_LIB")
endif()
target_link_libraries(${target} PUBLIC ${LIBOBS_LIB})
if(NOT LIBOBS_INCLUDE_DIR)
message(
FATAL_ERROR "obs.hpp header not found - please set LIBOBS_INCLUDE_DIR"
)
endif()
target_include_directories(${target} PRIVATE ${LIBOBS_INCLUDE_DIR})
endif()
find_package(obs-frontend-api)
if(obs-frontend-api_FOUND)
target_link_libraries(${target} PUBLIC OBS::obs-frontend-api)
else()
if(NOT LIBOBS_FRONTEND_API_LIB)
message(
FATAL_ERROR
"libobs frontend-api library not found - please set LIBOBS_FRONTEND_API_LIB"
)
endif()
target_link_libraries(${target} PUBLIC ${LIBOBS_FRONTEND_API_LIB})
if(NOT LIBOBS_FRONTEND_INCLUDE_DIR)
message(
FATAL_ERROR
" obs-frontend-api.h not found - please set LIBOBS_FRONTEND_INCLUDE_DIR"
)
endif()
target_include_directories(${target}
PRIVATE ${LIBOBS_FRONTEND_INCLUDE_DIR})
endif()
else()
target_link_libraries(${target} PUBLIC OBS::libobs OBS::frontend-api)
endif()
endfunction()
function(setup_advss_plugin target)
setup_obs_lib_dependency(${target})
find_qt(COMPONENTS Widgets Core)
target_link_libraries(${target} PRIVATE Qt::Core Qt::Widgets)

View File

@@ -346,13 +346,18 @@ else()
set(CPACK_PACKAGE_NAME "${CMAKE_PROJECT_NAME}")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${LINUX_MAINTAINER_EMAIL}")
set(CPACK_PACKAGE_VERSION "${CMAKE_PROJECT_VERSION}")
set(CPACK_PACKAGE_FILE_NAME
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-linux-x86_64")
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-linux-x86_64")
set(CPACK_GENERATOR "DEB")
set(CPACK_DEBIAN_PACKAGE_DEPENDS
"obs-studio (>= 27.0.0), libqt5core5a (>= 5.9.0~beta), libqt5gui5 (>= 5.3.0), libqt5widgets5 (>= 5.7.0)"
)
if(${QT_VERSION} STREQUAL "6")
set(CPACK_DEBIAN_PACKAGE_DEPENDS
"obs-studio (>= 28.0.0), libqt6core6 (>= 6.2.0), libqt6gui6 (>= 6.1.2), libqt6widgets6 (>= 6.1.2)"
)
else()
set(CPACK_DEBIAN_PACKAGE_DEPENDS
"obs-studio (>= 28.0.0), libqt5core5a (>= 5.9.0~beta), libqt5gui5 (>= 5.3.0), libqt5widgets5 (>= 5.7.0)"
)
endif()
set(CPACK_OUTPUT_FILE_PREFIX ${CMAKE_SOURCE_DIR}/release)

View File

@@ -1,5 +1,5 @@
#define MyAppName "@CMAKE_PROJECT_NAME@"
#define MyAppVersion "@CMAKE_PROJECT_VERSION@"
#define MyAppVersion "@GIT_TAG@"
#define MyAppPublisher "@PLUGIN_AUTHOR@"
#define MyAppURL "http://www.mywebsite.com"

View File

@@ -51,13 +51,13 @@ AdvSceneSwitcher.generalTab.priority.media="Medien"
AdvSceneSwitcher.generalTab.priority.time="Zeit"
AdvSceneSwitcher.generalTab.priority.audio="Audio"
AdvSceneSwitcher.generalTab.priority.video="Video"
AdvSceneSwitcher.generalTab.setTransitionBy="Beim Ändern des Szenenübergangs:"
AdvSceneSwitcher.generalTab.transitionOverride="Verwende Übergangsüberschreibungen"
AdvSceneSwitcher.generalTab.adjustActiveTransitionType="Wechsle den aktiven Szenenübergangtyp"
AdvSceneSwitcher.generalTab.transitionBehaviorSelectionError="Mindestens eine Option muss aktiv sein:\n\n - Verwende Übergangsüberschreibungen\n\n - Wechsle den aktiven Szenenübergangtyp"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Szenenübergänge"
AdvSceneSwitcher.transitionTab.setTransitionBy="Beim Ändern des Szenenübergangs:"
AdvSceneSwitcher.transitionTab.transitionOverride="Verwende Übergangsüberschreibungen"
AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="Wechsle den aktiven Szenenübergangtyp"
AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError="Mindestens eine Option muss aktiv sein:\n\n - Verwende Übergangsüberschreibungen\n\n - Wechsle den aktiven Szenenübergangtyp"
AdvSceneSwitcher.transitionTab.transitionForAToB="Szenenübergänge für automatisierte Szenenwechsel von Szene A zu Szene B"
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>Diese Einstellungen beeinflussen <span style=\"font-style:italic;\">nur</span> vom Szenenwechsler ausgelöste Szenenübergänge - Siehe <a href=\"https://obsproject.com/forum/resources/transition-table.1174/\"><span style=\" text-decoration: underline; color:#268bd2;\">Transition Table</span></a> um auch manuelle Szenenübergänge zu konfigurieren.<br/>Einstellungen auf diesem Tab haben Vorrang, vor denen welche auf den übrigen Tabs konfiguriert wurden.<br/><br/>Klicke auf das Plus Symbol, um einen neuen Eintrag hinzuzufügen.</p></body></html>"
AdvSceneSwitcher.transitionTab.defaultTransition="Ändere den Szenenübergang wenn eine Szene aktiv ist"

View File

@@ -55,6 +55,11 @@ AdvSceneSwitcher.generalTab.priority.time="Time"
AdvSceneSwitcher.generalTab.priority.audio="Audio"
AdvSceneSwitcher.generalTab.priority.video="Video"
AdvSceneSwitcher.generalTab.priority.macro="Macro"
AdvSceneSwitcher.generalTab.transition="Transitions"
AdvSceneSwitcher.generalTab.setTransitionBy="When changing transitions:"
AdvSceneSwitcher.generalTab.transitionOverride="Set transition overrides"
AdvSceneSwitcher.generalTab.adjustActiveTransitionType="Change active transition type"
AdvSceneSwitcher.generalTab.transitionBehaviorSelectionError="At least one option must be enabled:\n\n - Use transition overrides\n\n - Change active transition type"
; Macro Tab
AdvSceneSwitcher.macroTab.title="Macro"
@@ -83,7 +88,8 @@ AdvSceneSwitcher.macroTab.minimize="Minimize"
AdvSceneSwitcher.macroTab.highlightExecutedMacros="Highlight recently executed macros"
AdvSceneSwitcher.macroTab.highlightTrueConditions="Highlight conditions of currently selected macro that evaluated to true recently"
AdvSceneSwitcher.macroTab.highlightPerformedActions="Highlight recently performed actions of currently selected macro"
AdvSceneSwitcher.macroTab.disableHotkeys="Register hotkeys to control pause state of selected macro"
AdvSceneSwitcher.macroTab.newMacroRegisterHotkey="Register hotkeys to control pause state of new macros"
AdvSceneSwitcher.macroTab.currentDisableHotkeys="Register hotkeys to control pause state of selected macro"
; Macro List
AdvSceneSwitcher.macroList.deleted="deleted"
@@ -127,8 +133,12 @@ AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
AdvSceneSwitcher.condition.window="Window"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} exist and ..."
AdvSceneSwitcher.condition.window.entry.line2="... is {{fullscreen}} fullscreen {{maximized}} maximized {{focused}} focused {{windowFocusChanged}} foreground window changed"
AdvSceneSwitcher.condition.window.entry.line3="Current foreground window: {{focusWindow}}"
AdvSceneSwitcher.condition.file="File"
AdvSceneSwitcher.condition.file.entry.line1="Content of {{fileType}} {{filePath}} matches:"
AdvSceneSwitcher.condition.file.type.match="matches"
AdvSceneSwitcher.condition.file.type.contentChange="content changed"
AdvSceneSwitcher.condition.file.type.dateChange="modification date changed"
AdvSceneSwitcher.condition.file.entry.line1="Content of{{fileType}}{{filePath}}{{conditions}}"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.condition.media="Media"
@@ -145,14 +155,20 @@ AdvSceneSwitcher.condition.video.condition.hasNotChanged="has not changed"
AdvSceneSwitcher.condition.video.condition.noImage="has no output"
AdvSceneSwitcher.condition.video.condition.pattern="matches pattern"
AdvSceneSwitcher.condition.video.condition.object="contains object"
AdvSceneSwitcher.condition.video.condition.brightness="brightness"
AdvSceneSwitcher.condition.video.askFileAction="Do you want to use an existing file or create a screenshot of the currently selected source?"
AdvSceneSwitcher.condition.video.askFileAction.file="Use existing file"
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot"
AdvSceneSwitcher.condition.video.reduceLatency="Reduce matching latency"
AdvSceneSwitcher.condition.video.reduceLatency.tooltip="Enabling will reduce the matching latency, but will slow down the condition checks of all macros overall."
AdvSceneSwitcher.condition.video.usePatternForChangedCheck="Use pattern matching"
AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip="This will allow you to control how much the image has to change for the condition to be true."
AdvSceneSwitcher.condition.video.patternThreshold="Threshold: "
AdvSceneSwitcher.condition.video.patternThresholdDescription="A higher threshold value means that the pattern needs to match the video source more closely."
AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask="Use alpha channel as mask for pattern."
AdvSceneSwitcher.condition.video.brightnessThreshold="Average brightness:"
AdvSceneSwitcher.condition.video.brightnessThresholdDescription="A high value is indicating a bright image and a low one a darker one."
AdvSceneSwitcher.condition.video.currentBrightness="Current average brightness: %1"
AdvSceneSwitcher.condition.video.objectScaleThreshold="Scale factor: "
AdvSceneSwitcher.condition.video.objectScaleThresholdDescription="A lower scale factor will lead to more matches but higher CPU load."
AdvSceneSwitcher.condition.video.minNeighborDescription="A higher minimum neighbors value will result in fewer but higher quality matches."
@@ -169,7 +185,8 @@ AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePa
AdvSceneSwitcher.condition.video.entry.modelPath="Model data (haar cascade classifier): {{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum neighbors: {{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU load by performing check only every {{throttleCount}} milliseconds"
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}Perform check only in area {{checkArea}} {{selectArea}}"
AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Perform check only in area"
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
AdvSceneSwitcher.condition.video.minSize="Minimum size:"
AdvSceneSwitcher.condition.video.maxSize="Maximum size:"
AdvSceneSwitcher.condition.video.selectArea="Select area"
@@ -189,6 +206,7 @@ AdvSceneSwitcher.condition.record.state.stop="Recording stopped"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.process="Process"
AdvSceneSwitcher.condition.process.entry="{{processes}} is running {{focused}} and is focused"
AdvSceneSwitcher.condition.process.entry.focus="Current foreground process: {{focusProcess}}"
AdvSceneSwitcher.condition.idle="Idle"
AdvSceneSwitcher.condition.idle.entry="No keyboard or mouse inputs for {{duration}}"
AdvSceneSwitcher.condition.pluginState="Plugin state"
@@ -227,7 +245,6 @@ AdvSceneSwitcher.condition.source="Source"
AdvSceneSwitcher.condition.source.type.active="Is active"
AdvSceneSwitcher.condition.source.type.showing="Is showing"
AdvSceneSwitcher.condition.source.type.settings="Settings match"
AdvSceneSwitcher.condition.source.regex="Use regular expressions"
AdvSceneSwitcher.condition.source.getSettings="Get current settings"
AdvSceneSwitcher.condition.source.entry.line1="{{sources}} {{conditions}}"
AdvSceneSwitcher.condition.source.entry.line2="{{settings}}"
@@ -240,7 +257,6 @@ AdvSceneSwitcher.condition.filter="Filter"
AdvSceneSwitcher.condition.filter.type.active="Is enabled"
AdvSceneSwitcher.condition.filter.type.showing="Is disabled"
AdvSceneSwitcher.condition.filter.type.settings="Settings match"
AdvSceneSwitcher.condition.filter.regex="Use regular expressions"
AdvSceneSwitcher.condition.filter.getSettings="Get current settings"
AdvSceneSwitcher.condition.filter.entry.line1="On {{sources}} {{filters}} {{conditions}}"
AdvSceneSwitcher.condition.filter.entry.line2="{{settings}}"
@@ -286,7 +302,6 @@ AdvSceneSwitcher.condition.date.entry.nextMatchDate="Next match at: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} On repeat update selected date to repeat date"
AdvSceneSwitcher.condition.sceneTransform="Scene item transform"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
AdvSceneSwitcher.condition.sceneTransform.regex="Use regular expressions"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="On{{scenes}}{{sources}}matches transform"
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}"
@@ -335,12 +350,25 @@ AdvSceneSwitcher.condition.stats.entry="{{stats}} is {{condition}} {{value}}"
AdvSceneSwitcher.condition.profile="Profile"
AdvSceneSwitcher.condition.profile.entry="Current active profile is {{profiles}}"
AdvSceneSwitcher.condition.websocket="Websocket"
AdvSceneSwitcher.condition.websocket.type.request="Request"
AdvSceneSwitcher.condition.websocket.type.event="Event"
AdvSceneSwitcher.condition.websocket.useRegex="Use regular expressions"
AdvSceneSwitcher.condition.websocket.entry="Message was received:"
AdvSceneSwitcher.condition.websocket.entry.request="{{type}} was received:"
AdvSceneSwitcher.condition.websocket.entry.event="{{type}} was received from {{connection}}:"
AdvSceneSwitcher.condition.variable="Variable"
AdvSceneSwitcher.condition.variable.type.compare="equals"
AdvSceneSwitcher.condition.variable.type.empty="is empty"
AdvSceneSwitcher.condition.variable.type.number="is a number"
AdvSceneSwitcher.condition.variable.type.lessThan="is less than"
AdvSceneSwitcher.condition.variable.type.greaterThan="is greater than"
AdvSceneSwitcher.condition.variable.type.valueChanged="value changed"
AdvSceneSwitcher.condition.variable.regex="Use regular expressions"
AdvSceneSwitcher.condition.variable.entry="{{variables}}{{conditions}}{{strValue}}{{numValue}}"
; Macro Actions
AdvSceneSwitcher.action.switchScene="Switch scene"
AdvSceneSwitcher.action.scene.entry="Switch to scene {{scenes}} using {{transitions}} with a duration of {{duration}} seconds"
AdvSceneSwitcher.action.scene.entry="Switch to scene{{scenes}}using{{transitions}}with a duration of{{duration}}seconds"
AdvSceneSwitcher.action.scene.entry.noDuration="Switch to scene{{scenes}}using{{transitions}}"
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Wait until transition to target scene is complete"
AdvSceneSwitcher.action.wait="Wait"
AdvSceneSwitcher.action.wait.type.fixed="fixed"
@@ -481,8 +509,13 @@ AdvSceneSwitcher.action.random.entry="Randomly run any of the following macros (
AdvSceneSwitcher.action.systray="System tray notification"
AdvSceneSwitcher.action.systray.entry="Show notification: {{message}}"
AdvSceneSwitcher.action.screenshot="Screenshot"
AdvSceneSwitcher.action.screenshot.save.default="Default"
AdvSceneSwitcher.action.screenshot.save.custom="Custom"
AdvSceneSwitcher.action.screenshot.type.source="Source"
AdvSceneSwitcher.action.screenshot.type.scene="Scene"
AdvSceneSwitcher.action.screenshot.mainOutput="OBS's main output"
AdvSceneSwitcher.action.screenshot.entry="Screenshot {{sources}}"
AdvSceneSwitcher.action.screenshot.blackscreenNote="Sources or scenes, which are not always rendered, may result in some parts of screenshots to remain blank."
AdvSceneSwitcher.action.screenshot.entry="Screenshot{{targetType}}{{sources}}{{scenes}}and save to{{saveType}}location"
AdvSceneSwitcher.action.profile="Profile"
AdvSceneSwitcher.action.profile.entry="Switch active profile to {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="Scene collection"
@@ -495,20 +528,26 @@ AdvSceneSwitcher.action.sequence.status.none="none"
AdvSceneSwitcher.action.sequence.restart="Restart from beginning once end of list is reached"
AdvSceneSwitcher.action.sequence.continueFrom="Continue with selected item"
AdvSceneSwitcher.action.websocket="Websocket"
AdvSceneSwitcher.action.websocket.entry="Send scene switcher message via {{connection}}"
AdvSceneSwitcher.action.websocket.type.request="Request"
AdvSceneSwitcher.action.websocket.type.event="Event"
AdvSceneSwitcher.action.websocket.entry.request="Send scene switcher {{type}} via {{connection}}"
AdvSceneSwitcher.action.websocket.entry.event="Send scene switcher {{type}} to connected clients"
AdvSceneSwitcher.action.http="Http"
AdvSceneSwitcher.action.http.type.get="GET"
AdvSceneSwitcher.action.http.type.post="POST"
AdvSceneSwitcher.action.http.entry.line1="Send {{method}} to {{url}}"
AdvSceneSwitcher.action.http.entry.line2="Timeout: {{timeout}} seconds"
AdvSceneSwitcher.action.variable="Variable"
AdvSceneSwitcher.action.variable.type.set="Set"
AdvSceneSwitcher.action.variable.type.append="Append"
AdvSceneSwitcher.action.variable.type.appendVar="Append variable"
AdvSceneSwitcher.action.variable.type.increment="Increment"
AdvSceneSwitcher.action.variable.type.decrement="Decrement"
AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Transition"
AdvSceneSwitcher.transitionTab.setTransitionBy="When changing transitions:"
AdvSceneSwitcher.transitionTab.transitionOverride="Set transition overrides"
AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="Change active transition type"
AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError="At least one option must be enabled:\n\n - Use transition overrides\n\n - Change active transition type"
AdvSceneSwitcher.transitionTab.transitionForAToB="Use transition for automated scene switch from scene A to scene B"
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>These settings <span style=\"font-style:italic;\">only</span> affect transitions caused by the scene switcher - Check out <a href=\"https://obsproject.com/forum/resources/transition-table.1174/\"><span style=\" text-decoration: underline; color:#268bd2;\">Transition Table</span></a> if you want to configure this for manual scene changes.<br/>Settings defined here take priority over transition settings configured elsewhere in the scene switcher.<br/><br/>Click the plus symbol below to add a new entry.</p></body></html>"
AdvSceneSwitcher.transitionTab.defaultTransition="Change transition if scene is active"
@@ -757,15 +796,27 @@ AdvSceneSwitcher.askForMacro="Select macro {{macroSelection}}"
AdvSceneSwitcher.close="Close"
AdvSceneSwitcher.browse="Browse"
AdvSceneSwitcher.item.select="--select item--"
AdvSceneSwitcher.item.add="Add new item"
AdvSceneSwitcher.item.rename="Rename"
AdvSceneSwitcher.item.remove="Remove"
AdvSceneSwitcher.item.properties="Properties"
AdvSceneSwitcher.item.newName="New name:"
AdvSceneSwitcher.item.emptyName="Empty name not allowed!"
AdvSceneSwitcher.item.nameNotAvailable="Name not available!"
AdvSceneSwitcher.item.nameReserved="Name reserved!"
AdvSceneSwitcher.variable.select="--select variable--"
AdvSceneSwitcher.variable.add="Add new variable"
AdvSceneSwitcher.variable.name="Name:"
AdvSceneSwitcher.variable.value="Current value:"
AdvSceneSwitcher.variable.save="Save / load behavior"
AdvSceneSwitcher.variable.save.dontSave="Don't save variable value"
AdvSceneSwitcher.variable.save.save="Save variable value"
AdvSceneSwitcher.variable.save.default="Set to value"
AdvSceneSwitcher.connection.select="--select connection--"
AdvSceneSwitcher.connection.add="Add new connection"
AdvSceneSwitcher.connection.rename="Rename"
AdvSceneSwitcher.connection.remove="Remove"
AdvSceneSwitcher.connection.properties="Properties"
AdvSceneSwitcher.connection.newName="New name:"
AdvSceneSwitcher.connection.emptyName="Empty name not allowed!"
AdvSceneSwitcher.connection.nameNotAvailable="Name not available!"
AdvSceneSwitcher.connection.nameReserved="Name reserved!"
AdvSceneSwitcher.connection.name="Name:"
AdvSceneSwitcher.connection.address="Address:"
AdvSceneSwitcher.connection.port="Port:"
@@ -779,9 +830,17 @@ AdvSceneSwitcher.connection.status.connecting="Connecting"
AdvSceneSwitcher.connection.status.connected="Connected, but not authenticated"
AdvSceneSwitcher.connection.status.authenticated="Connected and authenticated"
AdvSceneSwitcher.regex.enable="Enable regular expressions"
AdvSceneSwitcher.regex.partialMatch="Allow partial match"
AdvSceneSwitcher.regex.caseInsensitive="Match case insensitive"
AdvSceneSwitcher.regex.dotMatchNewline=". matches newlines"
AdvSceneSwitcher.regex.multiLine="^ and $ match start/end of line"
AdvSceneSwitcher.regex.extendedPattern="Enable Qt's ExtendedPatternSyntax"
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene"
AdvSceneSwitcher.selectPreviewScene="Preview Scene"
AdvSceneSwitcher.selectAnyScene="Any Scene"
AdvSceneSwitcher.currentTransition="Current Transition"
AdvSceneSwitcher.anyTransition="Any Transition"
@@ -790,7 +849,7 @@ AdvSceneSwitcher.selectWindow="--select window--"
AdvSceneSwitcher.selectSource="--select source--"
AdvSceneSwitcher.selectAudioSource="--select audio source--"
AdvSceneSwitcher.selectVideoSource="--select video source--"
AdvSceneSwitcher.OBSVideoOutput="OBS video output"
AdvSceneSwitcher.OBSVideoOutput="OBS's main output"
AdvSceneSwitcher.selectMediaSource="--select media source--"
AdvSceneSwitcher.selectProcess="--select process--"
AdvSceneSwitcher.selectFilter="--select filter--"

View File

@@ -54,6 +54,10 @@ AdvSceneSwitcher.generalTab.priority.time="Tiempo"
AdvSceneSwitcher.generalTab.priority.audio="Audio"
AdvSceneSwitcher.generalTab.priority.video="Video"
AdvSceneSwitcher.generalTab.priority.macro="Macro"
AdvSceneSwitcher.generalTab.setTransitionBy="Al cambiar las transiciones:"
AdvSceneSwitcher.generalTab.transitionOverride="Establecer anulaciones de transición"
AdvSceneSwitcher.generalTab.adjustActiveTransitionType="Cambiar tipo de transición activa"
AdvSceneSwitcher.generalTab.transitionBehaviorSelectionError="Al menos una opción debe estar habilitada:\n\n - Usar anulaciones de transición\n\n - Cambiar el tipo de transición activo"
; Macro Tab
AdvSceneSwitcher.macroTab.title="Macro"
@@ -123,7 +127,7 @@ AdvSceneSwitcher.condition.window="Ventana"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} existen y ..."
AdvSceneSwitcher.condition.window.entry.line2="... es {{fullscreen}} pantalla completa {{maximized}} maximizada {{focused}} enfocada {{windowFocusChanged}} ventana de primer plano cambiada"
AdvSceneSwitcher.condition.file="Archivo"
AdvSceneSwitcher.condition.file.entry.line1="Contenido de {{fileType}} {{filePath}} coincidencias:"
AdvSceneSwitcher.condition.file.entry.line1="Contenido de{{fileType}}{{filePath}}{{conditions}}"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.condition.media="Medios"
@@ -164,6 +168,7 @@ AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePa
AdvSceneSwitcher.condition.video.entry.modelPath="Datos del modelo (haar cascade classifier): {{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="Mínimo de vecinos: {{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduzca la carga de la CPU realizando una comprobación solo cada {{throttleCount}} milisegundos"
AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Realizar comprobación solo en el área"
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}Realizar comprobación solo en el área {{checkArea}} {{selectArea}}"
AdvSceneSwitcher.condition.video.minSize="Tamaño mínimo:"
AdvSceneSwitcher.condition.video.maxSize="Tamaño máximo:"
@@ -463,7 +468,6 @@ AdvSceneSwitcher.action.systray="Notificación de la bandeja del sistema"
AdvSceneSwitcher.action.systray.entry="Mostrar notificación: {{message}}"
AdvSceneSwitcher.action.screenshot="Captura de pantalla"
AdvSceneSwitcher.action.screenshot.mainOutput="Salida principal de OBS"
AdvSceneSwitcher.action.screenshot.entry="Captura de pantalla {{sources}}"
AdvSceneSwitcher.action.profile="Perfil"
AdvSceneSwitcher.action.profile.entry="Cambiar perfil activo a {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="Colección de escenas"
@@ -478,10 +482,6 @@ AdvSceneSwitcher.action.sequence.continueFrom="Continuar con el elemento selecci
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Transición"
AdvSceneSwitcher.transitionTab.setTransitionBy="Al cambiar las transiciones:"
AdvSceneSwitcher.transitionTab.transitionOverride="Establecer anulaciones de transición"
AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="Cambiar tipo de transición activa"
AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError="Al menos una opción debe estar habilitada:\n\n - Usar anulaciones de transición\n\n - Cambiar el tipo de transición activo"
AdvSceneSwitcher.transitionTab.transitionForAToB="Utiliza la transición para el cambio de escena automatizado de la escena A a la escena B"
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body> <p> Estos ajustes <span style =\"font-style: italic; \"> solo </span> surgió a las transiciones causadas por el selector de escenas - Echa un vistazo a <a href =\"https://obsproject.com/forum/resources/transition-table.1174/\"> <span style=\" texto-decoración: subrayado; color: # 268bd2; \"> Tabla de transición </span> </a> si desea configurar esto para cambios de escena manuales. <br/> Los definiciones definidos aquí tienen prioridad sobre los ajustes de transición configurados en cualquier otro lugar del selector de escenas. < br/> <br/> Haz clic en el símbolo más a continuación para agregar una nueva entrada. </p></body></html>"
AdvSceneSwitcher.transitionTab.defaultTransition="Cambiar transición si la escena está activa"

View File

@@ -52,6 +52,10 @@ AdvSceneSwitcher.generalTab.priority.time="Время"
AdvSceneSwitcher.generalTab.priority.audio="Аудио"
AdvSceneSwitcher.generalTab.priority.video="Видео"
AdvSceneSwitcher.generalTab.priority.macro="Макрос"
AdvSceneSwitcher.generalTab.setTransitionBy="При изменении переходов:"
AdvSceneSwitcher.generalTab.transitionOverride="Установить переопределение переходов"
AdvSceneSwitcher.generalTab.adjustActiveTransitionType="Изменить тип активного перехода"
AdvSceneSwitcher.generalTab.transitionBehaviorSelectionError="Должна быть включена хотя бы одна опция:\n\n - Использовать переопределения переходов\n\n - Изменить тип активного перехода"
; Macro Tab
AdvSceneSwitcher.macroTab.title="Макрос"
@@ -147,10 +151,6 @@ AdvSceneSwitcher.action.run="Запустить"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Переход"
AdvSceneSwitcher.transitionTab.setTransitionBy="При изменении переходов:"
AdvSceneSwitcher.transitionTab.transitionOverride="Установить переопределение переходов"
AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="Изменить тип активного перехода"
AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError="Должна быть включена хотя бы одна опция:\n\n - Использовать переопределения переходов\n\n - Изменить тип активного перехода"
AdvSceneSwitcher.transitionTab.transitionForAToB="Использовать переход для автоматического переключения сцены со сцены A на сцену B"
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>Эти настройки <span style=\"font-style:italic;\">только</span> влияют на переходы, вызванные переключателем сцен - Проверьте <a href=\"https://obsproject.com/forum/resources/transition-table.1174/\"><span style=\" text-decoration: underline; color:#268bd2;\">Transition Table</span></a> если вы хотите настроить его для ручного изменения сцены.<br/>Настройки, определенные здесь, имеют приоритет над настройками перехода, сконфигурированными в других местах переключателя сцен.<br/><br/>Нажмите на символ плюса ниже, чтобы добавить новую запись.</p></body></html>"
AdvSceneSwitcher.transitionTab.defaultTransition="Изменить переход, если сцена активна"

View File

@@ -53,6 +53,10 @@ AdvSceneSwitcher.generalTab.priority.time="Zaman"
AdvSceneSwitcher.generalTab.priority.audio="Ses"
AdvSceneSwitcher.generalTab.priority.video="Video"
AdvSceneSwitcher.generalTab.priority.macro="Makro"
AdvSceneSwitcher.generalTab.setTransitionBy="Geçişleri değiştirirken:"
AdvSceneSwitcher.generalTab.transitionOverride="Geçiş geçersiz kılmaları ayarla"
AdvSceneSwitcher.generalTab.adjustActiveTransitionType="Etkin geçiş türünü değiştir"
AdvSceneSwitcher.generalTab.transitionBehaviorSelectionError="En az bir seçenek etkinleştirilmelidir: \n\n - Geçiş geçersiz kılmalarını kullan \n\n - Etkin geçiş türünü değiştir"
; Macro Tab
AdvSceneSwitcher.macroTab.title="Makro"
@@ -111,7 +115,7 @@ AdvSceneSwitcher.condition.window="Pencere"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} Varolan ve ..."
AdvSceneSwitcher.condition.window.entry.line2="... {{fullscreen}} Tam ekran {{maximized}} büyütüldü {{focused}} odaklanıldı {{windowFocusChanged}} ön plan penceresi değişikliği"
AdvSceneSwitcher.condition.file="Dosya"
AdvSceneSwitcher.condition.file.entry.line1="İçerik {{fileType}} {{filePath}} eşleştirmeler:"
AdvSceneSwitcher.condition.file.entry.line1="İçerik{{fileType}}{{filePath}}{{conditions}}"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.condition.media="Medya"
@@ -383,7 +387,6 @@ AdvSceneSwitcher.action.systray="Sistem tepsisi bildirimi"
AdvSceneSwitcher.action.systray.entry="Bildirimleri göster: {{message}}"
AdvSceneSwitcher.action.screenshot="Ekran görüntüsü"
AdvSceneSwitcher.action.screenshot.mainOutput="OBS'nin ana çıkışı"
AdvSceneSwitcher.action.screenshot.entry="Ekran görüntüsü {{sources}}"
AdvSceneSwitcher.action.profile="Profil"
AdvSceneSwitcher.action.profile.entry="Aktif profili şununla değiştir: {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="Sahne koleksiyonu"
@@ -392,10 +395,6 @@ AdvSceneSwitcher.action.sceneCollection.warning="Not: Değişen sahne koleksiyon
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Geçiş"
AdvSceneSwitcher.transitionTab.setTransitionBy="Geçişleri değiştirirken:"
AdvSceneSwitcher.transitionTab.transitionOverride="Geçiş geçersiz kılmaları ayarla"
AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="Etkin geçiş türünü değiştir"
AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError="En az bir seçenek etkinleştirilmelidir: \n\n - Geçiş geçersiz kılmalarını kullan \n\n - Etkin geçiş türünü değiştir"
AdvSceneSwitcher.transitionTab.transitionForAToB="Sahne A'dan sahne B'ye otomatik sahne geçişi için geçişi kullanın"
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>Bu ayarlar <span style=\"font-style:italic;\">yalnızca</span>, sahne değiştiricinin neden olduğu geçişleri etkiler - <a href=\ Göz atın "https://obsproject.com/forum/resources/transition-table.1174/\"><span style=\" text-decoration: underline; color:#268bd2;\">Geçiş Tablosu</span></ a> bunu manuel sahne değişiklikleri için yapılandırmak istiyorsanız.<br/>Burada tanımlanan ayarlar, sahne değiştiricide başka bir yerde yapılandırılmış geçiş ayarlarına göre önceliklidir.<br/><br/>Yeni bir giriş eklemek için aşağıdaki artı simgesini tıklayın. .</p></body></html>"
AdvSceneSwitcher.transitionTab.defaultTransition="Sahne aktifse geçişi değiştir"

View File

@@ -54,6 +54,9 @@ AdvSceneSwitcher.generalTab.priority.time="时间"
AdvSceneSwitcher.generalTab.priority.audio="音频"
AdvSceneSwitcher.generalTab.priority.video="视频"
AdvSceneSwitcher.generalTab.priority.macro="宏"
AdvSceneSwitcher.generalTab.setTransitionBy="更改转场特效时:"
AdvSceneSwitcher.generalTab.transitionOverride="在场景切换器中设定的转场特效优先级高于场景设定的转场特效"
AdvSceneSwitcher.generalTab.adjustActiveTransitionType="更改激活转场特效类型"
; Macro Tab
AdvSceneSwitcher.macroTab.title="宏"
@@ -116,7 +119,6 @@ AdvSceneSwitcher.condition.window="窗口"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} 存在..."
AdvSceneSwitcher.condition.window.entry.line2="... 并且是 {{fullscreen}} 全屏 {{maximized}} 最大化 {{focused}} 获得焦点 {{windowFocusChanged}} 焦点窗口已更改"
AdvSceneSwitcher.condition.file="文件"
AdvSceneSwitcher.condition.file.entry.line1="内容 {{fileType}} {{filePath}} (本地是文本,网络是获取的网页)完全匹配:"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.condition.media="媒体"
@@ -424,7 +426,6 @@ AdvSceneSwitcher.action.systray="系统托盘通知"
AdvSceneSwitcher.action.systray.entry="显示通知: {{message}}"
AdvSceneSwitcher.action.screenshot="截图"
AdvSceneSwitcher.action.screenshot.mainOutput="OBS 预览画面"
AdvSceneSwitcher.action.screenshot.entry="截图 {{sources}}"
AdvSceneSwitcher.action.profile="配置文件"
AdvSceneSwitcher.action.profile.entry="将活动配置文件切换到 {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="场景集合"
@@ -439,9 +440,6 @@ AdvSceneSwitcher.action.sequence.continueFrom="继续所选项目"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="转场特效"
AdvSceneSwitcher.transitionTab.setTransitionBy="更改转场特效时:"
AdvSceneSwitcher.transitionTab.transitionOverride="在场景切换器中设定的转场特效优先级高于场景设定的转场特效"
AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="更改激活转场特效类型"
AdvSceneSwitcher.transitionTab.transitionForAToB="当自动从场景A切换到场景B时使用的转场特效"
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>这里的设定<span style=\"font-style:bold;\">只影响由场景切换器引起的转场有效</span>,不影响你手动的引起的转场。<br/>在这里设定的转场特效优先级高于场景切换器其他地方设置的<br/><br/>单击加号添加项目.</p></body></html>"
AdvSceneSwitcher.transitionTab.defaultTransition="当切换到这个场景时修改默认转场特效"

1
deps/obs-websocket vendored

Submodule deps/obs-websocket deleted from a25427c7cc

View File

@@ -0,0 +1,216 @@
/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2022 Kyle Manning <tt2468@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#ifndef _OBS_WEBSOCKET_API_H
#define _OBS_WEBSOCKET_API_H
#include <obs.h>
#define OBS_WEBSOCKET_API_VERSION 2
#ifdef __cplusplus
extern "C" {
#endif
typedef void *obs_websocket_vendor;
typedef void (*obs_websocket_request_callback_function)(obs_data_t *, obs_data_t *, void *);
struct obs_websocket_request_response {
unsigned int status_code;
char *comment;
char *response_data; // JSON string, because obs_data_t* only supports array<object>, so conversions would break API.
};
/* ==================== INTERNAL DEFINITIONS ==================== */
struct obs_websocket_request_callback {
obs_websocket_request_callback_function callback;
void *priv_data;
};
inline proc_handler_t *_ph;
/* ==================== INTERNAL API FUNCTIONS ==================== */
static inline proc_handler_t *obs_websocket_get_ph(void)
{
proc_handler_t *global_ph = obs_get_proc_handler();
assert(global_ph != NULL);
calldata_t cd = {0};
if (!proc_handler_call(global_ph, "obs_websocket_api_get_ph", &cd))
blog(LOG_DEBUG, "Unable to fetch obs-websocket proc handler object. obs-websocket not installed?");
proc_handler_t *ret = (proc_handler_t *)calldata_ptr(&cd, "ph");
calldata_free(&cd);
return ret;
}
static inline bool obs_websocket_ensure_ph(void)
{
if (!_ph)
_ph = obs_websocket_get_ph();
return _ph != NULL;
}
static inline bool obs_websocket_vendor_run_simple_proc(obs_websocket_vendor vendor, const char *proc_name, calldata_t *cd)
{
if (!obs_websocket_ensure_ph())
return false;
if (!vendor || !proc_name || !strlen(proc_name) || !cd)
return false;
calldata_set_ptr(cd, "vendor", vendor);
proc_handler_call(_ph, proc_name, cd);
return calldata_bool(cd, "success");
}
/* ==================== GENERAL API FUNCTIONS ==================== */
// Gets the API version built with the obs-websocket plugin
static inline unsigned int obs_websocket_get_api_version(void)
{
if (!obs_websocket_ensure_ph())
return 0;
calldata_t cd = {0};
if (!proc_handler_call(_ph, "get_api_version", &cd))
return 1; // API v1 does not include get_api_version
unsigned int ret = calldata_int(&cd, "version");
calldata_free(&cd);
return ret;
}
// Calls an obs-websocket request. Free response with `obs_websocket_request_response_free()`
static inline obs_websocket_request_response *obs_websocket_call_request(const char *request_type, obs_data_t *request_data = NULL)
{
if (!obs_websocket_ensure_ph())
return NULL;
const char *request_data_string = NULL;
if (request_data)
request_data_string = obs_data_get_json(request_data);
calldata_t cd = {0};
calldata_set_string(&cd, "request_type", request_type);
calldata_set_string(&cd, "request_data", request_data_string);
proc_handler_call(_ph, "call_request", &cd);
auto ret = (struct obs_websocket_request_response *)calldata_ptr(&cd, "response");
calldata_free(&cd);
return ret;
}
// Free a request response object returned by `obs_websocket_call_request()`
static inline void obs_websocket_request_response_free(struct obs_websocket_request_response *response)
{
if (!response)
return;
if (response->comment)
bfree(response->comment);
if (response->response_data)
bfree(response->response_data);
bfree(response);
}
/* ==================== VENDOR API FUNCTIONS ==================== */
// ALWAYS CALL ONLY VIA `obs_module_post_load()` CALLBACK!
// Registers a new "vendor" (Example: obs-ndi)
static inline obs_websocket_vendor obs_websocket_register_vendor(const char *vendor_name)
{
if (!obs_websocket_ensure_ph())
return NULL;
calldata_t cd = {0};
calldata_set_string(&cd, "name", vendor_name);
proc_handler_call(_ph, "vendor_register", &cd);
obs_websocket_vendor ret = calldata_ptr(&cd, "vendor");
calldata_free(&cd);
return ret;
}
// Registers a new request for a vendor
static inline bool obs_websocket_vendor_register_request(obs_websocket_vendor vendor, const char *request_type,
obs_websocket_request_callback_function request_callback, void *priv_data)
{
calldata_t cd = {0};
struct obs_websocket_request_callback cb = {};
cb.callback = request_callback;
cb.priv_data = priv_data;
calldata_set_string(&cd, "type", request_type);
calldata_set_ptr(&cd, "callback", &cb);
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_register", &cd);
calldata_free(&cd);
return success;
}
// Unregisters an existing vendor request
static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor vendor, const char *request_type)
{
calldata_t cd = {0};
calldata_set_string(&cd, "type", request_type);
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_unregister", &cd);
calldata_free(&cd);
return success;
}
// Does not affect event_data refcount.
// Emits an event under the vendor's name
static inline bool obs_websocket_vendor_emit_event(obs_websocket_vendor vendor, const char *event_name, obs_data_t *event_data)
{
calldata_t cd = {0};
calldata_set_string(&cd, "type", event_name);
calldata_set_ptr(&cd, "data", (void *)event_data);
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_event_emit", &cd);
calldata_free(&cd);
return success;
}
/* ==================== END API FUNCTIONS ==================== */
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -68,7 +68,7 @@
<x>0</x>
<y>0</y>
<width>957</width>
<height>817</height>
<height>905</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_19">
@@ -419,6 +419,49 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="transitionBox">
<property name="title">
<string>AdvSceneSwitcher.generalTab.transition</string>
</property>
<layout class="QGridLayout" name="transitionLayout">
<item row="1" column="1">
<widget class="QCheckBox" name="adjustActiveTransitionType">
<property name="text">
<string>AdvSceneSwitcher.generalTab.adjustActiveTransitionType</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>AdvSceneSwitcher.generalTab.setTransitionBy</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="transitionOverridecheckBox">
<property name="text">
<string>AdvSceneSwitcher.generalTab.transitionOverride</string>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer_9">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="priorityBox">
<property name="title">
@@ -610,7 +653,7 @@
<enum>Qt::MoveAction</enum>
</property>
<property name="spacing">
<number>1</number>
<number>0</number>
</property>
<property name="sortingEnabled">
<bool>false</bool>
@@ -642,7 +685,7 @@
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
<height>16777215</height>
</size>
</property>
<property name="flat">
@@ -658,7 +701,7 @@
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
<height>16777215</height>
</size>
</property>
<property name="flat">
@@ -713,17 +756,14 @@
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string>upArrowIconSmall</string>
<string notr="true">upArrowIconSmall</string>
</property>
</widget>
</item>
@@ -732,17 +772,14 @@
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string>downArrowIconSmall</string>
<string notr="true">downArrowIconSmall</string>
</property>
</widget>
</item>
@@ -769,7 +806,7 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_33">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_18" stretch="0,0,0,0,0,0,0">
<layout class="QHBoxLayout" name="horizontalLayout_18" stretch="0,0,0,0,0,0,0,0,0">
<item>
<widget class="QLabel" name="label_20">
<property name="text">
@@ -789,16 +826,36 @@
</item>
<item>
<widget class="QCheckBox" name="runMacroInParallel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>AdvSceneSwitcher.macroTab.runInParallel</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="runMacroOnChange">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_18">
<property name="text">
<string>AdvSceneSwitcher.macroTab.onChange</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
@@ -819,7 +876,7 @@
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
<height>16777215</height>
</size>
</property>
<property name="text">
@@ -864,7 +921,7 @@
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
<height>16777215</height>
</size>
</property>
<property name="flat">
@@ -880,7 +937,7 @@
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
<height>16777215</height>
</size>
</property>
<property name="flat">
@@ -935,7 +992,7 @@
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
<height>16777215</height>
</size>
</property>
<property name="text">
@@ -954,7 +1011,7 @@
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
<height>16777215</height>
</size>
</property>
<property name="flat">
@@ -1006,7 +1063,7 @@
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
<height>16777215</height>
</size>
</property>
<property name="flat">
@@ -1022,7 +1079,7 @@
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
<height>16777215</height>
</size>
</property>
<property name="flat">
@@ -1077,7 +1134,7 @@
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
<height>16777215</height>
</size>
</property>
<property name="flat">
@@ -1093,7 +1150,7 @@
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
<height>16777215</height>
</size>
</property>
<property name="flat">
@@ -1716,44 +1773,6 @@
<string>AdvSceneSwitcher.transitionTab.title</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_15">
<item>
<layout class="QGridLayout" name="gridLayout_5">
<item row="1" column="1">
<widget class="QCheckBox" name="adjustActiveTransitionType">
<property name="text">
<string>AdvSceneSwitcher.transitionTab.adjustActiveTransitionType</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>AdvSceneSwitcher.transitionTab.setTransitionBy</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="transitionOverridecheckBox">
<property name="text">
<string>AdvSceneSwitcher.transitionTab.transitionOverride</string>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer_9">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="transitionsGroup">
<property name="title">

View File

@@ -227,7 +227,7 @@ void SwitcherData::Thread()
}
}
websocketMessages.clear();
ClearWebsocketMessages();
// After this point we will call frontend functions like
// obs_frontend_set_current_scene() and
@@ -280,6 +280,9 @@ void SwitcherData::setPreconditions()
}
currentTitle = title;
// Process name
GetForegroundProcessName(currentForegroundProcess);
// Cursor
std::pair<int, int> cursorPos = getCursorPos();
cursorPosChanged = cursorPos.first != switcher->lastCursorPos.first ||
@@ -629,9 +632,11 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
case OBS_FRONTEND_EVENT_STREAMING_STOPPED:
resetLiveTime();
break;
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(26, 0, 0)
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED:
setReplayBufferSaved();
break;
#endif
case OBS_FRONTEND_EVENT_TRANSITION_STOPPED:
setTranstionEnd();
break;

View File

@@ -95,6 +95,9 @@ signals:
void ConnectionAdded(const QString &);
void ConnectionRenamed(const QString &oldName, const QString &newName);
void ConnectionRemoved(const QString &);
void VariableAdded(const QString &);
void VariableRenamed(const QString &oldName, const QString &newName);
void VariableRemoved(const QString &);
public slots:
void on_windowUp_clicked();

View File

@@ -192,7 +192,9 @@ void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
bool isLegacyTab(const QString &name)
{
return name == obs_module_text("AdvSceneSwitcher.networkTab.title") ||
return name == obs_module_text(
"AdvSceneSwitcher.sceneGroupTab.title") ||
name == obs_module_text("AdvSceneSwitcher.networkTab.title") ||
name == obs_module_text(
"AdvSceneSwitcher.transitionTab.title") ||
name == obs_module_text(
@@ -221,13 +223,13 @@ void AdvSceneSwitcher::on_hideLegacyTabs_stateChanged(int state)
for (int idx = 0; idx < ui->tabWidget->count(); idx++) {
if (isLegacyTab(ui->tabWidget->tabText(idx))) {
#if defined(_WIN32) || defined(__APPLE__)
ui->tabWidget->setTabVisible(idx, !state);
#else
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
// TODO: Switch to setTabVisible() once QT 5.15 is more wide spread
ui->tabWidget->setTabEnabled(idx, !state);
ui->tabWidget->setStyleSheet(
"QTabBar::tab::disabled {width: 0; height: 0; margin: 0; padding: 0; border: none;} ");
#else
ui->tabWidget->setTabVisible(idx, !state);
#endif
}
}
@@ -494,6 +496,39 @@ void AdvSceneSwitcher::on_tabWidget_currentChanged(int)
SetShowFrames();
}
void AdvSceneSwitcher::on_transitionOverridecheckBox_stateChanged(int state)
{
if (loading) {
return;
}
if (!state && !switcher->adjustActiveTransitionType) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.generalTab.transitionBehaviorSelectionError"));
ui->adjustActiveTransitionType->setChecked(true);
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->transitionOverrideOverride = state;
}
void AdvSceneSwitcher::on_adjustActiveTransitionType_stateChanged(int state)
{
if (loading) {
return;
}
// This option only makes sense if we are allowed to use transition overrides
if (!state && !switcher->transitionOverrideOverride) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.generalTab.transitionBehaviorSelectionError"));
ui->transitionOverridecheckBox->setChecked(true);
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->adjustActiveTransitionType = state;
}
void SwitcherData::loadSettings(obs_data_t *obj)
{
if (!obj) {
@@ -503,6 +538,7 @@ 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);
loadMacros(obj);
loadConnections(obj);
loadWindowTitleSwitches(obj);
@@ -537,6 +573,7 @@ void SwitcherData::saveSettings(obs_data_t *obj)
saveSceneGroups(obj);
saveMacros(obj);
saveConnections(obj);
saveVariables(obj);
saveWindowTitleSwitches(obj);
saveScreenRegionSwitches(obj);
savePauseSwitches(obj);
@@ -595,6 +632,13 @@ void SwitcherData::saveGeneralSettings(obs_data_t *obj)
obs_data_set_int(obj, "priority10", functionNamesByPriority[10]);
obs_data_set_int(obj, "threadPriority", threadPriority);
obs_data_set_bool(obj, "tansitionOverrideOverride",
transitionOverrideOverride);
obs_data_set_default_bool(obj, "adjustActiveTransitionType",
adjustActiveTransitionType);
obs_data_set_bool(obj, "adjustActiveTransitionType",
adjustActiveTransitionType);
}
void SwitcherData::loadGeneralSettings(obs_data_t *obj)
@@ -670,6 +714,11 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
obs_data_set_default_int(obj, "threadPriority",
QThread::NormalPriority);
threadPriority = obs_data_get_int(obj, "threadPriority");
transitionOverrideOverride =
obs_data_get_bool(obj, "tansitionOverrideOverride");
adjustActiveTransitionType =
obs_data_get_bool(obj, "adjustActiveTransitionType");
}
void saveSplitterPos(QList<int> &sizes, obs_data_t *obj, const std::string name)
@@ -687,6 +736,7 @@ void saveSplitterPos(QList<int> &sizes, obs_data_t *obj, const std::string name)
void loadSplitterPos(QList<int> &sizes, obs_data_t *obj, const std::string name)
{
sizes.clear();
obs_data_array_t *array = obs_data_get_array(obj, name.c_str());
size_t count = obs_data_array_count(array);
for (size_t i = 0; i < count; i++) {

View File

@@ -185,18 +185,22 @@ void frontEndActionThread(sceneTriggerAction action, double delay)
case sceneTriggerAction::STOP_STREAMING:
obs_frontend_streaming_stop();
break;
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(26, 0, 0)
case sceneTriggerAction::START_REPLAY_BUFFER:
obs_frontend_replay_buffer_start();
break;
case sceneTriggerAction::STOP_REPLAY_BUFFER:
obs_frontend_replay_buffer_stop();
break;
#endif
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
case sceneTriggerAction::START_VCAM:
obs_frontend_start_virtualcam();
break;
case sceneTriggerAction::STOP_VCAM:
obs_frontend_stop_virtualcam();
break;
#endif
default:
blog(LOG_WARNING, "ignoring unexpected frontend action '%d'",
static_cast<int>(action));

View File

@@ -169,39 +169,6 @@ void SwitcherData::checkDefaultSceneTransitions()
}
}
void AdvSceneSwitcher::on_transitionOverridecheckBox_stateChanged(int state)
{
if (loading) {
return;
}
if (!state && !switcher->adjustActiveTransitionType) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError"));
ui->adjustActiveTransitionType->setChecked(true);
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->transitionOverrideOverride = state;
}
void AdvSceneSwitcher::on_adjustActiveTransitionType_stateChanged(int state)
{
if (loading) {
return;
}
// This option only makes sense if we are allowed to use transition overrides
if (!state && !switcher->transitionOverrideOverride) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError"));
ui->transitionOverridecheckBox->setChecked(true);
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->adjustActiveTransitionType = state;
}
void AdvSceneSwitcher::defTransitionDelayValueChanged(int value)
{
if (loading) {
@@ -335,13 +302,6 @@ void SwitcherData::saveSceneTransitions(obs_data_t *obj)
}
obs_data_set_array(obj, "defaultTransitions", defaultTransitionsArray);
obs_data_array_release(defaultTransitionsArray);
obs_data_set_bool(obj, "tansitionOverrideOverride",
transitionOverrideOverride);
obs_data_set_default_bool(obj, "adjustActiveTransitionType",
adjustActiveTransitionType);
obs_data_set_bool(obj, "adjustActiveTransitionType",
adjustActiveTransitionType);
obs_data_set_default_int(obj, "defTransitionDelay",
default_def_transition_dealy);
obs_data_set_int(obj, "defTransitionDelay",
@@ -380,11 +340,6 @@ void SwitcherData::loadSceneTransitions(obs_data_t *obj)
}
obs_data_array_release(defaultTransitionsArray);
transitionOverrideOverride =
obs_data_get_bool(obj, "tansitionOverrideOverride");
adjustActiveTransitionType =
obs_data_get_bool(obj, "adjustActiveTransitionType");
// Check for invalid config
if (!transitionOverrideOverride && !adjustActiveTransitionType) {
adjustActiveTransitionType = true;

View File

@@ -434,10 +434,24 @@ std::string getProcNameFromPid(int pid)
return buffer.str();
}
bool isInFocus(const QString &executable)
void GetForegroundProcessName(QString &proc)
{
std::string temp;
GetForegroundProcessName(temp);
proc = QString::fromStdString(temp);
}
void GetForegroundProcessName(std::string &proc)
{
proc.resize(0);
auto pid = getForegroundProcessPid();
std::string current = getProcNameFromPid(pid);
}
bool isInFocus(const QString &executable)
{
std::string current;
GetForegroundProcessName(current);
// True if executable switch equals current window
bool equals = (executable.toStdString() == current);

View File

@@ -43,9 +43,11 @@ bool MacroActionRecord::PerformAction()
obs_frontend_recording_pause(false);
}
break;
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(28, 0, 0)
case RecordAction::SPLIT:
obs_frontend_recording_split_file();
break;
#endif
default:
break;
}

View File

@@ -75,22 +75,22 @@ MacroActionRunEdit::MacroActionRunEdit(
_filePath = new FileSelection();
_argList = new QListWidget();
_addArg = new QPushButton();
_addArg->setMaximumSize(QSize(22, 22));
_addArg->setMaximumWidth(22);
_addArg->setProperty("themeID",
QVariant(QString::fromUtf8("addIconSmall")));
_addArg->setFlat(true);
_removeArg = new QPushButton();
_removeArg->setMaximumSize(QSize(22, 22));
_removeArg->setMaximumWidth(22);
_removeArg->setProperty("themeID",
QVariant(QString::fromUtf8("removeIconSmall")));
_removeArg->setFlat(true);
_argUp = new QPushButton();
_argUp->setMaximumSize(QSize(22, 22));
_argUp->setMaximumWidth(22);
_argUp->setProperty("themeID",
QVariant(QString::fromUtf8("upArrowIconSmall")));
_argUp->setFlat(true);
_argDown = new QPushButton();
_argDown->setMaximumSize(QSize(22, 22));
_argDown->setMaximumWidth(22);
_argDown->setProperty(
"themeID", QVariant(QString::fromUtf8("downArrowIconSmall")));
_argDown->setFlat(true);

View File

@@ -157,7 +157,7 @@ MacroActionSceneOrderEdit::MacroActionSceneOrderEdit(
QWidget *parent, std::shared_ptr<MacroActionSceneOrder> entryData)
: QWidget(parent)
{
_scenes = new SceneSelectionWidget(window(), false, false, true);
_scenes = new SceneSelectionWidget(window(), true, false, false, true);
_sources = new SceneItemSelectionWidget(parent);
_actions = new QComboBox();
_position = new QSpinBox();

View File

@@ -55,7 +55,7 @@ int getTransitionOverrideDuration(OBSWeakSource &scene)
return duration;
}
bool isUsingFixedLengthTransition(OBSWeakSource &transition)
bool isUsingFixedLengthTransition(const OBSWeakSource &transition)
{
obs_source_t *source = obs_weak_source_get_source(transition);
bool ret = obs_transition_fixed(source);
@@ -123,15 +123,15 @@ void MacroActionSwitchScene::LogAction()
auto t = _scene.GetType();
auto sceneName = GetWeakSourceName(_scene.GetScene(false));
switch (t) {
case SceneSelectionType::SCENE:
case SceneSelection::Type::SCENE:
vblog(LOG_INFO, "switch to scene '%s'",
_scene.ToString().c_str());
break;
case SceneSelectionType::GROUP:
case SceneSelection::Type::GROUP:
vblog(LOG_INFO, "switch to scene '%s' (scene group '%s')",
sceneName.c_str(), _scene.ToString().c_str());
break;
case SceneSelectionType::PREVIOUS:
case SceneSelection::Type::PREVIOUS:
vblog(LOG_INFO, "switch to previous scene '%s'",
sceneName.c_str());
break;
@@ -153,30 +153,6 @@ bool MacroActionSwitchScene::Save(obs_data_t *obj)
bool MacroActionSwitchScene::Load(obs_data_t *obj)
{
// Convert old data format
// TODO: Remove in future version
if (obs_data_has_user_value(obj, "targetType")) {
auto sceneName = obs_data_get_string(obj, "target");
obs_data_set_string(obj, "scene", sceneName);
auto transitionName = obs_data_get_string(obj, "transition");
bool usePreviousScene =
strcmp(sceneName, previous_scene_name) == 0;
bool useCurrentTransition =
strcmp(transitionName, current_transition_name) == 0;
obs_data_set_int(
obj, "sceneType",
(usePreviousScene)
? static_cast<int>(SceneSelectionType::PREVIOUS)
: obs_data_get_int(obj, "targetType"));
obs_data_set_int(
obj, "transitionType",
(useCurrentTransition)
? static_cast<int>(
TransitionSelectionType::CURRENT)
: static_cast<int>(
TransitionSelectionType::TRANSITION));
}
MacroAction::Load(obj);
_scene.Load(obj);
_transition.Load(obj);
@@ -193,14 +169,14 @@ std::string MacroActionSwitchScene::GetShortDesc()
MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
QWidget *parent, std::shared_ptr<MacroActionSwitchScene> entryData)
: QWidget(parent)
: QWidget(parent),
_scenes(new SceneSelectionWidget(window(), true, true, true)),
_transitions(new TransitionSelectionWidget(this)),
_duration(new DurationSelection(parent, false)),
_blockUntilTransitionDone(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.action.scene.blockUntilTransitionDone"))),
_entryLayout(new QHBoxLayout())
{
_scenes = new SceneSelectionWidget(window(), true, true);
_transitions = new TransitionSelectionWidget(this);
_duration = new DurationSelection(parent, false);
_blockUntilTransitionDone = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.action.scene.blockUntilTransitionDone"));
_duration->SpinBox()->setSpecialValueText("-");
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
@@ -214,7 +190,6 @@ MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
QWidget::connect(_blockUntilTransitionDone, SIGNAL(stateChanged(int)),
this, SLOT(BlockUntilTransitionDoneChanged(int)));
QHBoxLayout *entryLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes},
{"{{transitions}}", _transitions},
@@ -222,10 +197,10 @@ MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
{"{{blockUntilTransitionDone}}", _blockUntilTransitionDone},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.scene.entry"),
entryLayout, widgetPlaceholders);
_entryLayout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLayout);
mainLayout->addLayout(_entryLayout);
mainLayout->addWidget(_blockUntilTransitionDone);
setLayout(mainLayout);
@@ -235,6 +210,7 @@ MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
_duration->SetDuration(_entryData->_duration);
_blockUntilTransitionDone->setChecked(
_entryData->_blockUntilTransitionDone);
SetDurationVisibility();
_loading = false;
}
@@ -258,6 +234,37 @@ void MacroActionSwitchSceneEdit::BlockUntilTransitionDoneChanged(int state)
_entryData->_blockUntilTransitionDone = state;
}
void MacroActionSwitchSceneEdit::SetDurationVisibility()
{
if (_entryData->_transition.GetType() !=
TransitionSelection::Type::TRANSITION) {
_duration->show();
}
const bool fixedDuration = isUsingFixedLengthTransition(
_entryData->_transition.GetTransition());
_duration->setVisible(!fixedDuration);
_entryLayout->removeWidget(_scenes);
_entryLayout->removeWidget(_transitions);
_entryLayout->removeWidget(_duration);
clearLayout(_entryLayout);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes},
{"{{transitions}}", _transitions},
{"{{duration}}", _duration},
};
if (fixedDuration) {
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.action.scene.entry.noDuration"),
_entryLayout, widgetPlaceholders);
} else {
placeWidgets(
obs_module_text("AdvSceneSwitcher.action.scene.entry"),
_entryLayout, widgetPlaceholders);
}
}
void MacroActionSwitchSceneEdit::SceneChanged(const SceneSelection &s)
{
if (_loading || !_entryData) {
@@ -278,4 +285,5 @@ void MacroActionSwitchSceneEdit::TransitionChanged(const TransitionSelection &t)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_transition = t;
SetDurationVisibility();
}

View File

@@ -61,9 +61,11 @@ protected:
TransitionSelectionWidget *_transitions;
DurationSelection *_duration;
QCheckBox *_blockUntilTransitionDone;
QHBoxLayout *_entryLayout;
std::shared_ptr<MacroActionSwitchScene> _entryData;
private:
void SetDurationVisibility();
bool _loading = true;
};

View File

@@ -107,7 +107,7 @@ MacroActionSceneTransformEdit::MacroActionSceneTransformEdit(
QWidget *parent, std::shared_ptr<MacroActionSceneTransform> entryData)
: QWidget(parent)
{
_scenes = new SceneSelectionWidget(window(), false, false, true);
_scenes = new SceneSelectionWidget(window(), true, false, false, true);
_sources = new SceneItemSelectionWidget(parent);
_getSettings = new QPushButton(obs_module_text(
"AdvSceneSwitcher.action.sceneTransform.getTransform"));

View File

@@ -166,7 +166,7 @@ MacroActionSceneVisibilityEdit::MacroActionSceneVisibilityEdit(
QWidget *parent, std::shared_ptr<MacroActionSceneVisibility> entryData)
: QWidget(parent)
{
_scenes = new SceneSelectionWidget(window(), false, true, true);
_scenes = new SceneSelectionWidget(window(), true, false, true, true);
_sourceTypes = new QComboBox();
_sources = new SceneItemSelectionWidget(parent);
_sourceGroups = new QComboBox();

View File

@@ -9,73 +9,155 @@ bool MacroActionScreenshot::_registered = MacroActionFactory::Register(
{MacroActionScreenshot::Create, MacroActionScreenshotEdit::Create,
"AdvSceneSwitcher.action.screenshot"});
bool MacroActionScreenshot::PerformAction()
void MacroActionScreenshot::FrontendScreenshot(OBSWeakSource &source)
{
if (_source) {
auto s = obs_weak_source_get_source(_source);
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(26, 0, 0)
if (source) {
auto s = obs_weak_source_get_source(source);
obs_frontend_take_source_screenshot(s);
obs_source_release(s);
} else {
obs_frontend_take_screenshot();
}
#endif
}
void MacroActionScreenshot::CustomScreenshot(OBSWeakSource &source)
{
if (!source && _targetType == TargetType::SCENE) {
return;
}
auto s = obs_weak_source_get_source(source);
_screenshot.~ScreenshotHelper();
new (&_screenshot) ScreenshotHelper(s, false, 0, true, _path);
obs_source_release(s);
}
bool MacroActionScreenshot::PerformAction()
{
OBSWeakSource source = nullptr;
switch (_targetType) {
case MacroActionScreenshot::TargetType::SOURCE:
source = _source;
break;
case MacroActionScreenshot::TargetType::SCENE:
source = _scene.GetScene(false);
break;
}
switch (_saveType) {
case MacroActionScreenshot::SaveType::OBS_DEFAULT:
FrontendScreenshot(source);
break;
case MacroActionScreenshot::SaveType::CUSTOM:
CustomScreenshot(source);
break;
default:
break;
}
return true;
}
void MacroActionScreenshot::LogAction()
{
vblog(LOG_INFO, "trigger screenshot for source \"%s\"",
GetWeakSourceName(_source).c_str());
vblog(LOG_INFO, "trigger screenshot for \"%s\"",
_targetType == TargetType::SOURCE
? GetWeakSourceName(_source).c_str()
: _scene.ToString().c_str());
}
bool MacroActionScreenshot::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
_scene.Save(obj);
obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str());
obs_data_set_int(obj, "saveType", static_cast<int>(_saveType));
obs_data_set_int(obj, "targetType", static_cast<int>(_targetType));
obs_data_set_string(obj, "savePath", _path.c_str());
return true;
}
bool MacroActionScreenshot::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_scene.Load(obj);
const char *sourceName = obs_data_get_string(obj, "source");
_source = GetWeakSourceByName(sourceName);
_saveType = static_cast<SaveType>(obs_data_get_int(obj, "saveType"));
_targetType =
static_cast<TargetType>(obs_data_get_int(obj, "targetType"));
_path = obs_data_get_string(obj, "savePath");
return true;
}
std::string MacroActionScreenshot::GetShortDesc()
{
if (_source) {
if (_targetType == TargetType::SCENE) {
return _scene.ToString();
} else {
return GetWeakSourceName(_source);
}
return "";
}
void addOBSMainOutputEntry(QComboBox *cb)
static void populateSaveTypeSelection(QComboBox *list)
{
cb->insertItem(
0, obs_module_text(
"AdvSceneSwitcher.action.screenshot.mainOutput"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.action.screenshot.save.default"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.action.screenshot.save.custom"));
}
static void populateTargetTypeSelection(QComboBox *list)
{
list->addItem(obs_module_text(
"AdvSceneSwitcher.action.screenshot.type.source"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.action.screenshot.type.scene"));
}
MacroActionScreenshotEdit::MacroActionScreenshotEdit(
QWidget *parent, std::shared_ptr<MacroActionScreenshot> entryData)
: QWidget(parent)
: QWidget(parent),
_scenes(new SceneSelectionWidget(this, true, false, true, true,
true)),
_sources(new QComboBox()),
_saveType(new QComboBox()),
_targetType(new QComboBox()),
_savePath(new FileSelection(FileSelection::Type::WRITE, this))
{
_sources = new QComboBox();
populateVideoSelection(_sources, true, false);
addOBSMainOutputEntry(_sources);
setToolTip(obs_module_text(
"AdvSceneSwitcher.action.screenshot.blackscreenNote"));
populateVideoSelection(_sources, true);
populateSaveTypeSelection(_saveType);
populateTargetTypeSelection(_targetType);
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_sources, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(SourceChanged(const QString &)));
QWidget::connect(_saveType, SIGNAL(currentIndexChanged(int)), this,
SLOT(SaveTypeChanged(int)));
QWidget::connect(_targetType, SIGNAL(currentIndexChanged(int)), this,
SLOT(TargetTypeChanged(int)));
QWidget::connect(_savePath, SIGNAL(PathChanged(const QString &)), this,
SLOT(PathChanged(const QString &)));
QHBoxLayout *mainLayout = new QHBoxLayout;
QHBoxLayout *layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{sources}}", _sources},
{"{{scenes}}", _scenes},
{"{{saveType}}", _saveType},
{"{{targetType}}", _targetType},
};
placeWidgets(
obs_module_text("AdvSceneSwitcher.action.screenshot.entry"),
mainLayout, widgetPlaceholders);
layout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(layout);
mainLayout->addWidget(_savePath);
setLayout(mainLayout);
_entryData = entryData;
@@ -93,8 +175,55 @@ void MacroActionScreenshotEdit::UpdateEntryData()
_sources->setCurrentText(
GetWeakSourceName(_entryData->_source).c_str());
} else {
_sources->setCurrentIndex(0);
_sources->setCurrentIndex(1);
}
_scenes->SetScene(_entryData->_scene);
_saveType->setCurrentIndex(static_cast<int>(_entryData->_saveType));
_targetType->setCurrentIndex(static_cast<int>(_entryData->_targetType));
_savePath->SetPath(QString::fromStdString(_entryData->_path));
SetWidgetVisibility();
}
void MacroActionScreenshotEdit::SceneChanged(const SceneSelection &s)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_scene = s;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionScreenshotEdit::SaveTypeChanged(int index)
{
if (_loading || !_entryData) {
}
_entryData->_saveType =
static_cast<MacroActionScreenshot::SaveType>(index);
SetWidgetVisibility();
}
void MacroActionScreenshotEdit::TargetTypeChanged(int index)
{
if (_loading || !_entryData) {
}
_entryData->_targetType =
static_cast<MacroActionScreenshot::TargetType>(index);
SetWidgetVisibility();
}
void MacroActionScreenshotEdit::PathChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_path = text.toUtf8().constData();
}
void MacroActionScreenshotEdit::SourceChanged(const QString &text)
@@ -108,3 +237,18 @@ void MacroActionScreenshotEdit::SourceChanged(const QString &text)
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionScreenshotEdit::SetWidgetVisibility()
{
if (!_entryData) {
return;
}
_savePath->setVisible(_entryData->_saveType ==
MacroActionScreenshot::SaveType::CUSTOM);
_sources->setVisible(_entryData->_targetType ==
MacroActionScreenshot::TargetType::SOURCE);
_scenes->setVisible(_entryData->_targetType ==
MacroActionScreenshot::TargetType::SCENE);
adjustSize();
}

View File

@@ -1,5 +1,8 @@
#pragma once
#include "macro-action-edit.hpp"
#include "file-selection.hpp"
#include "scene-selection.hpp"
#include "screenshot-helper.hpp"
#include <QComboBox>
@@ -16,9 +19,25 @@ public:
{
return std::make_shared<MacroActionScreenshot>(m);
}
enum class SaveType {
OBS_DEFAULT,
CUSTOM,
};
SaveType _saveType = SaveType::OBS_DEFAULT;
enum class TargetType {
SOURCE,
SCENE,
};
TargetType _targetType = TargetType::SOURCE;
SceneSelection _scene;
OBSWeakSource _source;
std::string _path = obs_module_text("AdvSceneSwitcher.enterPath");
private:
void FrontendScreenshot(OBSWeakSource &);
void CustomScreenshot(OBSWeakSource &);
ScreenshotHelper _screenshot;
static bool _registered;
static const std::string id;
};
@@ -40,14 +59,24 @@ public:
action));
}
private slots:
void SceneChanged(const SceneSelection &);
void SourceChanged(const QString &text);
void SaveTypeChanged(int index);
void TargetTypeChanged(int index);
void PathChanged(const QString &text);
signals:
void HeaderInfoChanged(const QString &);
protected:
SceneSelectionWidget *_scenes;
QComboBox *_sources;
QComboBox *_saveType;
QComboBox *_targetType;
FileSelection *_savePath;
std::shared_ptr<MacroActionScreenshot> _entryData;
private:
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -124,7 +124,7 @@ MacroActionSudioModeEdit::MacroActionSudioModeEdit(
QWidget *parent, std::shared_ptr<MacroActionSudioMode> entryData)
: QWidget(parent),
_actions(new QComboBox()),
_scenes(new SceneSelectionWidget(window(), true, true, true))
_scenes(new SceneSelectionWidget(window(), true, true, true, true))
{
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,

View File

@@ -49,8 +49,32 @@ void MacroActionTransition::SetTransitionOverride()
obs_source_release(scene);
}
#if (LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)) && \
(LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(28, 0, 0))
void obs_sceneitem_set_transition(obs_sceneitem_t *item, bool show,
obs_source_t *transition)
{
if (show) {
obs_sceneitem_set_show_transition(item, transition);
} else {
obs_sceneitem_set_hide_transition(item, transition);
}
}
void obs_sceneitem_set_transition_duration(obs_sceneitem_t *item, bool show,
uint32_t duration_ms)
{
if (show) {
obs_sceneitem_set_show_transition_duration(item, duration_ms);
} else {
obs_sceneitem_set_hide_transition_duration(item, duration_ms);
}
}
#endif
void MacroActionTransition::SetSourceTransition(bool show)
{
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
auto transition =
obs_weak_source_get_source(_transition.GetTransition());
obs_data_t *settings = obs_source_get_settings(transition);
@@ -73,6 +97,7 @@ void MacroActionTransition::SetSourceTransition(bool show)
}
obs_source_release(t);
#endif
}
bool MacroActionTransition::PerformAction()
@@ -182,7 +207,7 @@ MacroActionTransitionEdit::MacroActionTransitionEdit(
: QWidget(parent),
_actions(new QComboBox),
_sources(new SceneItemSelectionWidget(parent, false)),
_scenes(new SceneSelectionWidget(this, false, false, true)),
_scenes(new SceneSelectionWidget(this, true, false, false, true)),
_setTransition(new QCheckBox),
_setDuration(new QCheckBox),
_transitions(new TransitionSelectionWidget(this, false)),

View File

@@ -0,0 +1,253 @@
#include "macro-action-variable.hpp"
#include "advanced-scene-switcher.hpp"
#include "utility.hpp"
const std::string MacroActionVariable::id = "variable";
bool MacroActionVariable::_registered = MacroActionFactory::Register(
MacroActionVariable::id,
{MacroActionVariable::Create, MacroActionVariableEdit::Create,
"AdvSceneSwitcher.action.variable"});
static std::map<MacroActionVariable::Type, std::string> waitTypes = {
{MacroActionVariable::Type::SET,
"AdvSceneSwitcher.action.variable.type.set"},
{MacroActionVariable::Type::APPEND,
"AdvSceneSwitcher.action.variable.type.append"},
{MacroActionVariable::Type::APPEND_VAR,
"AdvSceneSwitcher.action.variable.type.appendVar"},
{MacroActionVariable::Type::INCREMENT,
"AdvSceneSwitcher.action.variable.type.increment"},
{MacroActionVariable::Type::DECREMENT,
"AdvSceneSwitcher.action.variable.type.decrement"},
};
static void apppend(Variable &var, const std::string &value)
{
auto currentValue = var.Value();
var.SetValue(currentValue + value);
}
static void modifyNumValue(Variable &var, double val, const bool increment)
{
double current;
if (!var.DoubleValue(current)) {
return;
}
if (increment) {
var.SetValue(current + val);
} else {
var.SetValue(current - val);
}
}
bool MacroActionVariable::PerformAction()
{
auto var = GetVariableByName(_variableName);
if (!var) {
return true;
}
switch (_type) {
case MacroActionVariable::Type::SET:
var->SetValue(_strValue);
break;
case MacroActionVariable::Type::APPEND:
apppend(*var, _strValue);
break;
case MacroActionVariable::Type::APPEND_VAR: {
auto var2 = GetVariableByName(_variable2Name);
if (!var2) {
return true;
}
apppend(*var, var2->Value());
break;
}
case MacroActionVariable::Type::INCREMENT:
modifyNumValue(*var, _numValue, true);
break;
case MacroActionVariable::Type::DECREMENT:
modifyNumValue(*var, _numValue, false);
break;
}
return true;
}
bool MacroActionVariable::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
obs_data_set_string(obj, "variableName", _variableName.c_str());
obs_data_set_string(obj, "variable2Name", _variable2Name.c_str());
obs_data_set_string(obj, "strValue", _strValue.c_str());
obs_data_set_double(obj, "numValue", _numValue);
obs_data_set_int(obj, "condition", static_cast<int>(_type));
return true;
}
bool MacroActionVariable::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_variableName = obs_data_get_string(obj, "variableName");
_variable2Name = obs_data_get_string(obj, "variable2Name");
_strValue = obs_data_get_string(obj, "strValue");
_numValue = obs_data_get_double(obj, "numValue");
_type = static_cast<Type>(obs_data_get_int(obj, "condition"));
return true;
}
std::string MacroActionVariable::GetShortDesc()
{
return _variableName;
}
static inline void populateTypeSelection(QComboBox *list)
{
for (auto entry : waitTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionVariableEdit::MacroActionVariableEdit(
QWidget *parent, std::shared_ptr<MacroActionVariable> entryData)
: QWidget(parent),
_variables(new VariableSelection(this)),
_variables2(new VariableSelection(this)),
_actions(new QComboBox()),
_strValue(new QLineEdit()),
_numValue(new QDoubleSpinBox())
{
_numValue->setMinimum(-9999999999);
_numValue->setMaximum(9999999999);
populateTypeSelection(_actions);
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
this, SLOT(VariableChanged(const QString &)));
QWidget::connect(_variables2, SIGNAL(SelectionChanged(const QString &)),
this, SLOT(Variable2Changed(const QString &)));
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_strValue, SIGNAL(editingFinished()), this,
SLOT(StrValueChanged()));
QWidget::connect(_numValue, SIGNAL(valueChanged(double)), this,
SLOT(NumValueChanged(double)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{variables}}", _variables}, {"{{variables2}}", _variables2},
{"{{actions}}", _actions}, {"{{strValue}}", _strValue},
{"{{numValue}}", _numValue},
};
auto mainLayout = new QHBoxLayout;
placeWidgets(obs_module_text("AdvSceneSwitcher.action.variable.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionVariableEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_variables->SetVariable(_entryData->_variableName);
_variables2->SetVariable(_entryData->_variable2Name);
_actions->setCurrentIndex(static_cast<int>(_entryData->_type));
_strValue->setText(QString::fromStdString(_entryData->_strValue));
_numValue->setValue(_entryData->_numValue);
SetWidgetVisibility();
}
void MacroActionVariableEdit::VariableChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_variableName = text.toStdString();
}
void MacroActionVariableEdit::Variable2Changed(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_variable2Name = text.toStdString();
}
void MacroActionVariableEdit::ActionChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_type = static_cast<MacroActionVariable::Type>(value);
SetWidgetVisibility();
}
void MacroActionVariableEdit::StrValueChanged()
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_strValue = _strValue->text().toStdString();
}
void MacroActionVariableEdit::NumValueChanged(double val)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_numValue = val;
}
void MacroActionVariableEdit::SetWidgetVisibility()
{
if (!_entryData) {
return;
}
auto type = _entryData->_type;
switch (type) {
case MacroActionVariable::Type::SET:
_variables2->hide();
_strValue->show();
_numValue->hide();
break;
case MacroActionVariable::Type::APPEND:
_variables2->hide();
_strValue->show();
_numValue->hide();
break;
case MacroActionVariable::Type::APPEND_VAR:
_variables2->show();
_strValue->hide();
_numValue->hide();
break;
case MacroActionVariable::Type::INCREMENT:
_variables2->hide();
_strValue->hide();
_numValue->show();
break;
case MacroActionVariable::Type::DECREMENT:
_variables2->hide();
_strValue->hide();
_numValue->show();
break;
}
adjustSize();
updateGeometry();
}

View File

@@ -0,0 +1,76 @@
#pragma once
#include "macro-action-edit.hpp"
#include "variable.hpp"
class MacroActionVariable : public MacroAction {
public:
MacroActionVariable(Macro *m) : MacroAction(m) {}
bool PerformAction();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
std::string GetShortDesc();
std::string GetId() { return id; };
static std::shared_ptr<MacroAction> Create(Macro *m)
{
return std::make_shared<MacroActionVariable>(m);
}
enum class Type {
SET,
APPEND,
APPEND_VAR,
INCREMENT,
DECREMENT,
//...
};
Type _type = Type::SET;
std::string _variableName = "";
std::string _variable2Name = "";
std::string _strValue = "";
double _numValue = 0;
private:
static bool _registered;
static const std::string id;
};
class MacroActionVariableEdit : public QWidget {
Q_OBJECT
public:
MacroActionVariableEdit(
QWidget *parent,
std::shared_ptr<MacroActionVariable> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionVariableEdit(
parent,
std::dynamic_pointer_cast<MacroActionVariable>(action));
}
private slots:
void VariableChanged(const QString &);
void Variable2Changed(const QString &);
void ActionChanged(int);
void StrValueChanged();
void NumValueChanged(double);
signals:
void HeaderInfoChanged(const QString &);
protected:
VariableSelection *_variables;
VariableSelection *_variables2;
QComboBox *_actions;
QLineEdit *_strValue;
QDoubleSpinBox *_numValue;
std::shared_ptr<MacroActionVariable> _entryData;
private:
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -16,6 +16,7 @@ const static std::map<VCamAction, std::string> actionTypes = {
bool MacroActionVCam::PerformAction()
{
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
switch (_action) {
case VCamAction::STOP:
if (obs_frontend_virtualcam_active()) {
@@ -30,6 +31,7 @@ bool MacroActionVCam::PerformAction()
default:
break;
}
#endif
return true;
}

View File

@@ -9,25 +9,58 @@ bool MacroActionWebsocket::_registered = MacroActionFactory::Register(
{MacroActionWebsocket::Create, MacroActionWebsocketEdit::Create,
"AdvSceneSwitcher.action.websocket"});
bool MacroActionWebsocket::PerformAction()
static std::map<MacroActionWebsocket::Type, std::string> actionTypes = {
{MacroActionWebsocket::Type::REQUEST,
"AdvSceneSwitcher.action.websocket.type.request"},
{MacroActionWebsocket::Type::EVENT,
"AdvSceneSwitcher.action.websocket.type.event"},
};
void MacroActionWebsocket::SendRequest()
{
auto connection = GetConnectionByName(_connection);
if (!connection) {
return true;
return;
}
connection->SendMsg(_message);
}
bool MacroActionWebsocket::PerformAction()
{
switch (_type) {
case MacroActionWebsocket::Type::REQUEST:
SendRequest();
break;
case MacroActionWebsocket::Type::EVENT:
SendWebsocketEvent(_message);
break;
default:
break;
}
return true;
}
void MacroActionWebsocket::LogAction()
{
vblog(LOG_INFO, "sent msg \"%s\" via \"%s\"", _message.c_str(),
_connection.c_str());
switch (_type) {
case MacroActionWebsocket::Type::REQUEST:
vblog(LOG_INFO, "sent msg \"%s\" via \"%s\"", _message.c_str(),
_connection.c_str());
break;
case MacroActionWebsocket::Type::EVENT:
vblog(LOG_INFO, "sent event \"%s\" to connected clients",
_message.c_str());
break;
default:
break;
}
}
bool MacroActionWebsocket::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
obs_data_set_int(obj, "type", static_cast<int>(_type));
obs_data_set_string(obj, "message", _message.c_str());
obs_data_set_string(obj, "connection", _connection.c_str());
return true;
@@ -36,6 +69,7 @@ bool MacroActionWebsocket::Save(obs_data_t *obj)
bool MacroActionWebsocket::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
_message = obs_data_get_string(obj, "message");
_connection = obs_data_get_string(obj, "connection");
return true;
@@ -43,30 +77,39 @@ bool MacroActionWebsocket::Load(obs_data_t *obj)
std::string MacroActionWebsocket::GetShortDesc()
{
return _connection;
if (_type == Type::REQUEST) {
return _connection;
}
return "";
}
static inline void populateActionSelection(QComboBox *list)
{
for (auto entry : actionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionWebsocketEdit::MacroActionWebsocketEdit(
QWidget *parent, std::shared_ptr<MacroActionWebsocket> entryData)
: QWidget(parent),
_actions(new QComboBox(this)),
_message(new ResizingPlainTextEdit(this)),
_connection(new ConnectionSelection(this))
_connection(new ConnectionSelection(this)),
_editLayout(new QHBoxLayout())
{
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_message, SIGNAL(textChanged()), this,
SLOT(MessageChanged()));
QWidget::connect(_connection, SIGNAL(SelectionChanged(const QString &)),
this,
SLOT(ConnectionSelectionChanged(const QString &)));
auto *connectionLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{connection}}", _connection},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.websocket.entry"),
connectionLayout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(connectionLayout);
mainLayout->addLayout(_editLayout);
mainLayout->addWidget(_message);
setLayout(mainLayout);
@@ -75,19 +118,73 @@ MacroActionWebsocketEdit::MacroActionWebsocketEdit(
_loading = false;
}
void MacroActionWebsocketEdit::SetupRequestEdit()
{
_editLayout->removeWidget(_actions);
_editLayout->removeWidget(_connection);
clearLayout(_editLayout);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{type}}", _actions},
{"{{connection}}", _connection},
};
placeWidgets(obs_module_text(
"AdvSceneSwitcher.action.websocket.entry.request"),
_editLayout, widgetPlaceholders);
_connection->show();
}
void MacroActionWebsocketEdit::SetupEventEdit()
{
_editLayout->removeWidget(_actions);
_editLayout->removeWidget(_connection);
clearLayout(_editLayout);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{type}}", _actions},
{"{{connection}}", _connection},
};
placeWidgets(obs_module_text(
"AdvSceneSwitcher.action.websocket.entry.event"),
_editLayout, widgetPlaceholders);
_connection->hide();
}
void MacroActionWebsocketEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_type));
_message->setPlainText(QString::fromStdString(_entryData->_message));
_connection->SetConnection(_entryData->_connection);
if (_entryData->_type == MacroActionWebsocket::Type::REQUEST) {
SetupRequestEdit();
} else {
SetupEventEdit();
}
adjustSize();
updateGeometry();
}
void MacroActionWebsocketEdit::ActionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_type = static_cast<MacroActionWebsocket::Type>(index);
if (_entryData->_type == MacroActionWebsocket::Type::REQUEST) {
SetupRequestEdit();
} else {
SetupEventEdit();
}
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionWebsocketEdit::MessageChanged()
{
if (_loading || !_entryData) {

View File

@@ -22,10 +22,18 @@ public:
return std::make_shared<MacroActionWebsocket>(m);
}
enum class Type {
REQUEST,
EVENT,
};
Type _type = Type::REQUEST;
std::string _message = obs_module_text("AdvSceneSwitcher.enterText");
std::string _connection;
private:
void SendRequest();
static bool _registered;
static const std::string id;
};
@@ -47,6 +55,7 @@ public:
}
private slots:
void ActionChanged(int);
void MessageChanged();
void ConnectionSelectionChanged(const QString &);
signals:
@@ -56,7 +65,12 @@ protected:
std::shared_ptr<MacroActionWebsocket> _entryData;
private:
void SetupRequestEdit();
void SetupEventEdit();
QComboBox *_actions;
ResizingPlainTextEdit *_message;
ConnectionSelection *_connection;
QHBoxLayout *_editLayout;
bool _loading = true;
};

View File

@@ -67,6 +67,26 @@ bool MacroConditionDate::CheckDayOfWeek(int64_t msSinceLastCheck)
return false;
}
bool MacroConditionDate::CheckBetween(QDateTime &now)
{
// In the case of ignoring the date component treat the "left" value as
// the start of the time range and the "right" value as the end
if (_ignoreDate) {
if (_dateTime2 >= _dateTime) {
return now >= _dateTime && now <= _dateTime2;
}
// Assume that the end / start value should be shifted by 24h
// as otherwise the condition would never be true
return (now >= _dateTime && now <= _dateTime2.addDays(1)) ||
(now >= _dateTime.addDays(-1) && now <= _dateTime2);
}
if (_dateTime2 >= _dateTime) {
return now >= _dateTime && now <= _dateTime2;
}
return now >= _dateTime2 && now <= _dateTime;
}
bool MacroConditionDate::CheckRegularDate(int64_t msSinceLastCheck)
{
bool match = false;
@@ -92,11 +112,7 @@ bool MacroConditionDate::CheckRegularDate(int64_t msSinceLastCheck)
match = cur <= _dateTime;
break;
case DateCondition::BETWEEN:
if (_dateTime2 > _dateTime) {
match = cur >= _dateTime && cur <= _dateTime2;
} else {
match = cur >= _dateTime2 && cur <= _dateTime;
}
match = CheckBetween(cur);
break;
default:
break;
@@ -232,12 +248,12 @@ void MacroConditionDate::SetTime2(const QTime &time)
QDateTime MacroConditionDate::GetDateTime1()
{
return _updateOnRepeat ? _dateTime : _origDateTime;
return _repeat && _updateOnRepeat ? _dateTime : _origDateTime;
}
QDateTime MacroConditionDate::GetDateTime2()
{
return _updateOnRepeat ? _dateTime2 : _origDateTime2;
return _repeat && _updateOnRepeat ? _dateTime2 : _origDateTime2;
}
QDateTime MacroConditionDate::GetNextMatchDateTime()
@@ -576,12 +592,12 @@ void MacroConditionDateEdit::UpdateEntryData()
_dayOfWeek->setCurrentIndex(
static_cast<int>(static_cast<int>(_entryData->_dayOfWeek)));
_ignoreWeekTime->setChecked(!_entryData->_ignoreTime);
_weekTime->setDateTime(_entryData->GetDateTime1());
_weekTime->setTime(_entryData->GetDateTime1().time());
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
_date->setDateTime(_entryData->GetDateTime1());
_time->setDateTime(_entryData->GetDateTime1());
_date2->setDateTime(_entryData->GetDateTime2());
_time2->setDateTime(_entryData->GetDateTime2());
_date->setDate(_entryData->GetDateTime1().date());
_time->setTime(_entryData->GetDateTime1().time());
_date2->setDate(_entryData->GetDateTime2().date());
_time2->setTime(_entryData->GetDateTime2().time());
_ignoreDate->setChecked(!_entryData->_ignoreDate);
_ignoreTime->setChecked(!_entryData->_ignoreTime);
_repeat->setChecked(_entryData->_repeat);

View File

@@ -58,6 +58,7 @@ public:
private:
bool CheckDayOfWeek(int64_t);
bool CheckRegularDate(int64_t);
bool CheckBetween(QDateTime &now);
QDateTime _dateTime = QDateTime::currentDateTime();
QDateTime _dateTime2 = QDateTime::currentDateTime();

View File

@@ -120,7 +120,7 @@ DurationModifierEdit::DurationModifierEdit(QWidget *parent)
_condition = new QComboBox(parent);
_duration = new DurationSelection(parent);
_toggle = new QPushButton(parent);
_toggle->setMaximumSize(22, 22);
_toggle->setMaximumWidth(22);
_toggle->setIcon(
QIcon(QString::fromStdString(getDataFilePath("res/time.svg"))));
populateDurationModifierTypes(_condition);
@@ -447,6 +447,9 @@ void AdvSceneSwitcher::RemoveMacroCondition(int idx)
if (idx == 0 && macro->Conditions().size() > 0) {
auto newRoot = macro->Conditions().at(0);
newRoot->SetLogicType(LogicType::ROOT_NONE);
static_cast<MacroConditionEdit *>(
conditionsList->WidgetAt(0))
->SetRootNode(true);
}
SetConditionData(*macro);
}

View File

@@ -40,7 +40,7 @@ static std::string getRemoteData(std::string &url)
return readBuffer;
}
bool MacroConditionFile::matchFileContent(QString &filedata)
bool MacroConditionFile::MatchFileContent(QString &filedata)
{
if (_onlyMatchIfChanged) {
size_t newHash = strHash(filedata.toUtf8().constData());
@@ -50,31 +50,30 @@ bool MacroConditionFile::matchFileContent(QString &filedata)
_lastHash = newHash;
}
if (_useRegex) {
try {
std::regex expr(_text);
return std::regex_match(filedata.toStdString(), expr);
} catch (const std::regex_error &) {
if (_regex.Enabled()) {
auto expr = _regex.GetRegularExpression(_text);
if (!expr.isValid()) {
return false;
}
auto match = expr.match(filedata);
return match.hasMatch();
}
QString text = QString::fromStdString(_text);
return compareIgnoringLineEnding(text, filedata);
}
bool MacroConditionFile::checkRemoteFileContent()
bool MacroConditionFile::CheckRemoteFileContent()
{
std::string data = getRemoteData(_file);
QString qdata = QString::fromStdString(data);
return matchFileContent(qdata);
return MatchFileContent(qdata);
}
bool MacroConditionFile::checkLocalFileContent()
bool MacroConditionFile::CheckLocalFileContent()
{
QString t = QString::fromStdString(_text);
QFile file(QString::fromStdString(_file));
if (!file.open(QIODevice::ReadOnly)) {
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
@@ -87,28 +86,78 @@ bool MacroConditionFile::checkLocalFileContent()
}
QString filedata = QTextStream(&file).readAll();
bool match = matchFileContent(filedata);
bool match = MatchFileContent(filedata);
file.close();
return match;
}
bool MacroConditionFile::CheckCondition()
bool MacroConditionFile::CheckChangeContent()
{
QString filedata;
switch (_fileType) {
case FileType::LOCAL: {
QFile file(QString::fromStdString(_file));
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
filedata = QTextStream(&file).readAll();
file.close();
} break;
case FileType::REMOTE: {
std::string data = getRemoteData(_file);
QString filedata = QString::fromStdString(data);
} break;
default:
break;
}
size_t newHash = strHash(filedata.toUtf8().constData());
const bool contentChanged = newHash != _lastHash;
_lastHash = newHash;
return contentChanged;
}
bool MacroConditionFile::CheckChangeDate()
{
if (_fileType == FileType::REMOTE) {
return checkRemoteFileContent();
} else {
return checkLocalFileContent();
return false;
}
QFile file(QString::fromStdString(_file));
QDateTime newLastMod = QFileInfo(file).lastModified();
const bool dateChanged = _lastMod != newLastMod;
_lastMod = newLastMod;
return dateChanged;
}
bool MacroConditionFile::CheckCondition()
{
switch (_condition) {
case MacroConditionFile::ConditionType::MATCH:
if (_fileType == FileType::REMOTE) {
return CheckRemoteFileContent();
}
return CheckLocalFileContent();
case MacroConditionFile::ConditionType::CONTENT_CHANGE:
return CheckChangeContent();
case MacroConditionFile::ConditionType::DATE_CHANGE:
return CheckChangeDate();
default:
break;
}
return false;
}
bool MacroConditionFile::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
_regex.Save(obj);
obs_data_set_string(obj, "file", _file.c_str());
obs_data_set_string(obj, "text", _text.c_str());
obs_data_set_int(obj, "fileType", static_cast<int>(_fileType));
obs_data_set_bool(obj, "useRegex", _useRegex);
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
obs_data_set_bool(obj, "useTime", _useTime);
obs_data_set_bool(obj, "onlyMatchIfChanged", _onlyMatchIfChanged);
return true;
@@ -117,10 +166,17 @@ bool MacroConditionFile::Save(obs_data_t *obj)
bool MacroConditionFile::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_regex.Load(obj);
// TODO: remove in future version
if (obs_data_has_user_value(obj, "useRegex")) {
_regex.CreateBackwardsCompatibleRegex(
obs_data_get_bool(obj, "useRegex"));
}
_file = obs_data_get_string(obj, "file");
_text = obs_data_get_string(obj, "text");
_fileType = static_cast<FileType>(obs_data_get_int(obj, "fileType"));
_useRegex = obs_data_get_bool(obj, "useRegex");
_condition =
static_cast<ConditionType>(obs_data_get_int(obj, "condition"));
_useTime = obs_data_get_bool(obj, "useTime");
_onlyMatchIfChanged = obs_data_get_bool(obj, "onlyMatchIfChanged");
return true;
@@ -131,41 +187,59 @@ std::string MacroConditionFile::GetShortDesc()
return _file;
}
static void populateFileTypes(QComboBox *list)
{
list->addItem(obs_module_text("AdvSceneSwitcher.fileTab.local"));
list->addItem(obs_module_text("AdvSceneSwitcher.fileTab.remote"));
}
static void populateConditions(QComboBox *list)
{
list->addItem(
obs_module_text("AdvSceneSwitcher.condition.file.type.match"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.condition.file.type.contentChange"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.condition.file.type.dateChange"));
}
MacroConditionFileEdit::MacroConditionFileEdit(
QWidget *parent, std::shared_ptr<MacroConditionFile> entryData)
: QWidget(parent)
: QWidget(parent),
_fileTypes(new QComboBox()),
_conditions(new QComboBox()),
_filePath(new FileSelection()),
_matchText(new ResizingPlainTextEdit(this)),
_regex(new RegexConfigWidget(parent)),
_checkModificationDate(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.fileTab.checkfileContentTime"))),
_checkFileContent(new QCheckBox(
obs_module_text("AdvSceneSwitcher.fileTab.checkfileContent")))
{
_fileType = new QComboBox();
_filePath = new FileSelection();
_matchText = new ResizingPlainTextEdit(this);
_useRegex = new QCheckBox(
obs_module_text("AdvSceneSwitcher.fileTab.useRegExp"));
_checkModificationDate = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.fileTab.checkfileContentTime"));
_checkFileContent = new QCheckBox(
obs_module_text("AdvSceneSwitcher.fileTab.checkfileContent"));
populateFileTypes(_fileTypes);
populateConditions(_conditions);
QWidget::connect(_fileType, SIGNAL(currentIndexChanged(int)), this,
QWidget::connect(_fileTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(FileTypeChanged(int)));
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this,
SLOT(PathChanged(const QString &)));
QWidget::connect(_matchText, SIGNAL(textChanged()), this,
SLOT(MatchTextChanged()));
QWidget::connect(_useRegex, SIGNAL(stateChanged(int)), this,
SLOT(UseRegexChanged(int)));
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
SLOT(RegexChanged(RegexConfig)));
QWidget::connect(_checkModificationDate, SIGNAL(stateChanged(int)),
this, SLOT(CheckModificationDateChanged(int)));
QWidget::connect(_checkFileContent, SIGNAL(stateChanged(int)), this,
SLOT(OnlyMatchIfChangedChanged(int)));
_fileType->addItem(obs_module_text("AdvSceneSwitcher.fileTab.local"));
_fileType->addItem(obs_module_text("AdvSceneSwitcher.fileTab.remote"));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{fileType}}", _fileType},
{"{{fileType}}", _fileTypes},
{"{{conditions}}", _conditions},
{"{{filePath}}", _filePath},
{"{{matchText}}", _matchText},
{"{{useRegex}}", _useRegex},
{"{{useRegex}}", _regex},
{"{{checkModificationDate}}", _checkModificationDate},
{"{{checkFileContent}}", _checkFileContent},
};
@@ -200,16 +274,23 @@ void MacroConditionFileEdit::UpdateEntryData()
return;
}
_fileType->setCurrentIndex(static_cast<int>(_entryData->_fileType));
_fileTypes->setCurrentIndex(static_cast<int>(_entryData->_fileType));
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
_filePath->SetPath(QString::fromStdString(_entryData->_file));
_matchText->setPlainText(QString::fromStdString(_entryData->_text));
_useRegex->setChecked(_entryData->_useRegex);
_regex->SetRegexConfig(_entryData->_regex);
_checkModificationDate->setChecked(_entryData->_useTime);
_checkFileContent->setChecked(_entryData->_onlyMatchIfChanged);
adjustSize();
updateGeometry();
// TODO: Remove in future version
if (!_entryData->_useTime) {
_checkModificationDate->hide();
}
if (!_entryData->_onlyMatchIfChanged) {
_checkFileContent->hide();
}
SetWidgetVisibility();
}
void MacroConditionFileEdit::FileTypeChanged(int index)
@@ -218,9 +299,10 @@ void MacroConditionFileEdit::FileTypeChanged(int index)
return;
}
FileType type = static_cast<FileType>(index);
MacroConditionFile::FileType type =
static_cast<MacroConditionFile::FileType>(index);
if (type == FileType::LOCAL) {
if (type == MacroConditionFile::FileType::LOCAL) {
_filePath->Button()->setDisabled(false);
_checkModificationDate->setDisabled(false);
} else {
@@ -232,6 +314,18 @@ void MacroConditionFileEdit::FileTypeChanged(int index)
_entryData->_fileType = type;
}
void MacroConditionFileEdit::ConditionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_condition =
static_cast<MacroConditionFile::ConditionType>(index);
SetWidgetVisibility();
}
void MacroConditionFileEdit::PathChanged(const QString &text)
{
if (_loading || !_entryData) {
@@ -257,14 +351,16 @@ void MacroConditionFileEdit::MatchTextChanged()
updateGeometry();
}
void MacroConditionFileEdit::UseRegexChanged(int state)
void MacroConditionFileEdit::RegexChanged(RegexConfig conf)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_useRegex = state;
_entryData->_regex = conf;
adjustSize();
updateGeometry();
}
void MacroConditionFileEdit::CheckModificationDateChanged(int state)
@@ -286,3 +382,25 @@ void MacroConditionFileEdit::OnlyMatchIfChangedChanged(int state)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_onlyMatchIfChanged = state;
}
void MacroConditionFileEdit::SetWidgetVisibility()
{
if (!_entryData) {
return;
}
_matchText->setVisible(_entryData->_condition ==
MacroConditionFile::ConditionType::MATCH);
_regex->setVisible(_entryData->_condition ==
MacroConditionFile::ConditionType::MATCH);
_checkModificationDate->setVisible(
_entryData->_useTime &&
_entryData->_condition ==
MacroConditionFile::ConditionType::MATCH);
_checkFileContent->setVisible(
_entryData->_onlyMatchIfChanged &&
_entryData->_condition ==
MacroConditionFile::ConditionType::MATCH);
adjustSize();
updateGeometry();
}

View File

@@ -2,6 +2,7 @@
#include "macro.hpp"
#include "file-selection.hpp"
#include "resizing-text-edit.hpp"
#include "regex-config.hpp"
#include <QWidget>
#include <QComboBox>
@@ -10,11 +11,6 @@
#include <QPushButton>
#include <QCheckBox>
enum class FileType {
LOCAL,
REMOTE,
};
class MacroConditionFile : public MacroCondition {
public:
MacroConditionFile(Macro *m) : MacroCondition(m) {}
@@ -28,17 +24,33 @@ public:
return std::make_shared<MacroConditionFile>(m);
}
enum class FileType {
LOCAL,
REMOTE,
};
enum class ConditionType {
MATCH,
CONTENT_CHANGE,
DATE_CHANGE,
};
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
std::string _text = obs_module_text("AdvSceneSwitcher.enterText");
FileType _fileType = FileType::LOCAL;
bool _useRegex = false;
ConditionType _condition = ConditionType::MATCH;
RegexConfig _regex;
// TODO: Remove in future version
bool _useTime = false;
bool _onlyMatchIfChanged = false;
private:
bool matchFileContent(QString &filedata);
bool checkRemoteFileContent();
bool checkLocalFileContent();
bool MatchFileContent(QString &filedata);
bool CheckRemoteFileContent();
bool CheckLocalFileContent();
bool CheckChangeContent();
bool CheckChangeDate();
QDateTime _lastMod;
size_t _lastHash = 0;
@@ -64,23 +76,27 @@ public:
private slots:
void FileTypeChanged(int index);
void ConditionChanged(int index);
void PathChanged(const QString &text);
void MatchTextChanged();
void UseRegexChanged(int state);
void RegexChanged(RegexConfig);
void CheckModificationDateChanged(int state);
void OnlyMatchIfChangedChanged(int state);
signals:
void HeaderInfoChanged(const QString &);
protected:
QComboBox *_fileType;
QComboBox *_fileTypes;
QComboBox *_conditions;
FileSelection *_filePath;
ResizingPlainTextEdit *_matchText;
QCheckBox *_useRegex;
RegexConfigWidget *_regex;
QCheckBox *_checkModificationDate;
QCheckBox *_checkFileContent;
std::shared_ptr<MacroConditionFile> _entryData;
private:
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -56,7 +56,7 @@ bool MacroConditionFilter::Save(obs_data_t *obj)
obs_data_set_string(obj, "filter", GetWeakSourceName(_filter).c_str());
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
obs_data_set_string(obj, "settings", _settings.c_str());
obs_data_set_bool(obj, "regex", _regex);
_regex.Save(obj);
return true;
}
@@ -70,7 +70,12 @@ bool MacroConditionFilter::Load(obs_data_t *obj)
_condition = static_cast<FilterCondition>(
obs_data_get_int(obj, "condition"));
_settings = obs_data_get_string(obj, "settings");
_regex = obs_data_get_bool(obj, "regex");
_regex.Load(obj);
// TOOD: remove in future version
if (obs_data_has_user_value(obj, "regex")) {
_regex.CreateBackwardsCompatibleRegex(
obs_data_get_bool(obj, "regex"));
}
return true;
}
@@ -92,18 +97,16 @@ static inline void populateConditionSelection(QComboBox *list)
MacroConditionFilterEdit::MacroConditionFilterEdit(
QWidget *parent, std::shared_ptr<MacroConditionFilter> entryData)
: QWidget(parent)
: QWidget(parent),
_sources(new QComboBox()),
_filters(new QComboBox()),
_conditions(new QComboBox()),
_getSettings(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.filter.getSettings"))),
_settings(new ResizingPlainTextEdit(this)),
_regex(new RegexConfigWidget(parent))
{
_sources = new QComboBox();
_filters = new QComboBox();
_filters->setSizeAdjustPolicy(QComboBox::AdjustToContents);
_conditions = new QComboBox();
_getSettings = new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.filter.getSettings"));
_settings = new ResizingPlainTextEdit(this);
_regex = new QCheckBox(
obs_module_text("AdvSceneSwitcher.condition.filter.regex"));
populateConditionSelection(_conditions);
populateSourcesWithFilterSelection(_sources);
@@ -117,8 +120,8 @@ MacroConditionFilterEdit::MacroConditionFilterEdit(
SLOT(GetSettingsClicked()));
QWidget::connect(_settings, SIGNAL(textChanged()), this,
SLOT(SettingsChanged()));
QWidget::connect(_regex, SIGNAL(stateChanged(int)), this,
SLOT(RegexChanged(int)));
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
SLOT(RegexChanged(RegexConfig)));
QHBoxLayout *line1Layout = new QHBoxLayout;
QHBoxLayout *line2Layout = new QHBoxLayout;
@@ -194,7 +197,7 @@ void MacroConditionFilterEdit::GetSettingsClicked()
}
QString json = formatJsonString(getSourceSettings(_entryData->_filter));
if (_entryData->_regex) {
if (_entryData->_regex.Enabled()) {
json = escapeForRegex(json);
}
_settings->setPlainText(json);
@@ -213,14 +216,17 @@ void MacroConditionFilterEdit::SettingsChanged()
updateGeometry();
}
void MacroConditionFilterEdit::RegexChanged(int state)
void MacroConditionFilterEdit::RegexChanged(RegexConfig conf)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_regex = state;
_entryData->_regex = conf;
adjustSize();
updateGeometry();
}
void MacroConditionFilterEdit::SetSettingsSelectionVisible(bool visible)
@@ -244,7 +250,7 @@ void MacroConditionFilterEdit::UpdateEntryData()
GetWeakSourceName(_entryData->_filter).c_str());
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
_settings->setPlainText(QString::fromStdString(_entryData->_settings));
_regex->setChecked(_entryData->_regex);
_regex->SetRegexConfig(_entryData->_regex);
SetSettingsSelectionVisible(_entryData->_condition ==
FilterCondition::SETTINGS);

View File

@@ -1,6 +1,7 @@
#pragma once
#include "macro.hpp"
#include "resizing-text-edit.hpp"
#include "regex-config.hpp"
#include <QComboBox>
#include <QPushButton>
@@ -29,7 +30,7 @@ public:
OBSWeakSource _filter;
FilterCondition _condition = FilterCondition::ENABLED;
std::string _settings = "";
bool _regex = false;
RegexConfig _regex;
private:
static bool _registered;
@@ -58,7 +59,7 @@ private slots:
void ConditionChanged(int cond);
void GetSettingsClicked();
void SettingsChanged();
void RegexChanged(int);
void RegexChanged(RegexConfig);
signals:
void HeaderInfoChanged(const QString &);
@@ -68,7 +69,7 @@ protected:
QComboBox *_conditions;
QPushButton *_getSettings;
ResizingPlainTextEdit *_settings;
QCheckBox *_regex;
RegexConfigWidget *_regex;
std::shared_ptr<MacroConditionFilter> _entryData;

View File

@@ -124,6 +124,7 @@ bool MacroConditionMacro::Save(obs_data_t *obj)
obs_data_set_int(obj, "type", static_cast<int>(_type));
obs_data_set_int(obj, "condition", static_cast<int>(_counterCondition));
obs_data_set_int(obj, "count", _count);
obs_data_set_int(obj, "multiStateCount", _multiSateCount);
return true;
}
@@ -136,6 +137,7 @@ bool MacroConditionMacro::Load(obs_data_t *obj)
_counterCondition = static_cast<CounterCondition>(
obs_data_get_int(obj, "condition"));
_count = obs_data_get_int(obj, "count");
_multiSateCount = obs_data_get_int(obj, "multiStateCount");
return true;
}

View File

@@ -10,39 +10,40 @@ bool MacroConditionMedia::_registered = MacroConditionFactory::Register(
{MacroConditionMedia::Create, MacroConditionMediaEdit::Create,
"AdvSceneSwitcher.condition.media"});
static std::map<MediaTimeRestriction, std::string> mediaTimeRestrictions = {
{MediaTimeRestriction::TIME_RESTRICTION_NONE,
static std::map<MacroConditionMedia::Time, std::string> mediaTimeRestrictions = {
{MacroConditionMedia::Time::TIME_RESTRICTION_NONE,
"AdvSceneSwitcher.mediaTab.timeRestriction.none"},
{MediaTimeRestriction::TIME_RESTRICTION_SHORTER,
{MacroConditionMedia::Time::TIME_RESTRICTION_SHORTER,
"AdvSceneSwitcher.mediaTab.timeRestriction.shorter"},
{MediaTimeRestriction::TIME_RESTRICTION_LONGER,
{MacroConditionMedia::Time::TIME_RESTRICTION_LONGER,
"AdvSceneSwitcher.mediaTab.timeRestriction.longer"},
{MediaTimeRestriction::TIME_RESTRICTION_REMAINING_SHORTER,
{MacroConditionMedia::Time::TIME_RESTRICTION_REMAINING_SHORTER,
"AdvSceneSwitcher.mediaTab.timeRestriction.remainShorter"},
{MediaTimeRestriction::TIME_RESTRICTION_REMAINING_LONGER,
{MacroConditionMedia::Time::TIME_RESTRICTION_REMAINING_LONGER,
"AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger"},
};
static std::map<MediaState, std::string> mediaStates = {
{MediaState::OBS_MEDIA_STATE_NONE,
static std::map<MacroConditionMedia::State, std::string> mediaStates = {
{MacroConditionMedia::State::OBS_MEDIA_STATE_NONE,
"AdvSceneSwitcher.mediaTab.states.none"},
{MediaState::OBS_MEDIA_STATE_PLAYING,
{MacroConditionMedia::State::OBS_MEDIA_STATE_PLAYING,
"AdvSceneSwitcher.mediaTab.states.playing"},
{MediaState::OBS_MEDIA_STATE_OPENING,
{MacroConditionMedia::State::OBS_MEDIA_STATE_OPENING,
"AdvSceneSwitcher.mediaTab.states.opening"},
{MediaState::OBS_MEDIA_STATE_BUFFERING,
{MacroConditionMedia::State::OBS_MEDIA_STATE_BUFFERING,
"AdvSceneSwitcher.mediaTab.states.buffering"},
{MediaState::OBS_MEDIA_STATE_PAUSED,
{MacroConditionMedia::State::OBS_MEDIA_STATE_PAUSED,
"AdvSceneSwitcher.mediaTab.states.paused"},
{MediaState::OBS_MEDIA_STATE_STOPPED,
{MacroConditionMedia::State::OBS_MEDIA_STATE_STOPPED,
"AdvSceneSwitcher.mediaTab.states.stopped"},
{MediaState::OBS_MEDIA_STATE_ENDED,
{MacroConditionMedia::State::OBS_MEDIA_STATE_ENDED,
"AdvSceneSwitcher.mediaTab.states.ended"},
{MediaState::OBS_MEDIA_STATE_ERROR,
{MacroConditionMedia::State::OBS_MEDIA_STATE_ERROR,
"AdvSceneSwitcher.mediaTab.states.error"},
{MediaState::PLAYLIST_ENDED,
{MacroConditionMedia::State::PLAYLIST_ENDED,
"AdvSceneSwitcher.mediaTab.states.playlistEnd"},
{MediaState::ANY, "AdvSceneSwitcher.mediaTab.states.any"},
{MacroConditionMedia::State::ANY,
"AdvSceneSwitcher.mediaTab.states.any"},
};
MacroConditionMedia::~MacroConditionMedia()
@@ -65,20 +66,20 @@ bool MacroConditionMedia::CheckTime()
bool match = false;
switch (_restriction) {
case MediaTimeRestriction::TIME_RESTRICTION_NONE:
case Time::TIME_RESTRICTION_NONE:
match = true;
break;
case MediaTimeRestriction::TIME_RESTRICTION_SHORTER:
case Time::TIME_RESTRICTION_SHORTER:
match = currentTime < _time.seconds * 1000;
break;
case MediaTimeRestriction::TIME_RESTRICTION_LONGER:
case Time::TIME_RESTRICTION_LONGER:
match = currentTime > _time.seconds * 1000;
break;
case MediaTimeRestriction::TIME_RESTRICTION_REMAINING_SHORTER:
case Time::TIME_RESTRICTION_REMAINING_SHORTER:
match = duration > currentTime &&
duration - currentTime < _time.seconds * 1000;
break;
case MediaTimeRestriction::TIME_RESTRICTION_REMAINING_LONGER:
case Time::TIME_RESTRICTION_REMAINING_LONGER:
match = duration > currentTime &&
duration - currentTime > _time.seconds * 1000;
break;
@@ -100,24 +101,24 @@ bool MacroConditionMedia::CheckState()
int expectedState = static_cast<int>(_state);
switch (_state) {
case MediaState::OBS_MEDIA_STATE_STOPPED:
case State::OBS_MEDIA_STATE_STOPPED:
match = _stopped || currentState == OBS_MEDIA_STATE_STOPPED;
break;
case MediaState::OBS_MEDIA_STATE_ENDED:
case State::OBS_MEDIA_STATE_ENDED:
match = _ended || currentState == OBS_MEDIA_STATE_ENDED;
break;
case MediaState::PLAYLIST_ENDED:
case State::PLAYLIST_ENDED:
match = CheckPlaylistEnd(currentState);
break;
case MediaState::ANY:
case State::ANY:
match = true;
break;
case MediaState::OBS_MEDIA_STATE_NONE:
case MediaState::OBS_MEDIA_STATE_PLAYING:
case MediaState::OBS_MEDIA_STATE_OPENING:
case MediaState::OBS_MEDIA_STATE_BUFFERING:
case MediaState::OBS_MEDIA_STATE_PAUSED:
case MediaState::OBS_MEDIA_STATE_ERROR:
case State::OBS_MEDIA_STATE_NONE:
case State::OBS_MEDIA_STATE_PLAYING:
case State::OBS_MEDIA_STATE_OPENING:
case State::OBS_MEDIA_STATE_BUFFERING:
case State::OBS_MEDIA_STATE_PAUSED:
case State::OBS_MEDIA_STATE_ERROR:
match = currentState == expectedState;
break;
default:
@@ -148,7 +149,7 @@ bool MacroConditionMedia::CheckMediaMatch()
bool match = false;
bool matched = CheckState() && CheckTime();
if (matched && !(_onlyMatchonChagne && _alreadyMatched)) {
if (matched && !(_onlyMatchOnChagne && _alreadyMatched)) {
match = true;
}
_alreadyMatched = matched;
@@ -161,16 +162,22 @@ bool MacroConditionMedia::CheckMediaMatch()
return match;
}
void MacroConditionMedia::HandleSceneChange()
{
UpdateMediaSourcesOfSceneList();
_lastConfigureScene = switcher->currentScene;
}
bool MacroConditionMedia::CheckCondition()
{
bool match = false;
switch (_sourceType) {
case MediaSourceType::ANY:
case Type::ANY:
for (auto &source : _sources) {
match = match || source.CheckCondition();
}
break;
case MediaSourceType::ALL: {
case Type::ALL: {
bool res = true;
for (auto &source : _sources) {
res = res && source.CheckCondition();
@@ -178,12 +185,17 @@ bool MacroConditionMedia::CheckCondition()
match = res;
break;
}
case MediaSourceType::SOURCE:
case Type::SOURCE:
match = CheckMediaMatch();
break;
default:
break;
}
if (_lastConfigureScene != switcher->currentScene) {
HandleSceneChange();
}
return match;
}
@@ -196,7 +208,7 @@ bool MacroConditionMedia::Save(obs_data_t *obj)
obs_data_set_int(obj, "state", static_cast<int>(_state));
obs_data_set_int(obj, "restriction", static_cast<int>(_restriction));
_time.Save(obj);
obs_data_set_bool(obj, "matchOnChagne", _onlyMatchonChagne);
obs_data_set_bool(obj, "matchOnChagne", _onlyMatchOnChagne);
obs_data_set_int(obj, "version", 0);
return true;
}
@@ -222,22 +234,24 @@ static bool enumSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
return true;
}
void forMediaSourceOnSceneAddMediaCondition(
OBSWeakSource sceneWeakSource, MacroConditionMedia *origCond,
std::vector<MacroConditionMedia> &conditions)
void MacroConditionMedia::UpdateMediaSourcesOfSceneList()
{
conditions.clear();
_sources.clear();
if (!_scene.GetScene(false)) {
return;
}
std::vector<OBSWeakSource> mediaSources;
auto s = obs_weak_source_get_source(sceneWeakSource);
auto s = obs_weak_source_get_source(_scene.GetScene(false));
auto scene = obs_scene_from_source(s);
obs_scene_enum_items(scene, enumSceneItem, &mediaSources);
obs_source_release(s);
_sources.reserve(mediaSources.size());
for (auto &source : mediaSources) {
MacroConditionMedia cond(*origCond);
cond._sourceType = MediaSourceType::SOURCE;
MacroConditionMedia cond(*this);
cond._sourceType = Type::SOURCE;
cond._source = source;
conditions.push_back(cond);
_sources.push_back(cond);
}
}
@@ -247,15 +261,15 @@ bool MacroConditionMedia::Load(obs_data_t *obj)
const char *sourceName = obs_data_get_string(obj, "source");
_source = GetWeakSourceByName(sourceName);
_scene.Load(obj);
_sourceType = static_cast<MediaSourceType>(
obs_data_get_int(obj, "sourceType"));
_state = static_cast<MediaState>(obs_data_get_int(obj, "state"));
_restriction = static_cast<MediaTimeRestriction>(
_sourceType = static_cast<Type>(obs_data_get_int(obj, "sourceType"));
_state = static_cast<MacroConditionMedia::State>(
obs_data_get_int(obj, "state"));
_restriction = static_cast<MacroConditionMedia::Time>(
obs_data_get_int(obj, "restriction"));
_time.Load(obj);
_onlyMatchonChagne = obs_data_get_bool(obj, "matchOnChagne");
_onlyMatchOnChagne = obs_data_get_bool(obj, "matchOnChagne");
if (_sourceType == MediaSourceType::SOURCE) {
if (_sourceType == Type::SOURCE) {
obs_source_t *mediasource = obs_weak_source_get_source(_source);
signal_handler_t *sh =
obs_source_get_signal_handler(mediasource);
@@ -265,11 +279,11 @@ bool MacroConditionMedia::Load(obs_data_t *obj)
obs_source_release(mediasource);
}
forMediaSourceOnSceneAddMediaCondition(_scene.GetScene(), this,
_sources);
UpdateMediaSourcesOfSceneList();
if (!obs_data_has_user_value(obj, "version")) {
if (_state == MediaState::OBS_MEDIA_STATE_ENDED) {
_state = MediaState::PLAYLIST_ENDED;
if (_state ==
MacroConditionMedia::State::OBS_MEDIA_STATE_ENDED) {
_state = MacroConditionMedia::State::PLAYLIST_ENDED;
}
}
return true;
@@ -278,20 +292,20 @@ bool MacroConditionMedia::Load(obs_data_t *obj)
std::string MacroConditionMedia::GetShortDesc()
{
switch (_sourceType) {
case MediaSourceType::SOURCE:
case Type::SOURCE:
if (_source) {
return GetWeakSourceName(_source);
}
break;
case MediaSourceType::ANY:
if (_scene.GetScene()) {
case Type::ANY:
if (_scene.GetScene(false)) {
return obs_module_text(
"AdvSceneSwitcher.condition.media.anyOnScene") +
std::string(" ") + _scene.ToString();
}
break;
case MediaSourceType::ALL:
if (_scene.GetScene()) {
case Type::ALL:
if (_scene.GetScene(false)) {
return obs_module_text(
"AdvSceneSwitcher.condition.media.allOnScene") +
std::string(" ") + _scene.ToString();
@@ -356,7 +370,7 @@ void MacroConditionMedia::MediaNext(void *data, calldata_t *)
media->_next = true;
}
static void populateMediaTimeRestrictions(QComboBox *list)
static void populateMediaTimes(QComboBox *list)
{
for (auto entry : mediaTimeRestrictions) {
list->addItem(obs_module_text(entry.second.c_str()));
@@ -383,7 +397,8 @@ static void addAnyAndAllStates(QComboBox *list)
MacroConditionMediaEdit::MacroConditionMediaEdit(
QWidget *parent, std::shared_ptr<MacroConditionMedia> entryData)
: QWidget(parent),
_scenes(new SceneSelectionWidget(window())),
_scenes(new SceneSelectionWidget(window(), true, true, true, true,
true)),
_mediaSources(new QComboBox()),
_states(new QComboBox()),
_timeRestrictions(new QComboBox()),
@@ -414,7 +429,7 @@ MacroConditionMediaEdit::MacroConditionMediaEdit(
populateMediaSelection(_mediaSources);
addAnyAndAllStates(_mediaSources);
populateMediaStates(_states);
populateMediaTimeRestrictions(_timeRestrictions);
populateMediaTimes(_timeRestrictions);
QHBoxLayout *entryLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
@@ -446,14 +461,14 @@ void MacroConditionMediaEdit::SourceChanged(const QString &text)
if (text ==
obs_module_text("AdvSceneSwitcher.condition.media.anyOnScene")) {
_entryData->_sourceType = MediaSourceType::ANY;
_entryData->_sourceType = MacroConditionMedia::Type::ANY;
} else if (text ==
obs_module_text(
"AdvSceneSwitcher.condition.media.allOnScene")) {
_entryData->_sourceType = MediaSourceType::ALL;
_entryData->_sourceType = MacroConditionMedia::Type::ALL;
} else {
_entryData->_sources.clear();
_entryData->_sourceType = MediaSourceType::SOURCE;
_entryData->_sourceType = MacroConditionMedia::Type::SOURCE;
}
_entryData->ClearSignalHandler();
@@ -473,21 +488,21 @@ void MacroConditionMediaEdit::SceneChanged(const SceneSelection &s)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_scene = s;
forMediaSourceOnSceneAddMediaCondition(_entryData->_scene.GetScene(),
_entryData.get(),
_entryData->_sources);
_entryData->UpdateMediaSourcesOfSceneList();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
MediaState getMediaStateFromIdx(int idx)
MacroConditionMedia::State getMediaStateFromIdx(int idx)
{
if (idx < static_cast<int>(MediaState::LAST_OBS_MEDIA_STATE)) {
return static_cast<MediaState>(idx);
if (idx < static_cast<int>(
MacroConditionMedia::State::LAST_OBS_MEDIA_STATE)) {
return static_cast<MacroConditionMedia::State>(idx);
} else {
return static_cast<MediaState>(
return static_cast<MacroConditionMedia::State>(
idx -
static_cast<int>(MediaState::LAST_OBS_MEDIA_STATE) +
static_cast<int>(
MacroConditionMedia::State::LAST_OBS_MEDIA_STATE) +
custom_media_states_offset);
}
}
@@ -500,10 +515,8 @@ void MacroConditionMediaEdit::StateChanged(int index)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_state = getMediaStateFromIdx(index);
if (_entryData->_sourceType != MediaSourceType::SOURCE) {
forMediaSourceOnSceneAddMediaCondition(
_entryData->_scene.GetScene(), _entryData.get(),
_entryData->_sources);
if (_entryData->_sourceType != MacroConditionMedia::Type::SOURCE) {
_entryData->UpdateMediaSourcesOfSceneList();
}
}
@@ -513,19 +526,18 @@ void MacroConditionMediaEdit::TimeRestrictionChanged(int index)
return;
}
if (static_cast<MediaTimeRestriction>(index) ==
MediaTimeRestriction::TIME_RESTRICTION_NONE) {
if (static_cast<MacroConditionMedia::Time>(index) ==
MacroConditionMedia::Time::TIME_RESTRICTION_NONE) {
_time->setDisabled(true);
} else {
_time->setDisabled(false);
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_restriction = static_cast<MediaTimeRestriction>(index);
if (_entryData->_sourceType != MediaSourceType::SOURCE) {
forMediaSourceOnSceneAddMediaCondition(
_entryData->_scene.GetScene(), _entryData.get(),
_entryData->_sources);
_entryData->_restriction =
static_cast<MacroConditionMedia::Time>(index);
if (_entryData->_sourceType != MacroConditionMedia::Type::SOURCE) {
_entryData->UpdateMediaSourcesOfSceneList();
}
}
@@ -537,10 +549,8 @@ void MacroConditionMediaEdit::TimeChanged(double seconds)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_time.seconds = seconds;
if (_entryData->_sourceType != MediaSourceType::SOURCE) {
forMediaSourceOnSceneAddMediaCondition(
_entryData->_scene.GetScene(), _entryData.get(),
_entryData->_sources);
if (_entryData->_sourceType != MacroConditionMedia::Type::SOURCE) {
_entryData->UpdateMediaSourcesOfSceneList();
}
}
@@ -552,10 +562,8 @@ void MacroConditionMediaEdit::TimeUnitChanged(DurationUnit unit)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_time.displayUnit = unit;
if (_entryData->_sourceType != MediaSourceType::SOURCE) {
forMediaSourceOnSceneAddMediaCondition(
_entryData->_scene.GetScene(), _entryData.get(),
_entryData->_sources);
if (_entryData->_sourceType != MacroConditionMedia::Type::SOURCE) {
_entryData->UpdateMediaSourcesOfSceneList();
}
}
@@ -566,29 +574,29 @@ void MacroConditionMediaEdit::OnChangeChanged(int value)
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_onlyMatchonChagne = value;
if (_entryData->_sourceType != MediaSourceType::SOURCE) {
forMediaSourceOnSceneAddMediaCondition(
_entryData->_scene.GetScene(), _entryData.get(),
_entryData->_sources);
_entryData->_onlyMatchOnChagne = value;
if (_entryData->_sourceType != MacroConditionMedia::Type::SOURCE) {
_entryData->UpdateMediaSourcesOfSceneList();
}
}
void MacroConditionMediaEdit::SetWidgetVisibility()
{
_scenes->setVisible(_entryData->_sourceType != MediaSourceType::SOURCE);
_scenes->setVisible(_entryData->_sourceType !=
MacroConditionMedia::Type::SOURCE);
if (!_onChange->isChecked()) {
_onChange->hide();
}
}
int getIdxFromMediaState(MediaState state)
int getIdxFromMediaState(MacroConditionMedia::State state)
{
if (state < MediaState::LAST_OBS_MEDIA_STATE) {
if (state < MacroConditionMedia::State::LAST_OBS_MEDIA_STATE) {
return static_cast<int>(state);
} else {
return static_cast<int>(state) - custom_media_states_offset +
static_cast<int>(MediaState::LAST_OBS_MEDIA_STATE);
static_cast<int>(
MacroConditionMedia::State::LAST_OBS_MEDIA_STATE);
}
}
@@ -599,15 +607,15 @@ void MacroConditionMediaEdit::UpdateEntryData()
}
switch (_entryData->_sourceType) {
case MediaSourceType::ANY:
case MacroConditionMedia::Type::ANY:
_mediaSources->setCurrentText(obs_module_text(
"AdvSceneSwitcher.condition.media.anyOnScene"));
break;
case MediaSourceType::ALL:
case MacroConditionMedia::Type::ALL:
_mediaSources->setCurrentText(obs_module_text(
"AdvSceneSwitcher.condition.media.allOnScene"));
break;
case MediaSourceType::SOURCE:
case MacroConditionMedia::Type::SOURCE:
_mediaSources->setCurrentText(
GetWeakSourceName(_entryData->_source).c_str());
break;
@@ -621,9 +629,9 @@ void MacroConditionMediaEdit::UpdateEntryData()
static_cast<int>(_entryData->_restriction));
_time->SetDuration(_entryData->_time);
if (_entryData->_restriction ==
MediaTimeRestriction::TIME_RESTRICTION_NONE) {
MacroConditionMedia::Time::TIME_RESTRICTION_NONE) {
_time->setDisabled(true);
}
_onChange->setChecked(_entryData->_onlyMatchonChagne);
_onChange->setChecked(_entryData->_onlyMatchOnChagne);
SetWidgetVisibility();
}

View File

@@ -8,39 +8,8 @@
#include "duration-control.hpp"
#include "scene-selection.hpp"
enum class MediaTimeRestriction {
TIME_RESTRICTION_NONE,
TIME_RESTRICTION_SHORTER,
TIME_RESTRICTION_LONGER,
TIME_RESTRICTION_REMAINING_SHORTER,
TIME_RESTRICTION_REMAINING_LONGER,
};
constexpr auto custom_media_states_offset = 100;
enum class MediaState {
// OBS's internal states
OBS_MEDIA_STATE_NONE,
OBS_MEDIA_STATE_PLAYING,
OBS_MEDIA_STATE_OPENING,
OBS_MEDIA_STATE_BUFFERING,
OBS_MEDIA_STATE_PAUSED,
OBS_MEDIA_STATE_STOPPED,
OBS_MEDIA_STATE_ENDED,
OBS_MEDIA_STATE_ERROR,
// Just a marker
LAST_OBS_MEDIA_STATE,
// states added for use in the plugin
PLAYLIST_ENDED = custom_media_states_offset,
ANY,
};
enum class MediaSourceType {
SOURCE,
ANY,
ALL,
};
class MacroConditionMedia : public MacroCondition {
public:
MacroConditionMedia(Macro *m) : MacroCondition(m) {}
@@ -56,25 +25,57 @@ public:
}
void ClearSignalHandler();
void ResetSignalHandler();
void UpdateMediaSourcesOfSceneList();
static void MediaStopped(void *data, calldata_t *);
static void MediaEnded(void *data, calldata_t *);
static void MediaNext(void *data, calldata_t *);
MediaSourceType _sourceType = MediaSourceType::SOURCE;
enum class Type {
SOURCE,
ANY,
ALL,
};
Type _sourceType = Type::SOURCE;
enum class State {
// OBS's internal states
OBS_MEDIA_STATE_NONE,
OBS_MEDIA_STATE_PLAYING,
OBS_MEDIA_STATE_OPENING,
OBS_MEDIA_STATE_BUFFERING,
OBS_MEDIA_STATE_PAUSED,
OBS_MEDIA_STATE_STOPPED,
OBS_MEDIA_STATE_ENDED,
OBS_MEDIA_STATE_ERROR,
// Just a marker
LAST_OBS_MEDIA_STATE,
// states added for use in the plugin
PLAYLIST_ENDED = custom_media_states_offset,
ANY,
};
State _state = State::OBS_MEDIA_STATE_NONE;
enum class Time {
TIME_RESTRICTION_NONE,
TIME_RESTRICTION_SHORTER,
TIME_RESTRICTION_LONGER,
TIME_RESTRICTION_REMAINING_SHORTER,
TIME_RESTRICTION_REMAINING_LONGER,
};
Time _restriction = Time::TIME_RESTRICTION_NONE;
SceneSelection _scene;
OBSWeakSource _source = nullptr;
std::vector<MacroConditionMedia> _sources;
MediaState _state = MediaState::OBS_MEDIA_STATE_NONE;
MediaTimeRestriction _restriction =
MediaTimeRestriction::TIME_RESTRICTION_NONE;
Duration _time;
bool _onlyMatchonChagne = false;
bool _onlyMatchOnChagne = false;
private:
bool CheckTime();
bool CheckState();
bool CheckPlaylistEnd(const obs_media_state);
bool CheckMediaMatch();
void HandleSceneChange();
bool _stopped = false;
bool _ended = false;
@@ -84,6 +85,8 @@ private:
bool _alreadyMatched = false;
// Workaround to enable use of "ended" to specify end of VLC playlist
bool _previousStateEnded = false;
// Used to keep track of scene changes
OBSWeakSource _lastConfigureScene;
static bool _registered;
static const std::string id;

View File

@@ -48,36 +48,48 @@ std::string MacroConditionProcess::GetShortDesc()
MacroConditionProcessEdit::MacroConditionProcessEdit(
QWidget *parent, std::shared_ptr<MacroConditionProcess> entryData)
: QWidget(parent)
: QWidget(parent),
_processSelection(new QComboBox()),
_focused(new QCheckBox()),
_focusProcess(new QLabel()),
_focusLayout(new QHBoxLayout())
{
_processSelection = new QComboBox();
_processSelection->setEditable(true);
_processSelection->setMaxVisibleItems(20);
_focused = new QCheckBox();
QWidget::connect(_processSelection,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(ProcessChanged(const QString &)));
QWidget::connect(_focused, SIGNAL(stateChanged(int)), this,
SLOT(FocusChanged(int)));
QWidget::connect(&_timer, SIGNAL(timeout()), this,
SLOT(UpdateFocusProcess()));
populateProcessSelection(_processSelection);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{processes}}", _processSelection},
{"{{focused}}", _focused},
{"{{focusProcess}}", _focusProcess},
};
QHBoxLayout *mainLayout = new QHBoxLayout;
auto entryLayout = new QHBoxLayout;
placeWidgets(
obs_module_text("AdvSceneSwitcher.condition.process.entry"),
mainLayout, widgetPlaceholders);
entryLayout, widgetPlaceholders);
placeWidgets(obs_module_text(
"AdvSceneSwitcher.condition.process.entry.focus"),
_focusLayout, widgetPlaceholders);
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLayout);
mainLayout->addLayout(_focusLayout);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
_timer.start(1000);
}
void MacroConditionProcessEdit::ProcessChanged(const QString &text)
@@ -100,6 +112,22 @@ void MacroConditionProcessEdit::FocusChanged(int state)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_focus = state;
SetWidgetVisibility();
}
void MacroConditionProcessEdit::UpdateFocusProcess()
{
_focusProcess->setText(
QString::fromStdString(switcher->currentForegroundProcess));
}
void MacroConditionProcessEdit::SetWidgetVisibility()
{
if (!_entryData) {
return;
}
setLayoutVisible(_focusLayout, _entryData->_focus);
adjustSize();
}
void MacroConditionProcessEdit::UpdateEntryData()
@@ -110,4 +138,5 @@ void MacroConditionProcessEdit::UpdateEntryData()
_processSelection->setCurrentText(_entryData->_process.c_str());
_focused->setChecked(_entryData->_focus);
SetWidgetVisibility();
}

View File

@@ -44,14 +44,20 @@ public:
private slots:
void ProcessChanged(const QString &text);
void FocusChanged(int state);
void UpdateFocusProcess();
signals:
void HeaderInfoChanged(const QString &);
protected:
QComboBox *_processSelection;
QCheckBox *_focused;
QLabel *_focusProcess;
QHBoxLayout *_focusLayout;
QTimer _timer;
std::shared_ptr<MacroConditionProcess> _entryData;
private:
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -140,7 +140,7 @@ bool MacroConditionSceneOrder::Save(obs_data_t *obj)
MacroCondition::Save(obj);
_scene.Save(obj);
_source.Save(obj);
_source2.Save(obj, "sceneItem2", "sceneItemTarget2", "sceneItemIdx2");
_source2.Save(obj, "sceneItemSelection2");
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
obs_data_set_int(obj, "position", _position);
return true;
@@ -160,7 +160,14 @@ bool MacroConditionSceneOrder::Load(obs_data_t *obj)
MacroCondition::Load(obj);
_scene.Load(obj);
_source.Load(obj);
_source2.Load(obj, "sceneItem2", "sceneItemTarget2", "sceneItemIdx2");
// Convert old data format
// TODO: Remove in future version
if (obs_data_has_user_value(obj, "sceneItem2")) {
_source2.Load(obj, "sceneItem2", "sceneItemTarget2",
"sceneItemIdx2");
} else {
_source2.Load(obj, "sceneItemSelection2");
}
_condition = static_cast<SceneOrderCondition>(
obs_data_get_int(obj, "condition"));
_position = obs_data_get_int(obj, "position");
@@ -191,7 +198,7 @@ MacroConditionSceneOrderEdit::MacroConditionSceneOrderEdit(
QWidget *parent, std::shared_ptr<MacroConditionSceneOrder> entryData)
: QWidget(parent)
{
_scenes = new SceneSelectionWidget(window(), false, false, true);
_scenes = new SceneSelectionWidget(window(), true, false, false, true);
_sources = new SceneItemSelectionWidget(parent);
_sources2 = new SceneItemSelectionWidget(parent);
_conditions = new QComboBox();

View File

@@ -33,7 +33,7 @@ bool MacroConditionSceneTransform::Save(obs_data_t *obj)
_scene.Save(obj);
_source.Save(obj);
obs_data_set_string(obj, "settings", _settings.c_str());
obs_data_set_bool(obj, "regex", _regex);
_regex.Save(obj);
return true;
}
@@ -50,7 +50,12 @@ bool MacroConditionSceneTransform::Load(obs_data_t *obj)
_scene.Load(obj);
_source.Load(obj);
_settings = obs_data_get_string(obj, "settings");
_regex = obs_data_get_bool(obj, "regex");
_regex.Load(obj);
// TOOD: remove in future version
if (obs_data_has_user_value(obj, "regex")) {
_regex.CreateBackwardsCompatibleRegex(
obs_data_get_bool(obj, "regex"));
}
return true;
}
@@ -66,17 +71,16 @@ std::string MacroConditionSceneTransform::GetShortDesc()
MacroConditionSceneTransformEdit::MacroConditionSceneTransformEdit(
QWidget *parent,
std::shared_ptr<MacroConditionSceneTransform> entryData)
: QWidget(parent)
: QWidget(parent),
_scenes(new SceneSelectionWidget(window(), true, false, false, true)),
_sources(new SceneItemSelectionWidget(
parent, true,
SceneItemSelectionWidget::AllSelectionType::ANY)),
_getSettings(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.sceneTransform.getTransform"))),
_settings(new ResizingPlainTextEdit(this)),
_regex(new RegexConfigWidget(parent))
{
_scenes = new SceneSelectionWidget(window(), false, false, true);
_sources = new SceneItemSelectionWidget(
parent, true, SceneItemSelectionWidget::AllSelectionType::ANY);
_getSettings = new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.sceneTransform.getTransform"));
_settings = new ResizingPlainTextEdit(this);
_regex = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.sceneTransform.regex"));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
@@ -88,8 +92,8 @@ MacroConditionSceneTransformEdit::MacroConditionSceneTransformEdit(
SLOT(GetSettingsClicked()));
QWidget::connect(_settings, SIGNAL(textChanged()), this,
SLOT(SettingsChanged()));
QWidget::connect(_regex, SIGNAL(stateChanged(int)), this,
SLOT(RegexChanged(int)));
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
SLOT(RegexChanged(RegexConfig)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes}, {"{{sources}}", _sources},
@@ -132,7 +136,7 @@ void MacroConditionSceneTransformEdit::UpdateEntryData()
_scenes->SetScene(_entryData->_scene);
_sources->SetSceneItem(_entryData->_source);
_regex->setChecked(_entryData->_regex);
_regex->SetRegexConfig(_entryData->_regex);
_settings->setPlainText(QString::fromStdString(_entryData->_settings));
adjustSize();
@@ -175,7 +179,7 @@ void MacroConditionSceneTransformEdit::GetSettingsClicked()
}
auto settings = formatJsonString(getSceneItemTransform(items[0]));
if (_entryData->_regex) {
if (_entryData->_regex.Enabled()) {
settings = escapeForRegex(settings);
}
_settings->setPlainText(settings);
@@ -198,12 +202,15 @@ void MacroConditionSceneTransformEdit::SettingsChanged()
updateGeometry();
}
void MacroConditionSceneTransformEdit::RegexChanged(int state)
void MacroConditionSceneTransformEdit::RegexChanged(RegexConfig conf)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_regex = state;
_entryData->_regex = conf;
adjustSize();
updateGeometry();
}

View File

@@ -3,6 +3,7 @@
#include "scene-selection.hpp"
#include "scene-item-selection.hpp"
#include "resizing-text-edit.hpp"
#include "regex-config.hpp"
#include <QSpinBox>
#include <QCheckBox>
@@ -22,7 +23,7 @@ public:
SceneSelection _scene;
SceneItemSelection _source;
bool _regex = false;
RegexConfig _regex;
std::string _settings = "";
private:
@@ -53,7 +54,7 @@ private slots:
void SourceChanged(const SceneItemSelection &);
void GetSettingsClicked();
void SettingsChanged();
void RegexChanged(int);
void RegexChanged(RegexConfig);
signals:
void HeaderInfoChanged(const QString &);
@@ -62,7 +63,7 @@ protected:
SceneItemSelectionWidget *_sources;
QPushButton *_getSettings;
ResizingPlainTextEdit *_settings;
QCheckBox *_regex;
RegexConfigWidget *_regex;
std::shared_ptr<MacroConditionSceneTransform> _entryData;

View File

@@ -113,7 +113,7 @@ MacroConditionSceneVisibilityEdit::MacroConditionSceneVisibilityEdit(
std::shared_ptr<MacroConditionSceneVisibility> entryData)
: QWidget(parent)
{
_scenes = new SceneSelectionWidget(window(), false, true, true);
_scenes = new SceneSelectionWidget(window(), true, false, true, true);
_sources = new SceneItemSelectionWidget(parent);
_conditions = new QComboBox();

View File

@@ -95,7 +95,7 @@ MacroConditionSceneEdit::MacroConditionSceneEdit(
QWidget *parent, std::shared_ptr<MacroConditionScene> entryData)
: QWidget(parent)
{
_scenes = new SceneSelectionWidget(window(), false, false, false);
_scenes = new SceneSelectionWidget(window(), true, false, false, false);
_sceneType = new QComboBox();
_useTransitionTargetScene = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour"));

View File

@@ -3,8 +3,6 @@
#include "utility.hpp"
#include "advanced-scene-switcher.hpp"
#include <regex>
const std::string MacroConditionSource::id = "source";
bool MacroConditionSource::_registered = MacroConditionFactory::Register(
@@ -55,7 +53,7 @@ bool MacroConditionSource::Save(obs_data_t *obj)
obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str());
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
obs_data_set_string(obj, "settings", _settings.c_str());
obs_data_set_bool(obj, "regex", _regex);
_regex.Save(obj);
return true;
}
@@ -67,7 +65,12 @@ bool MacroConditionSource::Load(obs_data_t *obj)
_condition = static_cast<SourceCondition>(
obs_data_get_int(obj, "condition"));
_settings = obs_data_get_string(obj, "settings");
_regex = obs_data_get_bool(obj, "regex");
_regex.Load(obj);
// TOOD: remove in future version
if (obs_data_has_user_value(obj, "regex")) {
_regex.CreateBackwardsCompatibleRegex(
obs_data_get_bool(obj, "regex"));
}
return true;
}
@@ -88,16 +91,14 @@ static inline void populateConditionSelection(QComboBox *list)
MacroConditionSourceEdit::MacroConditionSourceEdit(
QWidget *parent, std::shared_ptr<MacroConditionSource> entryData)
: QWidget(parent)
: QWidget(parent),
_sources(new QComboBox()),
_conditions(new QComboBox()),
_getSettings(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.filter.getSettings"))),
_settings(new ResizingPlainTextEdit(this)),
_regex(new RegexConfigWidget(parent))
{
_sources = new QComboBox();
_conditions = new QComboBox();
_getSettings = new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.source.getSettings"));
_settings = new ResizingPlainTextEdit(this);
_regex = new QCheckBox(
obs_module_text("AdvSceneSwitcher.condition.source.regex"));
populateConditionSelection(_conditions);
populateSourceSelection(_sources);
@@ -109,8 +110,8 @@ MacroConditionSourceEdit::MacroConditionSourceEdit(
SLOT(GetSettingsClicked()));
QWidget::connect(_settings, SIGNAL(textChanged()), this,
SLOT(SettingsChanged()));
QWidget::connect(_regex, SIGNAL(stateChanged(int)), this,
SLOT(RegexChanged(int)));
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
SLOT(RegexChanged(RegexConfig)));
QVBoxLayout *mainLayout = new QVBoxLayout;
QHBoxLayout *line1Layout = new QHBoxLayout;
@@ -171,7 +172,7 @@ void MacroConditionSourceEdit::GetSettingsClicked()
}
QString json = formatJsonString(getSourceSettings(_entryData->_source));
if (_entryData->_regex) {
if (_entryData->_regex.Enabled()) {
json = escapeForRegex(json);
}
_settings->setPlainText(json);
@@ -190,14 +191,17 @@ void MacroConditionSourceEdit::SettingsChanged()
updateGeometry();
}
void MacroConditionSourceEdit::RegexChanged(int state)
void MacroConditionSourceEdit::RegexChanged(RegexConfig conf)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_regex = state;
_entryData->_regex = conf;
adjustSize();
updateGeometry();
}
void MacroConditionSourceEdit::SetSettingsSelectionVisible(bool visible)
@@ -218,7 +222,7 @@ void MacroConditionSourceEdit::UpdateEntryData()
GetWeakSourceName(_entryData->_source).c_str());
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
_settings->setPlainText(QString::fromStdString(_entryData->_settings));
_regex->setChecked(_entryData->_regex);
_regex->SetRegexConfig(_entryData->_regex);
SetSettingsSelectionVisible(_entryData->_condition ==
SourceCondition::SETTINGS);

View File

@@ -1,6 +1,7 @@
#pragma once
#include "macro.hpp"
#include "resizing-text-edit.hpp"
#include "regex-config.hpp"
#include <QComboBox>
#include <QPushButton>
@@ -28,7 +29,7 @@ public:
OBSWeakSource _source = nullptr;
SourceCondition _condition = SourceCondition::ACTIVE;
std::string _settings = "";
bool _regex = false;
RegexConfig _regex;
private:
static bool _registered;
@@ -56,7 +57,7 @@ private slots:
void ConditionChanged(int cond);
void GetSettingsClicked();
void SettingsChanged();
void RegexChanged(int);
void RegexChanged(RegexConfig);
signals:
void HeaderInfoChanged(const QString &);
@@ -65,7 +66,7 @@ protected:
QComboBox *_conditions;
QPushButton *_getSettings;
ResizingPlainTextEdit *_settings;
QCheckBox *_regex;
RegexConfigWidget *_regex;
std::shared_ptr<MacroConditionSource> _entryData;

View File

@@ -80,7 +80,7 @@ MacroConditionStudioModeEdit::MacroConditionStudioModeEdit(
: QWidget(parent)
{
_condition = new QComboBox();
_scenes = new SceneSelectionWidget(window());
_scenes = new SceneSelectionWidget(window(), true, false, true, true);
populateConditionSelection(_condition);

View File

@@ -54,7 +54,7 @@ bool MacroConditionTransition::CheckCondition()
bool transitionStarted = false;
bool transitionEnded = false;
if (_transition.GetType() == TransitionSelectionType::ANY) {
if (_transition.GetType() == TransitionSelection::Type::ANY) {
transitionStarted = anyTransitionStarted;
transitionEnded = anyTransitionEnded;
} else {
@@ -183,7 +183,7 @@ MacroConditionTransitionEdit::MacroConditionTransitionEdit(
{
_conditions = new QComboBox();
_transitions = new TransitionSelectionWidget(this, true, true);
_scenes = new SceneSelectionWidget(this, false, true, true);
_scenes = new SceneSelectionWidget(this, true, false, true, true);
_duration = new DurationSelection(this, false);
_durationSuffix = new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.transition.durationSuffix"));

View File

@@ -0,0 +1,283 @@
#include "macro-condition-edit.hpp"
#include "macro-condition-variable.hpp"
#include "utility.hpp"
#include "advanced-scene-switcher.hpp"
#include <regex>
const std::string MacroConditionVariable::id = "variable";
bool MacroConditionVariable::_registered = MacroConditionFactory::Register(
MacroConditionVariable::id,
{MacroConditionVariable::Create, MacroConditionVariableEdit::Create,
"AdvSceneSwitcher.condition.variable"});
const static std::map<MacroConditionVariable::Type, std::string>
conditionTypes = {
{MacroConditionVariable::Type::EQUALS,
"AdvSceneSwitcher.condition.variable.type.compare"},
{MacroConditionVariable::Type::IS_EMPTY,
"AdvSceneSwitcher.condition.variable.type.empty"},
{MacroConditionVariable::Type::IS_NUMBER,
"AdvSceneSwitcher.condition.variable.type.number"},
{MacroConditionVariable::Type::LESS_THAN,
"AdvSceneSwitcher.condition.variable.type.lessThan"},
{MacroConditionVariable::Type::GREATER_THAN,
"AdvSceneSwitcher.condition.variable.type.greaterThan"},
{MacroConditionVariable::Type::VALUE_CHANGED,
"AdvSceneSwitcher.condition.variable.type.valueChanged"},
};
static bool isNumber(const Variable &var)
{
double _;
return var.DoubleValue(_);
}
static bool compareNumber(const Variable &var, double value, bool less)
{
double varValue;
if (!var.DoubleValue(varValue)) {
return false;
}
if (less) {
return varValue < value;
}
return varValue > value;
}
bool MacroConditionVariable::Compare(const Variable &var) const
{
if (_regex) {
try {
std::regex expr(_strValue);
return std::regex_match(var.Value(), expr);
} catch (const std::regex_error &) {
return false;
}
} else {
return _strValue == var.Value();
}
}
bool MacroConditionVariable::ValueChanged(const Variable &var)
{
bool changed = var.Value() != _lastValue;
if (changed) {
_lastValue = var.Value();
}
return changed;
}
bool MacroConditionVariable::CheckCondition()
{
auto var = GetVariableByName(_variableName);
if (!var) {
return false;
}
switch (_type) {
case MacroConditionVariable::Type::EQUALS:
return Compare(*var);
case MacroConditionVariable::Type::IS_EMPTY:
return var->Value().empty();
case MacroConditionVariable::Type::IS_NUMBER:
return isNumber(*var);
case MacroConditionVariable::Type::LESS_THAN:
return compareNumber(*var, _numValue, true);
case MacroConditionVariable::Type::GREATER_THAN:
return compareNumber(*var, _numValue, false);
case MacroConditionVariable::Type::VALUE_CHANGED:
return ValueChanged(*var);
}
return false;
}
bool MacroConditionVariable::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_string(obj, "variableName", _variableName.c_str());
obs_data_set_string(obj, "strValue", _strValue.c_str());
obs_data_set_double(obj, "numValue", _numValue);
obs_data_set_int(obj, "condition", static_cast<int>(_type));
obs_data_set_bool(obj, "regex", _regex);
return true;
}
bool MacroConditionVariable::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_variableName = obs_data_get_string(obj, "variableName");
_strValue = obs_data_get_string(obj, "strValue");
_numValue = obs_data_get_double(obj, "numValue");
_type = static_cast<Type>(obs_data_get_int(obj, "condition"));
_regex = obs_data_get_bool(obj, "regex");
return true;
}
std::string MacroConditionVariable::GetShortDesc()
{
return _variableName;
}
static inline void populateConditionSelection(QComboBox *list)
{
for (auto entry : conditionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroConditionVariableEdit::MacroConditionVariableEdit(
QWidget *parent, std::shared_ptr<MacroConditionVariable> entryData)
: QWidget(parent),
_variables(new VariableSelection(this)),
_conditions(new QComboBox()),
_strValue(new QLineEdit()),
_numValue(new QDoubleSpinBox()),
_regex(new QCheckBox(
obs_module_text("AdvSceneSwitcher.condition.variable.regex")))
{
_numValue->setMinimum(-9999999999);
_numValue->setMaximum(9999999999);
populateConditionSelection(_conditions);
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
this, SLOT(VariableChanged(const QString &)));
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_strValue, SIGNAL(editingFinished()), this,
SLOT(StrValueChanged()));
QWidget::connect(_numValue, SIGNAL(valueChanged(double)), this,
SLOT(NumValueChanged(double)));
QWidget::connect(_regex, SIGNAL(stateChanged(int)), this,
SLOT(RegexChanged(int)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{variables}}", _variables}, {"{{conditions}}", _conditions},
{"{{strValue}}", _strValue}, {"{{numValue}}", _numValue},
{"{{regex}}", _regex},
};
QHBoxLayout *layout = new QHBoxLayout;
placeWidgets(
obs_module_text("AdvSceneSwitcher.condition.variable.entry"),
layout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(layout);
mainLayout->addWidget(_regex);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionVariableEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_variables->SetVariable(_entryData->_variableName);
_conditions->setCurrentIndex(static_cast<int>(_entryData->_type));
_strValue->setText(QString::fromStdString(_entryData->_strValue));
_numValue->setValue(_entryData->_numValue);
_regex->setChecked(_entryData->_regex);
SetWidgetVisibility();
}
void MacroConditionVariableEdit::VariableChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_variableName = text.toStdString();
}
void MacroConditionVariableEdit::ConditionChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_type = static_cast<MacroConditionVariable::Type>(value);
SetWidgetVisibility();
}
void MacroConditionVariableEdit::StrValueChanged()
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_strValue = _strValue->text().toStdString();
}
void MacroConditionVariableEdit::NumValueChanged(double val)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_numValue = val;
}
void MacroConditionVariableEdit::RegexChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_regex = state;
}
void MacroConditionVariableEdit::SetWidgetVisibility()
{
if (!_entryData) {
return;
}
switch (_entryData->_type) {
case MacroConditionVariable::Type::EQUALS:
_regex->show();
_strValue->show();
_numValue->hide();
break;
case MacroConditionVariable::Type::IS_EMPTY:
_regex->hide();
_strValue->hide();
_numValue->hide();
break;
case MacroConditionVariable::Type::IS_NUMBER:
_regex->hide();
_strValue->hide();
_numValue->hide();
break;
case MacroConditionVariable::Type::LESS_THAN:
_regex->hide();
_strValue->hide();
_numValue->show();
break;
case MacroConditionVariable::Type::GREATER_THAN:
_regex->hide();
_strValue->hide();
_numValue->show();
break;
case MacroConditionVariable::Type::VALUE_CHANGED:
_regex->hide();
_strValue->hide();
_numValue->hide();
break;
}
adjustSize();
updateGeometry();
}

View File

@@ -0,0 +1,88 @@
#pragma once
#include "macro.hpp"
#include "variable.hpp"
#include <QWidget>
#include <QComboBox>
#include <QLineEdit>
#include <QCheckBox>
class MacroConditionVariable : public MacroCondition {
public:
MacroConditionVariable(Macro *m) : MacroCondition(m) {}
bool CheckCondition();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
std::string GetShortDesc();
std::string GetId() { return id; };
static std::shared_ptr<MacroCondition> Create(Macro *m)
{
return std::make_shared<MacroConditionVariable>(m);
}
enum class Type {
EQUALS,
IS_EMPTY,
IS_NUMBER,
LESS_THAN,
GREATER_THAN,
VALUE_CHANGED,
//...
};
Type _type = Type::EQUALS;
std::string _variableName = "";
std::string _strValue = "";
double _numValue = 0;
bool _regex = false;
private:
bool Compare(const Variable &) const;
bool ValueChanged(const Variable &);
std::string _lastValue = "";
static bool _registered;
static const std::string id;
};
class MacroConditionVariableEdit : public QWidget {
Q_OBJECT
public:
MacroConditionVariableEdit(
QWidget *parent,
std::shared_ptr<MacroConditionVariable> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionVariableEdit(
parent,
std::dynamic_pointer_cast<MacroConditionVariable>(
cond));
}
private slots:
void VariableChanged(const QString &);
void ConditionChanged(int);
void StrValueChanged();
void NumValueChanged(double);
void RegexChanged(int state);
signals:
void HeaderInfoChanged(const QString &);
protected:
VariableSelection *_variables;
QComboBox *_conditions;
QLineEdit *_strValue;
QDoubleSpinBox *_numValue;
QCheckBox *_regex;
std::shared_ptr<MacroConditionVariable> _entryData;
private:
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -20,6 +20,7 @@ static std::map<VCamState, std::string> VCamStates = {
bool MacroConditionVCam::CheckCondition()
{
bool stateMatch = false;
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
switch (_state) {
case VCamState::STOP:
stateMatch = !obs_frontend_virtualcam_active();
@@ -30,6 +31,7 @@ bool MacroConditionVCam::CheckCondition()
default:
break;
}
#endif
return stateMatch;
}

View File

@@ -12,21 +12,50 @@ bool MacroConditionWebsocket::_registered = MacroConditionFactory::Register(
{MacroConditionWebsocket::Create, MacroConditionWebsocketEdit::Create,
"AdvSceneSwitcher.condition.websocket"});
static bool matchRegex(const std::string &msg, const std::string &expr)
static std::map<MacroConditionWebsocket::Type, std::string> conditionTypes = {
{MacroConditionWebsocket::Type::REQUEST,
"AdvSceneSwitcher.condition.websocket.type.request"},
{MacroConditionWebsocket::Type::EVENT,
"AdvSceneSwitcher.condition.websocket.type.event"},
};
static bool matchRegex(const RegexConfig &conf, const std::string &msg,
const std::string &expr)
{
try {
std::regex regExpr(expr);
return std::regex_match(msg, regExpr);
} catch (const std::regex_error &) {
auto regex = conf.GetRegularExpression(expr);
if (!regex.isValid()) {
return false;
}
return false;
auto match = regex.match(QString::fromStdString(msg));
return match.hasMatch();
}
bool MacroConditionWebsocket::CheckCondition()
{
for (const auto &msg : switcher->websocketMessages) {
if (_useRegex) {
if (matchRegex(msg, _message)) {
const std::vector<std::string> *messages = nullptr;
switch (_type) {
case MacroConditionWebsocket::Type::REQUEST:
messages = &switcher->websocketMessages;
break;
case MacroConditionWebsocket::Type::EVENT: {
auto connection = GetConnectionByName(_connection);
if (!connection) {
return false;
}
messages = &connection->Events();
break;
}
default:
break;
}
if (!messages) {
return false;
}
for (const auto &msg : *messages) {
if (_regex.Enabled()) {
if (matchRegex(_regex, msg, _message)) {
return true;
}
} else {
@@ -41,42 +70,72 @@ bool MacroConditionWebsocket::CheckCondition()
bool MacroConditionWebsocket::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_int(obj, "type", static_cast<int>(_type));
obs_data_set_string(obj, "message", _message.c_str());
obs_data_set_bool(obj, "useRegex", _useRegex);
_regex.Save(obj);
obs_data_set_string(obj, "connection", _connection.c_str());
return true;
}
bool MacroConditionWebsocket::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
_message = obs_data_get_string(obj, "message");
_useRegex = obs_data_get_bool(obj, "useRegex");
_regex.Load(obj);
// TOOD: remove in future version
if (obs_data_has_user_value(obj, "useRegex")) {
_regex.CreateBackwardsCompatibleRegex(
obs_data_get_bool(obj, "useRegex"), false);
}
_connection = obs_data_get_string(obj, "connection");
return true;
}
std::string MacroConditionWebsocket::GetShortDesc()
{
return "";
if (_type == Type::REQUEST) {
return "";
}
return _connection;
}
static inline void populateConditionSelection(QComboBox *list)
{
for (auto entry : conditionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroConditionWebsocketEdit::MacroConditionWebsocketEdit(
QWidget *parent, std::shared_ptr<MacroConditionWebsocket> entryData)
: QWidget(parent),
_conditions(new QComboBox(this)),
_message(new ResizingPlainTextEdit(this)),
_useRegex(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.websocket.useRegex")))
_regex(new RegexConfigWidget(parent)),
_connection(new ConnectionSelection(this)),
_editLayout(new QHBoxLayout())
{
populateConditionSelection(_conditions);
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_message, SIGNAL(textChanged()), this,
SLOT(MessageChanged()));
QWidget::connect(_useRegex, SIGNAL(stateChanged(int)), this,
SLOT(UseRegexChanged(int)));
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
SLOT(RegexChanged(RegexConfig)));
QWidget::connect(_connection, SIGNAL(SelectionChanged(const QString &)),
this,
SLOT(ConnectionSelectionChanged(const QString &)));
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.condition.websocket.entry")));
mainLayout->addLayout(_editLayout);
mainLayout->addWidget(_message);
mainLayout->addWidget(_useRegex);
auto regexLayout = new QHBoxLayout;
regexLayout->addWidget(_regex);
regexLayout->addStretch();
regexLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->addLayout(regexLayout);
setLayout(mainLayout);
_entryData = entryData;
@@ -84,19 +143,76 @@ MacroConditionWebsocketEdit::MacroConditionWebsocketEdit(
_loading = false;
}
void MacroConditionWebsocketEdit::SetupRequestEdit()
{
_editLayout->removeWidget(_conditions);
_editLayout->removeWidget(_connection);
clearLayout(_editLayout);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{type}}", _conditions},
{"{{connection}}", _connection},
};
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.websocket.entry.request"),
_editLayout, widgetPlaceholders);
_connection->hide();
}
void MacroConditionWebsocketEdit::SetupEventEdit()
{
_editLayout->removeWidget(_conditions);
_editLayout->removeWidget(_connection);
clearLayout(_editLayout);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{type}}", _conditions},
{"{{connection}}", _connection},
};
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.websocket.entry.event"),
_editLayout, widgetPlaceholders);
_connection->show();
}
void MacroConditionWebsocketEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_conditions->setCurrentIndex(static_cast<int>(_entryData->_type));
_message->setPlainText(QString::fromStdString(_entryData->_message));
_useRegex->setChecked(_entryData->_useRegex);
_regex->SetRegexConfig(_entryData->_regex);
_connection->SetConnection(_entryData->_connection);
if (_entryData->_type == MacroConditionWebsocket::Type::REQUEST) {
SetupRequestEdit();
} else {
SetupEventEdit();
}
adjustSize();
updateGeometry();
}
void MacroConditionWebsocketEdit::ConditionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_type = static_cast<MacroConditionWebsocket::Type>(index);
if (_entryData->_type == MacroConditionWebsocket::Type::REQUEST) {
SetupRequestEdit();
} else {
SetupEventEdit();
}
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroConditionWebsocketEdit::MessageChanged()
{
if (_loading || !_entryData) {
@@ -110,12 +226,27 @@ void MacroConditionWebsocketEdit::MessageChanged()
updateGeometry();
}
void MacroConditionWebsocketEdit::UseRegexChanged(int state)
void MacroConditionWebsocketEdit::ConnectionSelectionChanged(
const QString &connection)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_useRegex = state;
_entryData->_connection = connection.toStdString();
emit(HeaderInfoChanged(connection));
}
void MacroConditionWebsocketEdit::RegexChanged(RegexConfig conf)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_regex = conf;
adjustSize();
updateGeometry();
}

View File

@@ -1,6 +1,8 @@
#pragma once
#include "macro.hpp"
#include "connection-manager.hpp"
#include "resizing-text-edit.hpp"
#include "regex-config.hpp"
#include <QCheckBox>
@@ -17,8 +19,15 @@ public:
return std::make_shared<MacroConditionWebsocket>(m);
}
enum class Type {
REQUEST,
EVENT,
};
Type _type = Type::REQUEST;
std::string _message = obs_module_text("AdvSceneSwitcher.enterText");
bool _useRegex = false;
RegexConfig _regex;
std::string _connection;
private:
static bool _registered;
@@ -43,16 +52,24 @@ public:
}
private slots:
void ConditionChanged(int);
void MessageChanged();
void UseRegexChanged(int state);
void RegexChanged(RegexConfig);
void ConnectionSelectionChanged(const QString &);
signals:
void HeaderInfoChanged(const QString &);
protected:
ResizingPlainTextEdit *_message;
QCheckBox *_useRegex;
std::shared_ptr<MacroConditionWebsocket> _entryData;
private:
void SetupRequestEdit();
void SetupEventEdit();
QComboBox *_conditions;
ResizingPlainTextEdit *_message;
RegexConfigWidget *_regex;
ConnectionSelection *_connection;
QHBoxLayout *_editLayout;
bool _loading = true;
};

View File

@@ -13,7 +13,7 @@ bool MacroConditionWindow::_registered = MacroConditionFactory::Register(
"AdvSceneSwitcher.condition.window"});
bool MacroConditionWindow::CheckWindowTitleSwitchDirect(
std::string &currentWindowTitle)
const std::string &currentWindowTitle)
{
bool focus = (!_focus || _window == currentWindowTitle);
bool fullscreen = (!_fullscreen || isFullscreen(_window));
@@ -23,7 +23,8 @@ bool MacroConditionWindow::CheckWindowTitleSwitchDirect(
}
bool MacroConditionWindow::CheckWindowTitleSwitchRegex(
std::string &currentWindowTitle, std::vector<std::string> &windowList)
const std::string &currentWindowTitle,
const std::vector<std::string> &windowList)
{
bool match = false;
for (auto &window : windowList) {
@@ -54,7 +55,7 @@ bool foregroundWindowChanged()
bool MacroConditionWindow::CheckCondition()
{
std::string currentWindowTitle = switcher->currentTitle;
const std::string &currentWindowTitle = switcher->currentTitle;
std::vector<std::string> windowList;
GetWindowList(windowList);
@@ -101,17 +102,18 @@ std::string MacroConditionWindow::GetShortDesc()
MacroConditionWindowEdit::MacroConditionWindowEdit(
QWidget *parent, std::shared_ptr<MacroConditionWindow> entryData)
: QWidget(parent)
: QWidget(parent),
_windowSelection(new QComboBox()),
_fullscreen(new QCheckBox()),
_maximized(new QCheckBox()),
_focused(new QCheckBox()),
_windowFocusChanged(new QCheckBox()),
_focusWindow(new QLabel()),
_focusLayout(new QHBoxLayout())
{
_windowSelection = new QComboBox();
_windowSelection->setEditable(true);
_windowSelection->setMaxVisibleItems(20);
_fullscreen = new QCheckBox();
_maximized = new QCheckBox();
_focused = new QCheckBox();
_windowFocusChanged = new QCheckBox();
QWidget::connect(_windowSelection,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(WindowChanged(const QString &)));
@@ -123,6 +125,8 @@ MacroConditionWindowEdit::MacroConditionWindowEdit(
SLOT(FocusedChanged(int)));
QWidget::connect(_windowFocusChanged, SIGNAL(stateChanged(int)), this,
SLOT(WindowFocusChanged(int)));
QWidget::connect(&_timer, SIGNAL(timeout()), this,
SLOT(UpdateFocusWindow()));
populateWindowSelection(_windowSelection);
@@ -132,24 +136,31 @@ MacroConditionWindowEdit::MacroConditionWindowEdit(
{"{{maximized}}", _maximized},
{"{{focused}}", _focused},
{"{{windowFocusChanged}}", _windowFocusChanged},
{"{{focusWindow}}", _focusWindow},
};
QVBoxLayout *mainLayout = new QVBoxLayout;
QHBoxLayout *line1Layout = new QHBoxLayout;
QHBoxLayout *line2Layout = new QHBoxLayout;
auto *line1Layout = new QHBoxLayout;
placeWidgets(obs_module_text(
"AdvSceneSwitcher.condition.window.entry.line1"),
line1Layout, widgetPlaceholders);
auto *line2Layout = new QHBoxLayout;
placeWidgets(obs_module_text(
"AdvSceneSwitcher.condition.window.entry.line2"),
line2Layout, widgetPlaceholders);
placeWidgets(obs_module_text(
"AdvSceneSwitcher.condition.window.entry.line3"),
_focusLayout, widgetPlaceholders);
auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(line1Layout);
mainLayout->addLayout(line2Layout);
mainLayout->addLayout(_focusLayout);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
_timer.start(1000);
}
void MacroConditionWindowEdit::WindowChanged(const QString &text)
@@ -192,6 +203,7 @@ void MacroConditionWindowEdit::FocusedChanged(int state)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_focus = state;
SetWidgetVisibility();
}
void MacroConditionWindowEdit::WindowFocusChanged(int state)
@@ -202,6 +214,22 @@ void MacroConditionWindowEdit::WindowFocusChanged(int state)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_windowFocusChanged = state;
SetWidgetVisibility();
}
void MacroConditionWindowEdit::UpdateFocusWindow()
{
_focusWindow->setText(QString::fromStdString(switcher->currentTitle));
}
void MacroConditionWindowEdit::SetWidgetVisibility()
{
if (!_entryData) {
return;
}
setLayoutVisible(_focusLayout,
_entryData->_focus || _entryData->_windowFocusChanged);
adjustSize();
}
void MacroConditionWindowEdit::UpdateEntryData()
@@ -215,4 +243,5 @@ void MacroConditionWindowEdit::UpdateEntryData()
_maximized->setChecked(_entryData->_maximized);
_focused->setChecked(_entryData->_focus);
_windowFocusChanged->setChecked(_entryData->_windowFocusChanged);
SetWidgetVisibility();
}

View File

@@ -18,9 +18,11 @@ public:
}
private:
bool CheckWindowTitleSwitchDirect(std::string &currentWindowTitle);
bool CheckWindowTitleSwitchRegex(std::string &currentWindowTitle,
std::vector<std::string> &windowList);
bool
CheckWindowTitleSwitchDirect(const std::string &currentWindowTitle);
bool
CheckWindowTitleSwitchRegex(const std::string &currentWindowTitle,
const std::vector<std::string> &windowList);
public:
std::string _window;
@@ -56,6 +58,7 @@ private slots:
void MaximizedChanged(int state);
void FocusedChanged(int state);
void WindowFocusChanged(int state);
void UpdateFocusWindow();
signals:
void HeaderInfoChanged(const QString &);
@@ -65,8 +68,13 @@ protected:
QCheckBox *_maximized;
QCheckBox *_focused;
QCheckBox *_windowFocusChanged;
QLabel *_focusWindow;
QHBoxLayout *_focusLayout;
QTimer _timer;
std::shared_ptr<MacroConditionWindow> _entryData;
private:
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -10,6 +10,8 @@ void MacroProperties::Save(obs_data_t *obj)
obs_data_set_bool(data, "highlightExecuted", _highlightExecuted);
obs_data_set_bool(data, "highlightConditions", _highlightConditions);
obs_data_set_bool(data, "highlightActions", _highlightActions);
obs_data_set_bool(data, "newMacroRegisterHotkey",
_newMacroRegisterHotkeys);
obs_data_set_obj(obj, "macroProperties", data);
obs_data_release(data);
}
@@ -17,16 +19,11 @@ void MacroProperties::Save(obs_data_t *obj)
void MacroProperties::Load(obs_data_t *obj)
{
auto data = obs_data_get_obj(obj, "macroProperties");
// TODO: Remove in future version
if (obs_data_has_user_value(obj, "highlightExecutedMacros")) {
_highlightExecuted =
obs_data_get_bool(obj, "highlightExecutedMacros");
} else {
_highlightExecuted =
obs_data_get_bool(data, "highlightExecuted");
}
_highlightExecuted = obs_data_get_bool(data, "highlightExecuted");
_highlightConditions = obs_data_get_bool(data, "highlightConditions");
_highlightActions = obs_data_get_bool(data, "highlightActions");
_newMacroRegisterHotkeys =
obs_data_get_bool(data, "newMacroRegisterHotkey");
obs_data_release(data);
}
@@ -40,8 +37,10 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
"AdvSceneSwitcher.macroTab.highlightTrueConditions"))),
_actions(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.highlightPerformedActions"))),
_hotkeys(new QCheckBox(
obs_module_text("AdvSceneSwitcher.macroTab.disableHotkeys")))
_newMacroRegisterHotkeys(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.newMacroRegisterHotkey"))),
_currentMacroRegisterHotkeys(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.currentDisableHotkeys")))
{
setModal(true);
setWindowModality(Qt::WindowModality::WindowModal);
@@ -52,17 +51,26 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
_executed->setChecked(prop._highlightExecuted);
_conditions->setChecked(prop._highlightConditions);
_actions->setChecked(prop._highlightActions);
_newMacroRegisterHotkeys->setChecked(prop._newMacroRegisterHotkeys);
if (macro) {
_hotkeys->setChecked(macro->PauseHotkeysEnabled());
_currentMacroRegisterHotkeys->setChecked(
macro->PauseHotkeysEnabled());
} else {
_hotkeys->hide();
_currentMacroRegisterHotkeys->hide();
}
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(_executed);
layout->addWidget(_conditions);
layout->addWidget(_actions);
layout->addWidget(_hotkeys);
layout->addWidget(_newMacroRegisterHotkeys);
if (macro) {
QFrame *line = new QFrame();
line->setFrameShape(QFrame::HLine);
line->setFrameShadow(QFrame::Sunken);
layout->addWidget(line);
}
layout->addWidget(_currentMacroRegisterHotkeys);
setLayout(layout);
QDialogButtonBox *buttonbox = new QDialogButtonBox(
@@ -85,8 +93,11 @@ bool MacroPropertiesDialog::AskForSettings(QWidget *parent,
userInput._highlightExecuted = dialog._executed->isChecked();
userInput._highlightConditions = dialog._conditions->isChecked();
userInput._highlightActions = dialog._actions->isChecked();
userInput._newMacroRegisterHotkeys =
dialog._newMacroRegisterHotkeys->isChecked();
if (macro) {
macro->EnablePauseHotkeys(dialog._hotkeys->isChecked());
macro->EnablePauseHotkeys(
dialog._currentMacroRegisterHotkeys->isChecked());
}
return true;
}

View File

@@ -15,6 +15,7 @@ public:
bool _highlightExecuted = false;
bool _highlightConditions = false;
bool _highlightActions = false;
bool _newMacroRegisterHotkeys = true;
};
// Dialog for configuring global and macro specific settings
@@ -31,5 +32,7 @@ private:
QCheckBox *_executed;
QCheckBox *_conditions;
QCheckBox *_actions;
QCheckBox *_hotkeys;
QCheckBox *_newMacroRegisterHotkeys;
// Current macro specific settings
QCheckBox *_currentMacroRegisterHotkeys;
};

View File

@@ -1,5 +1,6 @@
#pragma once
#include <string>
#include <vector>
#include <QString>
#include <obs-module.h>

View File

@@ -58,8 +58,9 @@ bool AdvSceneSwitcher::addNewMacro(std::string &name, std::string format)
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->macros.emplace_back(
std::make_shared<Macro>(name, true));
switcher->macros.emplace_back(std::make_shared<Macro>(
name,
switcher->macroProperties._newMacroRegisterHotkeys));
}
return true;
}

View File

@@ -17,6 +17,7 @@ Macro::Macro(const std::string &name, const bool addHotkey)
if (addHotkey) {
SetupHotkeys();
}
_registerHotkeys = addHotkey;
}
Macro::~Macro()

View File

@@ -33,6 +33,8 @@ static std::map<VideoCondition, std::string> conditionTypes = {
"AdvSceneSwitcher.condition.video.condition.pattern"},
{VideoCondition::OBJECT,
"AdvSceneSwitcher.condition.video.condition.object"},
{VideoCondition::BRIGHTNESS,
"AdvSceneSwitcher.condition.video.condition.brightness"},
};
cv::CascadeClassifier initObjectCascade(std::string &path)
@@ -82,6 +84,10 @@ bool MacroConditionVideo::CheckCondition()
return _lastMatchResult;
}
if (_blockUntilScreenshotDone) {
GetScreenshot(true);
}
if (_screenshotData.done) {
match = Compare();
_lastMatchResult = match;
@@ -94,7 +100,7 @@ bool MacroConditionVideo::CheckCondition()
match = _lastMatchResult;
}
if (_getNextScreenshot) {
if (!_blockUntilScreenshotDone && _getNextScreenshot) {
GetScreenshot();
}
return match;
@@ -106,10 +112,13 @@ bool MacroConditionVideo::Save(obs_data_t *obj)
_video.Save(obj);
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
obs_data_set_string(obj, "filePath", _file.c_str());
obs_data_set_bool(obj, "blockUntilScreenshotDone",
_blockUntilScreenshotDone);
obs_data_set_bool(obj, "usePatternForChangedCheck",
_usePatternForChangedCheck);
obs_data_set_double(obj, "threshold", _patternThreshold);
obs_data_set_bool(obj, "useAlphaAsMask", _useAlphaAsMask);
obs_data_set_double(obj, "brightness", _brightnessThreshold);
obs_data_set_string(obj, "modelDataPath", _modelDataPath.c_str());
obs_data_set_double(obj, "scaleFactor", _scaleFactor);
obs_data_set_int(obj, "minNeighbors", _minNeighbors);
@@ -143,10 +152,13 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
_condition =
static_cast<VideoCondition>(obs_data_get_int(obj, "condition"));
_file = obs_data_get_string(obj, "filePath");
_blockUntilScreenshotDone =
obs_data_get_bool(obj, "blockUntilScreenshotDone");
_usePatternForChangedCheck =
obs_data_get_bool(obj, "usePatternForChangedCheck");
_patternThreshold = obs_data_get_double(obj, "threshold");
_useAlphaAsMask = obs_data_get_bool(obj, "useAlphaAsMask");
_brightnessThreshold = obs_data_get_double(obj, "brightness");
_modelDataPath = obs_data_get_string(obj, "modelDataPath");
_scaleFactor = obs_data_get_double(obj, "scaleFactor");
if (!isScaleFactorValid(_scaleFactor)) {
@@ -186,11 +198,12 @@ std::string MacroConditionVideo::GetShortDesc()
return _video.ToString();
}
void MacroConditionVideo::GetScreenshot()
void MacroConditionVideo::GetScreenshot(bool blocking)
{
auto source = obs_weak_source_get_source(_video.GetVideo());
_screenshotData.~ScreenshotHelper();
new (&_screenshotData) ScreenshotHelper(source);
new (&_screenshotData)
ScreenshotHelper(source, blocking, GetSwitcher()->interval);
obs_source_release(source);
_getNextScreenshot = false;
}
@@ -200,6 +213,8 @@ bool MacroConditionVideo::LoadImageFromFile()
if (!_matchImage.load(QString::fromStdString(_file))) {
blog(LOG_WARNING, "Cannot load image data from file '%s'",
_file.c_str());
(&_matchImage)->~QImage();
new (&_matchImage) QImage();
return false;
}
@@ -244,6 +259,12 @@ bool MacroConditionVideo::ScreenshotContainsObject()
return objects.size() > 0;
}
bool MacroConditionVideo::CheckBrightnessThreshold()
{
_currentBrigthness = getAvgBrightness(_screenshotData.image) / 255.;
return _currentBrigthness > _brightnessThreshold;
}
bool MacroConditionVideo::Compare()
{
if (_checkAreaEnable && _condition != VideoCondition::NO_IMAGE) {
@@ -267,6 +288,8 @@ bool MacroConditionVideo::Compare()
return ScreenshotContainsPattern();
case VideoCondition::OBJECT:
return ScreenshotContainsObject();
case VideoCondition::BRIGHTNESS:
return CheckBrightnessThreshold();
default:
break;
}
@@ -285,6 +308,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
: QWidget(parent),
_videoSelection(new VideoSelectionWidget(this)),
_condition(new QComboBox()),
_reduceLatency(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.reduceLatency"))),
_usePatternForChangedCheck(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck"))),
_imagePath(new FileSelection()),
@@ -296,6 +321,13 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
"AdvSceneSwitcher.condition.video.patternThresholdDescription"))),
_useAlphaAsMask(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask"))),
_brightnessThreshold(new ThresholdSlider(
0., 1.,
obs_module_text(
"AdvSceneSwitcher.condition.video.brightnessThreshold"),
obs_module_text(
"AdvSceneSwitcher.condition.video.brightnessThresholdDescription"))),
_currentBrightness(new QLabel),
_modelDataPath(new FileSelection()),
_modelPathLayout(new QHBoxLayout),
_objectScaleThreshold(new ThresholdSlider(
@@ -312,7 +344,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
_minSize(new SizeSelection(0, 1024)),
_maxSize(new SizeSelection(0, 4096)),
_checkAreaControlLayout(new QHBoxLayout),
_checkAreaEnable(new QCheckBox()),
_checkAreaEnable(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.entry.checkAreaEnable"))),
_checkArea(new AreaSelection(0, 99999)),
_selectArea(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.video.selectArea"))),
@@ -323,6 +356,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
"AdvSceneSwitcher.condition.video.showMatch"))),
_previewDialog(this, entryData.get(), &GetSwitcher()->m)
{
_reduceLatency->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.reduceLatency.tooltip"));
_imagePath->Button()->disconnect();
_usePatternForChangedCheck->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip"));
@@ -338,6 +373,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
SLOT(VideoSelectionChanged(const VideoSelection &)));
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_reduceLatency, SIGNAL(stateChanged(int)), this,
SLOT(ReduceLatencyChanged(int)));
QWidget::connect(_imagePath, SIGNAL(PathChanged(const QString &)), this,
SLOT(ImagePathChanged(const QString &)));
QWidget::connect(_imagePath->Button(), SIGNAL(clicked()), this,
@@ -348,6 +385,9 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
this, SLOT(PatternThresholdChanged(double)));
QWidget::connect(_useAlphaAsMask, SIGNAL(stateChanged(int)), this,
SLOT(UseAlphaAsMaskChanged(int)));
QWidget::connect(_brightnessThreshold,
SIGNAL(DoubleValueChanged(double)), this,
SLOT(BrightnessThresholdChanged(double)));
QWidget::connect(_objectScaleThreshold,
SIGNAL(DoubleValueChanged(double)), this,
SLOT(ObjectScaleThresholdChanged(double)));
@@ -380,6 +420,7 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{videoSources}}", _videoSelection},
{"{{condition}}", _condition},
{"{{reduceLatency}}", _reduceLatency},
{"{{imagePath}}", _imagePath},
{"{{minNeighbors}}", _minNeighbors},
{"{{minSize}}", _minSize},
@@ -432,19 +473,26 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
mainLayout->addWidget(_usePatternForChangedCheck);
mainLayout->addWidget(_patternThreshold);
mainLayout->addWidget(_useAlphaAsMask);
mainLayout->addWidget(_brightnessThreshold);
mainLayout->addWidget(_currentBrightness);
mainLayout->addLayout(_modelPathLayout);
mainLayout->addWidget(_objectScaleThreshold);
mainLayout->addLayout(_neighborsControlLayout);
mainLayout->addWidget(_minNeighborsDescription);
mainLayout->addLayout(_sizeLayout);
mainLayout->addLayout(showMatchLayout);
mainLayout->addLayout(_throttleControlLayout);
mainLayout->addLayout(_checkAreaControlLayout);
mainLayout->addWidget(_reduceLatency);
mainLayout->addLayout(showMatchLayout);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
connect(&_updateBrightnessTimer, &QTimer::timeout, this,
&MacroConditionVideoEdit::UpdateCurrentBrightness);
_updateBrightnessTimer.start(1000);
}
void MacroConditionVideoEdit::UpdatePreviewTooltip()
@@ -632,6 +680,16 @@ void MacroConditionVideoEdit::PatternThresholdChanged(double value)
_entryData->_patternThreshold = value;
}
void MacroConditionVideoEdit::ReduceLatencyChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
_entryData->_blockUntilScreenshotDone = value;
}
void MacroConditionVideoEdit::UseAlphaAsMaskChanged(int value)
{
if (_loading || !_entryData) {
@@ -643,6 +701,16 @@ void MacroConditionVideoEdit::UseAlphaAsMaskChanged(int value)
_entryData->LoadImageFromFile();
}
void MacroConditionVideoEdit::BrightnessThresholdChanged(double value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
_entryData->_brightnessThreshold = value;
}
void MacroConditionVideoEdit::ObjectScaleThresholdChanged(double value)
{
if (_loading || !_entryData) {
@@ -693,6 +761,9 @@ void MacroConditionVideoEdit::CheckAreaEnableChanged(int value)
_entryData->_checkAreaEnable = value;
_checkArea->setEnabled(value);
_selectArea->setEnabled(value);
_checkArea->setVisible(value);
_selectArea->setVisible(value);
adjustSize();
}
void MacroConditionVideoEdit::CheckAreaChanged(advss::Area value)
@@ -741,6 +812,14 @@ void MacroConditionVideoEdit::ShowMatchClicked()
_previewDialog.ShowMatch();
}
void MacroConditionVideoEdit::UpdateCurrentBrightness()
{
QString text = obs_module_text(
"AdvSceneSwitcher.condition.video.currentBrightness");
_currentBrightness->setText(
text.arg(_entryData->GetCurrentBrightness()));
}
void MacroConditionVideoEdit::SelectAreaClicked()
{
_previewDialog.show();
@@ -810,6 +889,10 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
_patternThreshold->setVisible(needsThreshold(_entryData->_condition));
_useAlphaAsMask->setVisible(_entryData->_condition ==
VideoCondition::PATTERN);
_brightnessThreshold->setVisible(_entryData->_condition ==
VideoCondition::BRIGHTNESS);
_currentBrightness->setVisible(_entryData->_condition ==
VideoCondition::BRIGHTNESS);
_showMatch->setVisible(needsShowMatch(_entryData->_condition));
_objectScaleThreshold->setVisible(
needsObjectControls(_entryData->_condition));
@@ -825,6 +908,8 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
needsThrottleControls(_entryData->_condition));
setLayoutVisible(_checkAreaControlLayout,
needsAreaControls(_entryData->_condition));
_checkArea->setVisible(_entryData->_checkAreaEnable);
_selectArea->setVisible(_entryData->_checkAreaEnable);
if (_entryData->_condition == VideoCondition::HAS_CHANGED ||
_entryData->_condition == VideoCondition::HAS_NOT_CHANGED) {
@@ -843,11 +928,13 @@ void MacroConditionVideoEdit::UpdateEntryData()
_videoSelection->SetVideoSelection(_entryData->_video);
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
_reduceLatency->setChecked(_entryData->_blockUntilScreenshotDone);
_imagePath->SetPath(QString::fromStdString(_entryData->_file));
_usePatternForChangedCheck->setChecked(
_entryData->_usePatternForChangedCheck);
_patternThreshold->SetDoubleValue(_entryData->_patternThreshold);
_useAlphaAsMask->setChecked(_entryData->_useAlphaAsMask);
_brightnessThreshold->SetDoubleValue(_entryData->_brightnessThreshold);
_modelDataPath->SetPath(_entryData->GetModelDataPath().c_str());
_objectScaleThreshold->SetDoubleValue(_entryData->_scaleFactor);
_minNeighbors->setValue(_entryData->_minNeighbors);

View File

@@ -25,6 +25,7 @@ enum class VideoCondition {
NO_IMAGE,
PATTERN,
OBJECT,
BRIGHTNESS,
};
class MacroConditionVideo : public MacroCondition {
@@ -40,19 +41,28 @@ public:
{
return std::make_shared<MacroConditionVideo>(m);
}
void GetScreenshot();
void GetScreenshot(bool blocking = false);
bool LoadImageFromFile();
bool LoadModelData(std::string &path);
std::string GetModelDataPath() { return _modelDataPath; }
void ResetLastMatch() { _lastMatchResult = false; }
double GetCurrentBrightness() { return _currentBrigthness; }
VideoSelection _video;
VideoCondition _condition = VideoCondition::MATCH;
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
// Enabling this will reduce matching latency, but slow down the
// the condition checks of all macros overall.
//
// If not set the screenshot will be gathered in one interval and
// checked in the next one.
// If set both operations will happen in the same interval.
bool _blockUntilScreenshotDone = false;
bool _useAlphaAsMask = false;
bool _usePatternForChangedCheck = false;
PatternMatchData _patternData;
double _patternThreshold = 0.8;
double _brightnessThreshold = 0.5;
cv::CascadeClassifier _objectCascade;
double _scaleFactor = 1.1;
int _minNeighbors = minMinNeighbors;
@@ -69,6 +79,7 @@ private:
bool OutputChanged();
bool ScreenshotContainsPattern();
bool ScreenshotContainsObject();
bool CheckBrightnessThreshold();
bool Compare();
bool CheckShouldBeSkipped();
@@ -81,6 +92,7 @@ private:
"/res/cascadeClassifiers/haarcascade_frontalface_alt.xml");
bool _lastMatchResult = false;
int _runCount = 0;
double _currentBrigthness = 0.;
static bool _registered;
static const std::string id;
@@ -107,12 +119,17 @@ public:
private slots:
void VideoSelectionChanged(const VideoSelection &);
void ConditionChanged(int cond);
void ReduceLatencyChanged(int value);
void ImagePathChanged(const QString &text);
void ImageBrowseButtonClicked();
void UsePatternForChangedCheckChanged(int value);
void PatternThresholdChanged(double);
void UseAlphaAsMaskChanged(int value);
void BrightnessThresholdChanged(double);
void ModelPathChanged(const QString &text);
void ObjectScaleThresholdChanged(double);
void MinNeighborsChanged(int value);
@@ -127,6 +144,8 @@ private slots:
void ThrottleEnableChanged(int value);
void ThrottleCountChanged(int value);
void ShowMatchClicked();
void UpdateCurrentBrightness();
signals:
void HeaderInfoChanged(const QString &);
@@ -134,11 +153,17 @@ protected:
VideoSelectionWidget *_videoSelection;
QComboBox *_condition;
QCheckBox *_reduceLatency;
QCheckBox *_usePatternForChangedCheck;
FileSelection *_imagePath;
ThresholdSlider *_patternThreshold;
QCheckBox *_useAlphaAsMask;
ThresholdSlider *_brightnessThreshold;
QLabel *_currentBrightness;
FileSelection *_modelDataPath;
QHBoxLayout *_modelPathLayout;
ThresholdSlider *_objectScaleThreshold;
@@ -164,6 +189,8 @@ protected:
std::shared_ptr<MacroConditionVideo> _entryData;
private:
QTimer _updateBrightnessTimer;
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -2,7 +2,7 @@
PatternMatchData createPatternData(QImage &pattern)
{
PatternMatchData data;
PatternMatchData data{};
if (pattern.isNull()) {
return data;
}
@@ -77,6 +77,21 @@ std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
return objects;
}
uchar getAvgBrightness(QImage &img)
{
auto i = QImageToMat(img);
cv::Mat hsvImage, rgbImage;
cv::cvtColor(i, rgbImage, cv::COLOR_RGBA2RGB);
cv::cvtColor(rgbImage, hsvImage, cv::COLOR_RGB2HSV);
long long brightnessSum = 0;
for (int i = 0; i < hsvImage.rows; ++i) {
for (int j = 0; j < hsvImage.cols; ++j) {
brightnessSum += hsvImage.at<cv::Vec3b>(i, j)[2];
}
}
return brightnessSum / (hsvImage.rows * hsvImage.cols);
}
// Assumption is that QImage uses Format_RGBA8888.
// Conversion from: https://github.com/dbzhang800/QtOpenCV
cv::Mat QImageToMat(const QImage &img)

View File

@@ -20,5 +20,6 @@ void matchPattern(QImage &img, QImage &pattern, double threshold,
std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
double scaleFactor, int minNeighbors,
cv::Size minSize, cv::Size maxSize);
uchar getAvgBrightness(QImage &img);
cv::Mat QImageToMat(const QImage &img);
QImage MatToQImage(const cv::Mat &mat);

View File

@@ -288,10 +288,38 @@ void GetProcessList(QStringList &list)
}
}
void GetForegroundProcessName(std::string &proc)
{
proc.resize(0);
@autoreleasepool {
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
NSArray *array = [ws runningApplications];
for (NSRunningApplication *app in array) {
if (!app.isActive) {
continue;
}
NSString *name = app.localizedName;
if (!name) {
break;
}
const char *str = name.UTF8String;
proc = std::string(str);
break;
}
}
}
void GetForegroundProcessName(QString &proc)
{
std::string temp;
GetForegroundProcessName(temp);
proc = QString::fromStdString(temp);
}
bool isInFocus(const QString &executable)
{
std::string current;
GetCurrentWindowTitle(current);
GetForegroundProcessName(current);
// True if executable switch equals current window
bool equals = (executable.toStdString() == current);

View File

@@ -13,6 +13,7 @@ bool isMaximized(const std::string &title);
std::pair<int, int> getCursorPos();
int secondsSinceLastInput();
void GetProcessList(QStringList &processes);
void GetForegroundProcessName(std::string &name);
bool isInFocus(const QString &executable);
void PressKeys(const std::vector<HotkeyType> keys, int duration);
void PlatformInit();

View File

@@ -72,6 +72,7 @@ typedef struct transitionData {
} transitionData;
class Item;
class SwitcherThread;
/********************************************************************************
@@ -138,13 +139,16 @@ struct SwitcherData {
Curlhelper curl;
std::deque<Connection> connections;
std::deque<std::shared_ptr<Item>> connections;
std::vector<std::string> websocketMessages;
std::deque<std::shared_ptr<Item>> variables;
std::deque<WindowSwitch> windowSwitches;
std::vector<std::string> ignoreIdleWindows;
std::string lastTitle;
std::string currentTitle;
std::string currentForegroundProcess;
std::deque<ScreenRegionSwitch> screenRegionSwitches;
std::pair<int, int> lastCursorPos = {0, 0};
@@ -291,6 +295,7 @@ struct SwitcherData {
void saveSettings(obs_data_t *obj);
void saveMacros(obs_data_t *obj);
void saveConnections(obs_data_t *obj);
void saveVariables(obs_data_t *obj);
void saveWindowTitleSwitches(obs_data_t *obj);
void saveScreenRegionSwitches(obs_data_t *obj);
void savePauseSwitches(obs_data_t *obj);
@@ -315,6 +320,7 @@ struct SwitcherData {
void loadSettings(obs_data_t *obj);
void loadMacros(obs_data_t *obj);
void loadConnections(obs_data_t *obj);
void loadVariables(obs_data_t *obj);
void loadWindowTitleSwitches(obs_data_t *obj);
void loadScreenRegionSwitches(obs_data_t *obj);
void loadPauseSwitches(obs_data_t *obj);

View File

@@ -14,9 +14,9 @@ Q_DECLARE_METATYPE(Connection *);
void SwitcherData::saveConnections(obs_data_t *obj)
{
obs_data_array_t *connectionArray = obs_data_array_create();
for (Connection &c : connections) {
for (const auto &c : connections) {
obs_data_t *array_obj = obs_data_create();
c.Save(array_obj);
c->Save(array_obj);
obs_data_array_push_back(connectionArray, array_obj);
obs_data_release(array_obj);
}
@@ -34,8 +34,9 @@ void SwitcherData::loadConnections(obs_data_t *obj)
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(connectionArray, i);
connections.emplace_back();
connections.back().Load(array_obj);
auto con = Connection::Create();
connections.emplace_back(con);
connections.back()->Load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(connectionArray);
@@ -44,7 +45,7 @@ void SwitcherData::loadConnections(obs_data_t *obj)
Connection::Connection(std::string name, std::string address, uint64_t port,
std::string pass, bool connectOnStart, bool reconnect,
int reconnectDelay)
: _name(name),
: Item(name),
_address(address),
_port(port),
_password(pass),
@@ -54,7 +55,7 @@ Connection::Connection(std::string name, std::string address, uint64_t port,
{
}
Connection::Connection(const Connection &other)
Connection::Connection(const Connection &other) : Item(other)
{
_name = other._name;
_address = other._address;
@@ -125,8 +126,8 @@ Connection *GetConnectionByName(const QString &name)
Connection *GetConnectionByName(const std::string &name)
{
for (auto &con : switcher->connections) {
if (con.GetName() == name) {
return &con;
if (con->Name() == name) {
return dynamic_cast<Connection *>(con.get());
}
}
return nullptr;
@@ -142,58 +143,38 @@ bool ConnectionNameAvailable(const std::string &name)
return ConnectionNameAvailable(QString::fromStdString(name));
}
ConnectionSelection::ConnectionSelection(QWidget *parent)
: QWidget(parent), _selection(new QComboBox), _modify(new QPushButton)
static bool AskForSettingsWrapper(QWidget *parent, Item &settings)
{
_modify->setMaximumSize(22, 22);
QIcon icon;
icon.addFile(
QString::fromUtf8(":/settings/images/settings/general.svg"),
QSize(), QIcon::Normal, QIcon::Off);
_modify->setIcon(icon);
_modify->setFlat(true);
Connection &ConnectionSettings = dynamic_cast<Connection &>(settings);
return ConnectionSettingsDialog::AskForSettings(parent,
ConnectionSettings);
}
ConnectionSelection::ConnectionSelection(QWidget *parent)
: ItemSelection(switcher->connections, Connection::Create,
AskForSettingsWrapper,
"AdvSceneSwitcher.connection.select",
"AdvSceneSwitcher.connection.add", parent)
{
// Connect to slots
QWidget::connect(_selection,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(ChangeSelection(const QString &)));
QWidget::connect(_modify, SIGNAL(clicked()), this,
SLOT(ModifyButtonClicked()));
QWidget::connect(
window(),
SIGNAL(ConnectionRenamed(const QString &, const QString &)),
this, SLOT(RenameConnection(const QString &, const QString &)));
this, SLOT(RenameItem(const QString &, const QString &)));
QWidget::connect(window(), SIGNAL(ConnectionAdded(const QString &)),
this, SLOT(AddConnection(const QString &)));
this, SLOT(AddItem(const QString &)));
QWidget::connect(window(), SIGNAL(ConnectionRemoved(const QString &)),
this, SLOT(RemoveConnection(const QString &)));
this, SLOT(RemoveItem(const QString &)));
// Forward signals
QWidget::connect(
this,
SIGNAL(ConnectionRenamed(const QString &, const QString &)),
this, SIGNAL(ItemRenamed(const QString &, const QString &)),
window(),
SIGNAL(ConnectionRenamed(const QString &, const QString &)));
QWidget::connect(this, SIGNAL(ConnectionAdded(const QString &)),
window(), SIGNAL(ConnectionAdded(const QString &)));
QWidget::connect(this, SIGNAL(ConnectionRemoved(const QString &)),
window(), SIGNAL(ConnectionRemoved(const QString &)));
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(_selection);
layout->addWidget(_modify);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
for (const auto &c : switcher->connections) {
_selection->addItem(QString::fromStdString(c._name));
}
_selection->model()->sort(0);
addSelectionEntry(
_selection,
obs_module_text("AdvSceneSwitcher.connection.select"));
_selection->insertSeparator(_selection->count());
_selection->addItem(obs_module_text("AdvSceneSwitcher.connection.add"));
QWidget::connect(this, SIGNAL(ItemAdded(const QString &)), window(),
SIGNAL(ConnectionAdded(const QString &)));
QWidget::connect(this, SIGNAL(ItemRemoved(const QString &)), window(),
SIGNAL(ConnectionRemoved(const QString &)));
}
void ConnectionSelection::SetConnection(const std::string &con)
@@ -206,191 +187,30 @@ void ConnectionSelection::SetConnection(const std::string &con)
}
}
void ConnectionSelection::ChangeSelection(const QString &sel)
{
if (sel == obs_module_text("AdvSceneSwitcher.connection.add")) {
Connection connection;
bool accepted = ConnectionSettingsDialog::AskForSettings(
this, connection);
if (!accepted) {
_selection->setCurrentIndex(0);
return;
}
switcher->connections.emplace_back(connection);
const QSignalBlocker b(_selection);
const QString name = QString::fromStdString(connection._name);
AddConnection(name);
_selection->setCurrentText(name);
emit ConnectionAdded(name);
emit SelectionChanged(name);
return;
}
auto connection = GetCurrentConnection();
if (connection) {
emit SelectionChanged(
QString::fromStdString(connection->_name));
} else {
emit SelectionChanged("");
}
}
void ConnectionSelection::ModifyButtonClicked()
{
auto connection = GetCurrentConnection();
if (!connection) {
return;
}
auto properties = [&]() {
const auto oldName = connection->_name;
bool accepted = ConnectionSettingsDialog::AskForSettings(
this, *connection);
if (!accepted) {
return;
}
if (oldName != connection->_name) {
emit ConnectionRenamed(
QString::fromStdString(oldName),
QString::fromStdString(connection->_name));
}
};
QMenu menu(this);
QAction *action = new QAction(
obs_module_text("AdvSceneSwitcher.connection.rename"), &menu);
connect(action, SIGNAL(triggered()), this, SLOT(RenameConnection()));
action->setProperty("connetion", QVariant::fromValue(connection));
menu.addAction(action);
action = new QAction(
obs_module_text("AdvSceneSwitcher.connection.remove"), &menu);
connect(action, SIGNAL(triggered()), this, SLOT(RemoveConnection()));
menu.addAction(action);
action = new QAction(
obs_module_text("AdvSceneSwitcher.connection.properties"),
&menu);
connect(action, &QAction::triggered, properties);
menu.addAction(action);
menu.exec(QCursor::pos());
}
void ConnectionSelection::RenameConnection()
{
QAction *action = reinterpret_cast<QAction *>(sender());
QVariant variant = action->property("connetion");
Connection *connection = variant.value<Connection *>();
std::string name;
bool accepted = AdvSSNameDialog::AskForName(
this, obs_module_text("AdvSceneSwitcher.windowTitle"),
obs_module_text("AdvSceneSwitcher.connection.newName"), name,
QString::fromStdString(name));
if (!accepted) {
return;
}
if (name.empty()) {
DisplayMessage("AdvSceneSwitcher.connection.emptyName");
return;
}
if (_selection->currentText().toStdString() != name &&
!ConnectionNameAvailable(name)) {
DisplayMessage("AdvSceneSwitcher.connection.nameNotAvailable");
return;
}
const auto oldName = connection->_name;
connection->_name = name;
emit ConnectionRenamed(QString::fromStdString(oldName),
QString::fromStdString(name));
}
void ConnectionSelection::RenameConnection(const QString &oldName,
const QString &name)
{
int idx = _selection->findText(oldName);
if (idx == -1) {
return;
}
_selection->setItemText(idx, name);
}
void ConnectionSelection::AddConnection(const QString &name)
{
if (_selection->findText(name) == -1) {
_selection->insertItem(1, name);
}
}
void ConnectionSelection::RemoveConnection()
{
auto connection = GetCurrentConnection();
if (!connection) {
return;
}
int idx =
_selection->findText(QString::fromStdString(connection->_name));
if (idx == -1 || idx == _selection->count()) {
return;
}
emit ConnectionRemoved(QString::fromStdString(connection->_name));
for (auto it = switcher->connections.begin();
it != switcher->connections.end(); ++it) {
if (it->_name == connection->_name) {
switcher->connections.erase(it);
break;
}
}
}
void ConnectionSelection::RemoveConnection(const QString &name)
{
const int idx = _selection->findText(name);
if (idx == _selection->currentIndex()) {
_selection->setCurrentIndex(0);
}
_selection->removeItem(idx);
}
Connection *ConnectionSelection::GetCurrentConnection()
{
return GetConnectionByName(_selection->currentText());
}
ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
const Connection &settings)
: QDialog(parent),
_name(new QLineEdit()),
_nameHint(new QLabel),
: ItemSettingsDialog(settings, switcher->connections,
"AdvSceneSwitcher.connection.select",
"AdvSceneSwitcher.connection.add", parent),
_address(new QLineEdit()),
_port(new QSpinBox()),
_password(new QLineEdit()),
_showPassword(new QPushButton()),
_connectOnStart(new QCheckBox()),
_reconnect(new QCheckBox()),
_reconnectDelay(new QSpinBox()),
_test(new QPushButton(
obs_module_text("AdvSceneSwitcher.connection.test"))),
_status(new QLabel()),
_buttonbox(new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel))
_status(new QLabel())
{
setModal(true);
setWindowModality(Qt::WindowModality::WindowModal);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setFixedWidth(555);
setMinimumHeight(100);
_port->setMaximum(65535);
_showPassword->setMaximumWidth(22);
_showPassword->setFlat(true);
_showPassword->setStyleSheet(
"QPushButton { background-color: transparent; border: 0px }");
_reconnectDelay->setMaximum(9999);
_reconnectDelay->setSuffix("s");
_buttonbox->setCenterButtons(true);
_buttonbox->button(QDialogButtonBox::Ok)->setDisabled(true);
_name->setText(QString::fromStdString(settings._name));
_address->setText(QString::fromStdString(settings._address));
_port->setValue(settings._port);
_password->setText(QString::fromStdString(settings._password));
@@ -398,18 +218,15 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
_reconnect->setChecked(settings._reconnect);
_reconnectDelay->setValue(settings._reconnectDelay);
QWidget::connect(_name, SIGNAL(textEdited(const QString &)), this,
SLOT(NameChanged(const QString &)));
QWidget::connect(_reconnect, SIGNAL(stateChanged(int)), this,
SLOT(ReconnectChanged(int)));
QWidget::connect(_showPassword, SIGNAL(pressed()), this,
SLOT(ShowPassword()));
QWidget::connect(_showPassword, SIGNAL(released()), this,
SLOT(HidePassword()));
QWidget::connect(_test, SIGNAL(clicked()), this,
SLOT(TestConnection()));
QWidget::connect(_buttonbox, &QDialogButtonBox::accepted, this,
&QDialog::accept);
QWidget::connect(_buttonbox, &QDialogButtonBox::rejected, this,
&QDialog::reject);
QGridLayout *layout = new QGridLayout;
int row = 0;
layout->addWidget(
@@ -433,7 +250,10 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.connection.password")),
row, 0);
layout->addWidget(_password, row, 1);
QHBoxLayout *passLayout = new QHBoxLayout;
passLayout->addWidget(_password);
passLayout->addWidget(_showPassword);
layout->addLayout(passLayout, row, 1);
++row;
layout->addWidget(
new QLabel(obs_module_text(
@@ -458,30 +278,8 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
layout->addWidget(_buttonbox, row, 0, 1, -1);
setLayout(layout);
NameChanged(_name->text());
ReconnectChanged(_reconnect->isChecked());
}
void ConnectionSettingsDialog::NameChanged(const QString &text)
{
if (text != _name->text() && !ConnectionNameAvailable(text)) {
SetNameWarning(obs_module_text(
"AdvSceneSwitcher.connection.nameNotAvailable"));
return;
}
if (text.isEmpty()) {
SetNameWarning(obs_module_text(
"AdvSceneSwitcher.connection.emptyName"));
return;
}
if (text == obs_module_text("AdvSceneSwitcher.connection.select") ||
text == obs_module_text("AdvSceneSwitcher.connection.add")) {
SetNameWarning(obs_module_text(
"AdvSceneSwitcher.connection.nameReserved"));
return;
}
SetNameWarning("");
HidePassword();
}
void ConnectionSettingsDialog::ReconnectChanged(int state)
@@ -513,6 +311,18 @@ void ConnectionSettingsDialog::SetStatus()
}
}
void ConnectionSettingsDialog::ShowPassword()
{
setButtonIcon(_showPassword, ":res/images/visible.svg");
_password->setEchoMode(QLineEdit::Normal);
}
void ConnectionSettingsDialog::HidePassword()
{
setButtonIcon(_showPassword, ":res/images/invisible.svg");
_password->setEchoMode(QLineEdit::PasswordEchoOnEdit);
}
void ConnectionSettingsDialog::TestConnection()
{
_testConnection.Disconnect();
@@ -545,21 +355,9 @@ bool ConnectionSettingsDialog::AskForSettings(QWidget *parent,
return true;
}
void ConnectionSettingsDialog::SetNameWarning(const QString warn)
{
if (warn.isEmpty()) {
_nameHint->hide();
_buttonbox->button(QDialogButtonBox::Ok)->setDisabled(false);
return;
}
_nameHint->setText(warn);
_nameHint->show();
_buttonbox->button(QDialogButtonBox::Ok)->setDisabled(true);
}
void Connection::Load(obs_data_t *obj)
{
_name = obs_data_get_string(obj, "name");
Item::Load(obj);
_address = obs_data_get_string(obj, "address");
_port = obs_data_get_int(obj, "port");
_password = obs_data_get_string(obj, "password");
@@ -573,9 +371,9 @@ void Connection::Load(obs_data_t *obj)
}
}
void Connection::Save(obs_data_t *obj)
void Connection::Save(obs_data_t *obj) const
{
obs_data_set_string(obj, "name", _name.c_str());
Item::Save(obj);
obs_data_set_string(obj, "address", _address.c_str());
obs_data_set_int(obj, "port", _port);
obs_data_set_string(obj, "password", _password.c_str());

View File

@@ -1,4 +1,5 @@
#pragma once
#include "item-selection-helpers.hpp"
#include <QComboBox>
#include <QPushButton>
@@ -7,7 +8,6 @@
#include <QCheckBox>
#include <QSpinBox>
#include <QLabel>
#include <QDialogButtonBox>
#include <QSpinBox>
#include <QTimer>
#include <QWidget>
@@ -18,7 +18,7 @@
class ConnectionSelection;
class ConnectionSettingsDialog;
class Connection {
class Connection : public Item {
public:
Connection(std::string name, std::string address, uint64_t port,
std::string pass, bool connectOnStart, bool reconnect,
@@ -27,15 +27,19 @@ public:
Connection(const Connection &);
Connection &operator=(const Connection &);
~Connection();
static std::shared_ptr<Item> Create()
{
return std::make_shared<Connection>();
}
void Reconnect();
void SendMsg(const std::string &msg);
void Load(obs_data_t *obj);
void Save(obs_data_t *obj);
void Save(obs_data_t *obj) const;
std::string GetName() { return _name; }
std::vector<std::string> &Events() { return _client.Events(); }
private:
std::string _name = "";
std::string _address = "localhost";
uint64_t _port = 4455;
std::string _password = "password";
@@ -53,7 +57,7 @@ private:
Connection *GetConnectionByName(const QString &);
Connection *GetConnectionByName(const std::string &);
class ConnectionSettingsDialog : public QDialog {
class ConnectionSettingsDialog : public ItemSettingsDialog {
Q_OBJECT
public:
@@ -61,54 +65,31 @@ public:
static bool AskForSettings(QWidget *parent, Connection &settings);
private slots:
void NameChanged(const QString &);
void ReconnectChanged(int);
void ShowPassword();
void HidePassword();
void SetStatus();
void TestConnection();
private:
void SetNameWarning(const QString);
QLineEdit *_name;
QLabel *_nameHint;
QLineEdit *_address;
QSpinBox *_port;
QLineEdit *_password;
QPushButton *_showPassword;
QCheckBox *_connectOnStart;
QCheckBox *_reconnect;
QSpinBox *_reconnectDelay;
QPushButton *_test;
QLabel *_status;
QDialogButtonBox *_buttonbox;
QTimer _statusTimer;
WSConnection _testConnection;
};
class ConnectionSelection : public QWidget {
class ConnectionSelection : public ItemSelection {
Q_OBJECT
public:
ConnectionSelection(QWidget *parent = 0);
void SetConnection(const std::string &);
private slots:
void ModifyButtonClicked();
void ChangeSelection(const QString &);
void RenameConnection();
void RenameConnection(const QString &oldName, const QString &name);
void AddConnection(const QString &);
void RemoveConnection();
void RemoveConnection(const QString &);
signals:
void SelectionChanged(const QString &);
void ConnectionAdded(const QString &);
void ConnectionRemoved(const QString &);
void ConnectionRenamed(const QString &oldName, const QString &name);
private:
Connection *GetCurrentConnection();
QComboBox *_selection;
QPushButton *_modify;
};

View File

@@ -3,6 +3,8 @@
#include <obs-module.h>
#include <QLayout>
#include <QFileDialog>
#include <QStandardPaths>
#include <filesystem>
FileSelection::FileSelection(FileSelection::Type type, QWidget *parent)
: QWidget(parent), _type(type)
@@ -29,12 +31,22 @@ void FileSelection::SetPath(const QString &path)
void FileSelection::BrowseButtonClicked()
{
QString defaultPath;
if (std::filesystem::exists(
std::filesystem::path(_filePath->text().toStdString()))) {
defaultPath = _filePath->text();
} else {
defaultPath = QStandardPaths::writableLocation(
QStandardPaths::DesktopLocation);
}
QString path;
if (_type == FileSelection::Type::WRITE) {
path = QFileDialog::getSaveFileName(this);
path = QFileDialog::getSaveFileName(this, "", defaultPath);
} else {
path = QFileDialog::getOpenFileName(this);
path = QFileDialog::getOpenFileName(this, "", defaultPath);
}
if (path.isEmpty()) {
return;
}

View File

@@ -0,0 +1,317 @@
#include "item-selection-helpers.hpp"
#include "utility.hpp"
#include "name-dialog.hpp"
#include <algorithm>
#include <QAction>
#include <QMenu>
#include <QLayout>
#include <obs-module.h>
Q_DECLARE_METATYPE(Item *);
Item::Item(std::string name) : _name(name) {}
static Item *GetItemByName(const std::string &name,
std::deque<std::shared_ptr<Item>> &items)
{
for (auto &item : items) {
if (item->Name() == name) {
return item.get();
}
}
return nullptr;
}
static Item *GetItemByName(const QString &name,
std::deque<std::shared_ptr<Item>> &items)
{
return GetItemByName(name.toStdString(), items);
}
bool ItemNameAvailable(const QString &name,
std::deque<std::shared_ptr<Item>> &items)
{
return !GetItemByName(name, items);
}
bool ItemNameAvailable(const std::string &name,
std::deque<std::shared_ptr<Item>> &items)
{
return ItemNameAvailable(QString::fromStdString(name), items);
}
ItemSelection::ItemSelection(std::deque<std::shared_ptr<Item>> &items,
CreateItemFunc create, SettingsCallback callback,
std::string_view select, std::string_view add,
QWidget *parent)
: QWidget(parent),
_selection(new QComboBox),
_modify(new QPushButton),
_create(create),
_askForSettings(callback),
_items(items),
_selectStr(select),
_addStr(add)
{
_modify->setMaximumWidth(22);
setButtonIcon(_modify, ":/settings/images/settings/general.svg");
_modify->setFlat(true);
// Connect to slots
QWidget::connect(_selection,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(ChangeSelection(const QString &)));
QWidget::connect(_modify, SIGNAL(clicked()), this,
SLOT(ModifyButtonClicked()));
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(_selection);
layout->addWidget(_modify);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
for (const auto &i : items) {
_selection->addItem(QString::fromStdString(i->_name));
}
_selection->model()->sort(0);
addSelectionEntry(_selection, obs_module_text(_selectStr.data()));
_selection->insertSeparator(_selection->count());
_selection->addItem(obs_module_text(_addStr.data()));
}
void ItemSelection::SetItem(const std::string &item)
{
const QSignalBlocker blocker(_selection);
if (!!GetItemByName(item, _items)) {
_selection->setCurrentText(QString::fromStdString(item));
} else {
_selection->setCurrentIndex(0);
}
}
void ItemSelection::ChangeSelection(const QString &sel)
{
if (sel == obs_module_text(_addStr.data())) {
auto item = _create();
bool accepted = _askForSettings(this, *item.get());
if (!accepted) {
_selection->setCurrentIndex(0);
return;
}
_items.emplace_back(item);
const QSignalBlocker b(_selection);
const QString name = QString::fromStdString(item->_name);
AddItem(name);
_selection->setCurrentText(name);
emit ItemAdded(name);
emit SelectionChanged(name);
return;
}
auto item = GetCurrentItem();
if (item) {
emit SelectionChanged(QString::fromStdString(item->_name));
} else {
emit SelectionChanged("");
}
}
void ItemSelection::ModifyButtonClicked()
{
auto item = GetCurrentItem();
if (!item) {
return;
}
auto properties = [&]() {
const auto oldName = item->_name;
bool accepted = _askForSettings(this, *item);
if (!accepted) {
return;
}
if (oldName != item->_name) {
emit ItemRenamed(QString::fromStdString(oldName),
QString::fromStdString(item->_name));
}
};
QMenu menu(this);
QAction *action = new QAction(
obs_module_text("AdvSceneSwitcher.item.rename"), &menu);
connect(action, SIGNAL(triggered()), this, SLOT(RenameItem()));
action->setProperty("connetion", QVariant::fromValue(item));
menu.addAction(action);
action = new QAction(obs_module_text("AdvSceneSwitcher.item.remove"),
&menu);
connect(action, SIGNAL(triggered()), this, SLOT(RemoveItem()));
menu.addAction(action);
action = new QAction(
obs_module_text("AdvSceneSwitcher.item.properties"), &menu);
connect(action, &QAction::triggered, properties);
menu.addAction(action);
menu.exec(QCursor::pos());
}
void ItemSelection::RenameItem()
{
QAction *action = reinterpret_cast<QAction *>(sender());
QVariant variant = action->property("connetion");
Item *item = variant.value<Item *>();
std::string name;
bool accepted = AdvSSNameDialog::AskForName(
this, obs_module_text("AdvSceneSwitcher.windowTitle"),
obs_module_text("AdvSceneSwitcher.item.newName"), name,
QString::fromStdString(name));
if (!accepted) {
return;
}
if (name.empty()) {
DisplayMessage("AdvSceneSwitcher.item.emptyName");
return;
}
if (_selection->currentText().toStdString() != name &&
!ItemNameAvailable(name, _items)) {
DisplayMessage("AdvSceneSwitcher.item.nameNotAvailable");
return;
}
const auto oldName = item->_name;
item->_name = name;
emit ItemRenamed(QString::fromStdString(oldName),
QString::fromStdString(name));
}
void ItemSelection::RenameItem(const QString &oldName, const QString &name)
{
int idx = _selection->findText(oldName);
if (idx == -1) {
return;
}
_selection->setItemText(idx, name);
}
void ItemSelection::AddItem(const QString &name)
{
if (_selection->findText(name) == -1) {
_selection->insertItem(1, name);
}
}
void ItemSelection::RemoveItem()
{
auto item = GetCurrentItem();
if (!item) {
return;
}
int idx = _selection->findText(QString::fromStdString(item->_name));
if (idx == -1 || idx == _selection->count()) {
return;
}
auto name = item->_name;
for (auto it = _items.begin(); it != _items.end(); ++it) {
if (it->get()->_name == item->_name) {
_items.erase(it);
break;
}
}
emit ItemRemoved(QString::fromStdString(name));
}
void ItemSelection::RemoveItem(const QString &name)
{
const int idx = _selection->findText(name);
if (idx == _selection->currentIndex()) {
_selection->setCurrentIndex(0);
}
_selection->removeItem(idx);
}
Item *ItemSelection::GetCurrentItem()
{
return GetItemByName(_selection->currentText(), _items);
}
ItemSettingsDialog::ItemSettingsDialog(const Item &settings,
std::deque<std::shared_ptr<Item>> &items,
std::string_view select,
std::string_view add, QWidget *parent)
: QDialog(parent),
_name(new QLineEdit()),
_nameHint(new QLabel),
_buttonbox(new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel)),
_items(items),
_selectStr(select),
_addStr(add)
{
setModal(true);
setWindowModality(Qt::WindowModality::WindowModal);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setFixedWidth(555);
setMinimumHeight(100);
_buttonbox->setCenterButtons(true);
_buttonbox->button(QDialogButtonBox::Ok)->setDisabled(true);
_name->setText(QString::fromStdString(settings._name));
QWidget::connect(_name, SIGNAL(textEdited(const QString &)), this,
SLOT(NameChanged(const QString &)));
QWidget::connect(_buttonbox, &QDialogButtonBox::accepted, this,
&QDialog::accept);
QWidget::connect(_buttonbox, &QDialogButtonBox::rejected, this,
&QDialog::reject);
NameChanged(_name->text());
}
void ItemSettingsDialog::NameChanged(const QString &text)
{
if (text != _name->text() && !ItemNameAvailable(text, _items)) {
SetNameWarning(obs_module_text(
"AdvSceneSwitcher.item.nameNotAvailable"));
return;
}
if (text.isEmpty()) {
SetNameWarning(
obs_module_text("AdvSceneSwitcher.item.emptyName"));
return;
}
if (text == obs_module_text(_selectStr.data()) ||
text == obs_module_text(_addStr.data())) {
SetNameWarning(
obs_module_text("AdvSceneSwitcher.item.nameReserved"));
return;
}
SetNameWarning("");
}
void ItemSettingsDialog::SetNameWarning(const QString warn)
{
if (warn.isEmpty()) {
_nameHint->hide();
_buttonbox->button(QDialogButtonBox::Ok)->setDisabled(false);
return;
}
_nameHint->setText(warn);
_nameHint->show();
_buttonbox->button(QDialogButtonBox::Ok)->setDisabled(true);
}
void Item::Load(obs_data_t *obj)
{
_name = obs_data_get_string(obj, "name");
}
void Item::Save(obs_data_t *obj) const
{
obs_data_set_string(obj, "name", _name.c_str());
}

View File

@@ -0,0 +1,101 @@
#pragma once
#include <QComboBox>
#include <QPushButton>
#include <QDialog>
#include <QLineEdit>
#include <QCheckBox>
#include <QSpinBox>
#include <QLabel>
#include <QDialogButtonBox>
#include <QSpinBox>
#include <QTimer>
#include <QWidget>
#include <deque>
#include <obs.hpp>
#include <websocket-helpers.hpp>
class ItemSelection;
class ItemSettingsDialog;
class Item {
public:
Item(std::string name);
Item() = default;
virtual ~Item() = default;
virtual void Load(obs_data_t *obj);
virtual void Save(obs_data_t *obj) const;
std::string Name() const { return _name; }
protected:
std::string _name = "";
friend ItemSelection;
friend ItemSettingsDialog;
};
class ItemSettingsDialog : public QDialog {
Q_OBJECT
public:
ItemSettingsDialog(const Item &, std::deque<std::shared_ptr<Item>> &,
std::string_view = "AdvSceneSwitcher.item.select",
std::string_view = "AdvSceneSwitcher.item.select",
QWidget *parent = 0);
virtual ~ItemSettingsDialog() = default;
private slots:
void NameChanged(const QString &);
protected:
void SetNameWarning(const QString);
QLineEdit *_name;
QLabel *_nameHint;
QDialogButtonBox *_buttonbox;
std::deque<std::shared_ptr<Item>> &_items;
std::string_view _selectStr;
std::string_view _addStr;
};
typedef bool (*SettingsCallback)(QWidget *, Item &);
typedef std::shared_ptr<Item> (*CreateItemFunc)();
class ItemSelection : public QWidget {
Q_OBJECT
public:
ItemSelection(std::deque<std::shared_ptr<Item>> &, CreateItemFunc,
SettingsCallback,
std::string_view = "AdvSceneSwitcher.item.select",
std::string_view = "AdvSceneSwitcher.item.select",
QWidget *parent = 0);
virtual ~ItemSelection() = default;
void SetItem(const std::string &);
private slots:
void ModifyButtonClicked();
void ChangeSelection(const QString &);
void RenameItem();
void RenameItem(const QString &oldName, const QString &name);
void AddItem(const QString &);
void RemoveItem();
void RemoveItem(const QString &);
signals:
void SelectionChanged(const QString &);
void ItemAdded(const QString &);
void ItemRemoved(const QString &);
void ItemRenamed(const QString &oldName, const QString &name);
protected:
Item *GetCurrentItem();
QComboBox *_selection;
QPushButton *_modify;
CreateItemFunc _create;
SettingsCallback _askForSettings;
std::deque<std::shared_ptr<Item>> &_items;
std::string_view _selectStr;
std::string_view _addStr;
};

View File

@@ -17,19 +17,19 @@ MacroList::MacroList(QWidget *parent, bool allowDuplicates, bool reorder)
_up->setVisible(reorder);
_down->setVisible(reorder);
_add->setMaximumSize(QSize(22, 22));
_add->setMaximumWidth(22);
_add->setProperty("themeID",
QVariant(QString::fromUtf8("addIconSmall")));
_add->setFlat(true);
_remove->setMaximumSize(QSize(22, 22));
_remove->setMaximumWidth(22);
_remove->setProperty("themeID",
QVariant(QString::fromUtf8("removeIconSmall")));
_remove->setFlat(true);
_up->setMaximumSize(QSize(22, 22));
_up->setMaximumWidth(22);
_up->setProperty("themeID",
QVariant(QString::fromUtf8("upArrowIconSmall")));
_up->setFlat(true);
_down->setMaximumSize(QSize(22, 22));
_down->setMaximumWidth(22);
_down->setProperty("themeID",
QVariant(QString::fromUtf8("downArrowIconSmall")));
_down->setFlat(true);

175
src/utils/regex-config.cpp Normal file
View File

@@ -0,0 +1,175 @@
#include "regex-config.hpp"
#include "utility.hpp"
#include <QLayout>
#include <obs-module.h>
void RegexConfig::Save(obs_data_t *obj)
{
auto data = obs_data_create();
obs_data_set_bool(data, "enable", _enable);
obs_data_set_bool(data, "partial", _partialMatch);
obs_data_set_int(data, "options", _options);
obs_data_set_obj(obj, "regexConfig", data);
obs_data_release(data);
}
void RegexConfig::Load(obs_data_t *obj)
{
auto data = obs_data_get_obj(obj, "regexConfig");
_enable = obs_data_get_bool(data, "enable");
_partialMatch = obs_data_get_bool(data, "partial");
_options = static_cast<QRegularExpression::PatternOption>(
obs_data_get_int(data, "options"));
obs_data_release(data);
}
void RegexConfig::CreateBackwardsCompatibleRegex(bool enable, bool setOptions)
{
_enable = enable;
if (setOptions) {
_options |= QRegularExpression::DotMatchesEverythingOption;
}
}
QRegularExpression RegexConfig::GetRegularExpression(const QString &expr) const
{
if (_partialMatch) {
return QRegularExpression(expr, _options);
}
return QRegularExpression(QRegularExpression::anchoredPattern(expr),
_options);
}
QRegularExpression
RegexConfig::GetRegularExpression(const std::string &expr) const
{
return GetRegularExpression(QString::fromStdString(expr));
}
RegexConfigWidget::RegexConfigWidget(QWidget *parent)
: QWidget(parent),
_openSettings(new QPushButton()),
_enable(new QCheckBox(
obs_module_text("AdvSceneSwitcher.regex.enable")))
{
_openSettings->setMaximumWidth(22);
setButtonIcon(_openSettings, ":/settings/images/settings/general.svg");
_openSettings->setFlat(true);
QWidget::connect(_enable, SIGNAL(stateChanged(int)), this,
SLOT(EnableChanged(int)));
QWidget::connect(_openSettings, SIGNAL(clicked()), this,
SLOT(OpenSettingsClicked()));
auto layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(_enable);
layout->addWidget(_openSettings);
setLayout(layout);
}
void RegexConfigWidget::SetRegexConfig(const RegexConfig &config)
{
_config = config;
_enable->setChecked(config._enable);
SetVisibility();
}
void RegexConfigWidget::OpenSettingsClicked()
{
bool accepted = RegexConfigDialog::AskForSettings(this, _config);
if (!accepted) {
return;
}
emit RegexConfigChanged(_config);
}
void RegexConfigWidget::SetVisibility()
{
_openSettings->setVisible(_config._enable);
adjustSize();
updateGeometry();
}
void RegexConfigWidget::EnableChanged(int value)
{
_config._enable = value;
SetVisibility();
emit RegexConfigChanged(_config);
}
RegexConfigDialog::RegexConfigDialog(QWidget *parent, const RegexConfig &config)
: QDialog(parent),
_partialMatch(new QCheckBox(
obs_module_text("AdvSceneSwitcher.regex.partialMatch"))),
_caseInsensitive(new QCheckBox(
obs_module_text("AdvSceneSwitcher.regex.caseInsensitive"))),
_dotMatch(new QCheckBox(
obs_module_text("AdvSceneSwitcher.regex.dotMatchNewline"))),
_multiLine(new QCheckBox(
obs_module_text("AdvSceneSwitcher.regex.multiLine"))),
_extendedPattern(new QCheckBox(
obs_module_text("AdvSceneSwitcher.regex.extendedPattern"))),
_buttonbox(new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel))
{
setModal(true);
setWindowModality(Qt::WindowModality::WindowModal);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
_partialMatch->setChecked(config._partialMatch);
_caseInsensitive->setChecked(config._options &
QRegularExpression::CaseInsensitiveOption);
_dotMatch->setChecked(config._options &
QRegularExpression::DotMatchesEverythingOption);
_multiLine->setChecked(config._options &
QRegularExpression::MultilineOption);
_extendedPattern->setChecked(
config._options &
QRegularExpression::ExtendedPatternSyntaxOption);
QWidget::connect(_buttonbox, &QDialogButtonBox::accepted, this,
&QDialog::accept);
QWidget::connect(_buttonbox, &QDialogButtonBox::rejected, this,
&QDialog::reject);
auto layout = new QVBoxLayout();
layout->addWidget(_partialMatch);
layout->addWidget(_caseInsensitive);
layout->addWidget(_dotMatch);
layout->addWidget(_multiLine);
layout->addWidget(_extendedPattern);
layout->addWidget(_buttonbox, Qt::AlignHCenter);
setLayout(layout);
}
void RegexConfigDialog::SetOption(RegexConfig &conf,
QRegularExpression::PatternOptions what,
QCheckBox *cb)
{
if (cb->isChecked()) {
conf._options |= what;
} else {
conf._options &= ~what;
}
}
bool RegexConfigDialog::AskForSettings(QWidget *parent, RegexConfig &settings)
{
RegexConfigDialog dialog(parent, settings);
dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
if (dialog.exec() != DialogCode::Accepted) {
return false;
}
settings._partialMatch = dialog._partialMatch->isChecked();
SetOption(settings, QRegularExpression::CaseInsensitiveOption,
dialog._caseInsensitive);
SetOption(settings, QRegularExpression::DotMatchesEverythingOption,
dialog._dotMatch);
SetOption(settings, QRegularExpression::MultilineOption,
dialog._multiLine);
SetOption(settings, QRegularExpression::ExtendedPatternSyntaxOption,
dialog._extendedPattern);
return true;
}

View File

@@ -0,0 +1,74 @@
#pragma once
#include "obs-data.h"
#include <QWidget>
#include <QCheckBox>
#include <QPushButton>
#include <QDialog>
#include <QDialogButtonBox>
#include <QRegularExpression>
class RegexConfigWidget;
class RegexConfigDialog;
class RegexConfig {
public:
void Save(obs_data_t *obj);
void Load(obs_data_t *obj);
bool Enabled() const { return _enable; }
void CreateBackwardsCompatibleRegex(bool, bool = true);
QRegularExpression::PatternOptions GetPatternOptions() const
{
return _options;
};
QRegularExpression GetRegularExpression(const QString &) const;
QRegularExpression GetRegularExpression(const std::string &) const;
private:
bool _enable = false;
bool _partialMatch = false;
QRegularExpression::PatternOptions _options =
QRegularExpression::NoPatternOption;
friend RegexConfigWidget;
friend RegexConfigDialog;
};
class RegexConfigWidget : public QWidget {
Q_OBJECT
public:
RegexConfigWidget(QWidget *parent = nullptr);
void SetRegexConfig(const RegexConfig &);
private slots:
void EnableChanged(int);
void OpenSettingsClicked();
signals:
void RegexConfigChanged(RegexConfig);
private:
void SetVisibility();
QPushButton *_openSettings;
QCheckBox *_enable;
RegexConfig _config;
};
class RegexConfigDialog : public QDialog {
Q_OBJECT
public:
RegexConfigDialog(QWidget *parent, const RegexConfig &);
static bool AskForSettings(QWidget *parent, RegexConfig &settings);
private:
static void SetOption(RegexConfig &, QRegularExpression::PatternOptions,
QCheckBox *);
QCheckBox *_partialMatch;
QCheckBox *_caseInsensitive;
QCheckBox *_dotMatch;
QCheckBox *_multiLine;
QCheckBox *_extendedPattern;
QDialogButtonBox *_buttonbox;
};

View File

@@ -2,28 +2,105 @@
#include <obs-module.h>
void SceneItemSelection::Save(obs_data_t *obj, const char *name,
const char *targetName, const char *idxName)
constexpr std::string_view typeSaveName = "type";
constexpr std::string_view itemSaveName = "item";
constexpr std::string_view idxSaveName = "idx";
constexpr std::string_view idxTypeSaveName = "idxType";
void SceneItemSelection::Save(obs_data_t *obj, const char *name) const
{
obs_data_set_int(obj, targetName, static_cast<int>(_target));
if (_target == SceneItemSelection::Target::INDIVIDUAL) {
obs_data_set_int(obj, idxName, _idx);
auto data = obs_data_create();
obs_data_set_int(data, typeSaveName.data(), static_cast<int>(_type));
obs_data_set_int(data, idxTypeSaveName.data(),
static_cast<int>(_idxType));
if (_idxType == IdxType::INDIVIDUAL) {
obs_data_set_int(data, idxSaveName.data(), _idx);
} else {
obs_data_set_int(obj, idxName, 0);
obs_data_set_int(data, idxSaveName.data(), 0);
}
obs_data_set_string(obj, name, GetWeakSourceName(_sceneItem).c_str());
if (_type == SceneItemSelection::Type::SOURCE) {
obs_data_set_string(data, itemSaveName.data(),
GetWeakSourceName(_sceneItem).c_str());
} else {
auto var = _variable.lock();
if (var) {
obs_data_set_string(data, itemSaveName.data(),
var->Name().c_str());
}
}
obs_data_set_obj(obj, name, data);
obs_data_release(data);
}
// TODO: Remove in future version
void SceneItemSelection::Load(obs_data_t *obj, const char *name,
const char *targetName, const char *idxName)
const char *typeName, const char *idxName)
{
_target = static_cast<SceneItemSelection::Target>(
obs_data_get_int(obj, targetName));
_type = Type::SOURCE;
_idxType = static_cast<IdxType>(obs_data_get_int(obj, typeName));
_idx = obs_data_get_int(obj, idxName);
auto sceneItemName = obs_data_get_string(obj, name);
_sceneItem = GetWeakSourceByName(sceneItemName);
}
void SceneItemSelection::Load(obs_data_t *obj, const char *name)
{
// TODO: Remove in future version
if (!obs_data_has_user_value(obj, name)) {
Load(obj, "sceneItem", "sceneItemTarget", "sceneItemIdx");
return;
}
auto data = obs_data_get_obj(obj, name);
_type = static_cast<Type>(obs_data_get_int(data, typeSaveName.data()));
_idxType = static_cast<IdxType>(
obs_data_get_int(data, idxTypeSaveName.data()));
_idx = obs_data_get_int(data, idxSaveName.data());
const auto itemName = obs_data_get_string(data, itemSaveName.data());
switch (_type) {
case SceneItemSelection::Type::SOURCE:
_sceneItem = GetWeakSourceByName(itemName);
break;
case SceneItemSelection::Type::VARIABLE:
_variable = GetWeakVariableByName(itemName);
break;
default:
break;
}
obs_data_release(data);
}
struct ItemInfo {
std::string name;
std::vector<obs_sceneitem_t *> items = {};
};
static bool getSceneItems(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
{
ItemInfo *moveInfo = reinterpret_cast<ItemInfo *>(ptr);
auto sourceName = obs_source_get_name(obs_sceneitem_get_source(item));
if (moveInfo->name == sourceName) {
obs_sceneitem_addref(item);
moveInfo->items.push_back(item);
}
if (obs_sceneitem_is_group(item)) {
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
obs_scene_enum_items(scene, getSceneItems, ptr);
}
return true;
}
std::vector<obs_scene_item *> getSceneItemsWithName(obs_scene_t *scene,
std::string &name)
{
ItemInfo itemInfo = {name};
obs_scene_enum_items(scene, getSceneItems, &itemInfo);
return itemInfo.items;
}
struct ItemCountData {
std::string name;
int count = 0;
@@ -44,12 +121,12 @@ static bool countSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
return true;
}
int getCountOfSceneItemOccurance(SceneSelection &s, std::string &name,
int getCountOfSceneItemOccurance(const SceneSelection &s,
const std::string &name,
bool enumAllScenes = true)
{
ItemCountData data{name};
if (enumAllScenes && (s.GetType() == SceneSelectionType::CURRENT ||
s.GetType() == SceneSelectionType::PREVIOUS)) {
if (enumAllScenes && (s.GetType() != SceneSelection::Type::SCENE)) {
auto enumScenes = [](void *param, obs_source_t *source) {
if (!source) {
return true;
@@ -72,17 +149,25 @@ int getCountOfSceneItemOccurance(SceneSelection &s, std::string &name,
std::vector<obs_scene_item *>
SceneItemSelection::GetSceneItems(SceneSelection &sceneSelection)
{
std::vector<obs_scene_item *> ret;
auto s = obs_weak_source_get_source(sceneSelection.GetScene(false));
auto scene = obs_scene_from_source(s);
auto name = GetWeakSourceName(_sceneItem);
std::string name;
if (_type == Type::VARIABLE) {
auto var = _variable.lock();
if (!var) {
return ret;
}
name = var->Value();
} else {
name = GetWeakSourceName(_sceneItem);
}
int count = getCountOfSceneItemOccurance(sceneSelection, name, false);
auto items = getSceneItemsWithName(scene, name);
obs_source_release(s);
std::vector<obs_scene_item *> ret;
if (_target == SceneItemSelection::Target::ALL ||
_target == SceneItemSelection::Target::ANY) {
if (_idxType == SceneItemSelection::IdxType::ALL ||
_idxType == SceneItemSelection::IdxType::ANY) {
ret = items;
} else {
// Index order starts at the bottom and increases to the top
@@ -103,13 +188,96 @@ SceneItemSelection::GetSceneItems(SceneSelection &sceneSelection)
std::string SceneItemSelection::ToString()
{
if (_type == Type::VARIABLE) {
auto var = _variable.lock();
if (!var) {
return "";
}
return var->Name();
}
return GetWeakSourceName(_sceneItem);
}
static bool enumSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
{
QStringList *names = reinterpret_cast<QStringList *>(ptr);
if (obs_sceneitem_is_group(item)) {
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
obs_scene_enum_items(scene, enumSceneItem, ptr);
}
auto name = obs_source_get_name(obs_sceneitem_get_source(item));
names->append(name);
return true;
}
void populateSceneItemSelection(QComboBox *list)
{
QStringList names;
obs_scene_enum_items(nullptr, enumSceneItem, &names);
names.removeDuplicates();
names.sort();
list->addItems(names);
addSelectionEntry(list, obs_module_text("AdvSceneSwitcher.selectItem"));
list->setCurrentIndex(0);
}
QStringList GetSceneItemsList(SceneSelection &s)
{
QStringList names;
if (s.GetType() != SceneSelection::Type::SCENE) {
auto enumScenes = [](void *param, obs_source_t *source) {
if (!source) {
return true;
}
QStringList *names =
reinterpret_cast<QStringList *>(param);
auto scene = obs_scene_from_source(source);
obs_scene_enum_items(scene, enumSceneItem, names);
return true;
};
obs_enum_scenes(enumScenes, &names);
} else {
auto source = obs_weak_source_get_source(s.GetScene(false));
auto scene = obs_scene_from_source(source);
obs_scene_enum_items(scene, enumSceneItem, &names);
obs_source_release(source);
}
names.removeDuplicates();
names.sort();
return names;
}
void SceneItemSelectionWidget::Reset()
{
auto previousSel = _currentSelection;
PopulateItemSelection();
SetSceneItem(previousSel);
}
void SceneItemSelectionWidget::PopulateItemSelection()
{
_sceneItems->clear();
addSelectionEntry(_sceneItems,
obs_module_text("AdvSceneSwitcher.selectItem"));
_sceneItems->insertSeparator(_sceneItems->count());
const QStringList variables = GetVariablesNameList();
addSelectionGroup(_sceneItems, variables);
_variablesEndIdx = _sceneItems->count();
const QStringList sceneItmes = GetSceneItemsList(_scene);
addSelectionGroup(_sceneItems, sceneItmes, false);
_itemsEndIdx = _sceneItems->count();
_sceneItems->setCurrentIndex(0);
}
SceneItemSelectionWidget::SceneItemSelectionWidget(QWidget *parent,
bool showAll,
AllSelectionType type)
: QWidget(parent), _showAll(showAll), _allType(type)
: QWidget(parent), _hasAllEntry(showAll), _allType(type)
{
_sceneItems = new QComboBox();
_idx = new QComboBox();
@@ -124,6 +292,16 @@ SceneItemSelectionWidget::SceneItemSelectionWidget(QWidget *parent,
SLOT(SelectionChanged(const QString &)));
QWidget::connect(_idx, SIGNAL(currentIndexChanged(int)), this,
SLOT(IdxChanged(int)));
// Variables
QWidget::connect(window(), SIGNAL(VariableAdded(const QString &)), this,
SLOT(ItemAdd(const QString &)));
QWidget::connect(window(), SIGNAL(VariableRemoved(const QString &)),
this, SLOT(ItemRemove(const QString &)));
QWidget::connect(
window(),
SIGNAL(VariableRenamed(const QString &, const QString &)), this,
SLOT(ItemRename(const QString &, const QString &)));
auto layout = new QHBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(_idx);
@@ -134,21 +312,48 @@ SceneItemSelectionWidget::SceneItemSelectionWidget(QWidget *parent,
void SceneItemSelectionWidget::SetSceneItem(const SceneItemSelection &item)
{
_sceneItems->setCurrentText(
QString::fromStdString(GetWeakSourceName(item._sceneItem)));
if (item._target == SceneItemSelection::Target::ALL) {
_allType = AllSelectionType::ALL;
_idx->setCurrentIndex(0);
} else if (item._target == SceneItemSelection::Target::ANY) {
_allType = AllSelectionType::ANY;
_idx->setCurrentIndex(0);
} else {
int itemIdx = 0;
switch (item._type) {
case SceneItemSelection::Type::SOURCE: {
int idx = item._idx;
if (_showAll) {
if (_hasAllEntry) {
idx += 1;
}
_idx->setCurrentIndex(idx);
itemIdx = findIdxInRagne(_sceneItems, _variablesEndIdx,
_itemsEndIdx,
GetWeakSourceName(item._sceneItem));
_sceneItems->setCurrentIndex(itemIdx);
break;
}
case SceneItemSelection::Type::VARIABLE: {
auto var = item._variable.lock();
if (!var) {
break;
}
itemIdx = findIdxInRagne(_sceneItems, _selectIdx,
_variablesEndIdx, var->Name());
_sceneItems->setCurrentIndex(itemIdx);
break;
}
}
switch (item._idxType) {
case SceneItemSelection::IdxType::ALL:
case SceneItemSelection::IdxType::ANY:
_allType = AllSelectionType::ALL;
_idx->setCurrentIndex(0);
break;
case SceneItemSelection::IdxType::INDIVIDUAL:
int idx = item._idx;
if (_hasAllEntry) {
idx += 1;
}
_idx->setCurrentIndex(idx);
break;
}
_currentSelection = item;
}
void SceneItemSelectionWidget::SetScene(const SceneSelection &s)
@@ -156,12 +361,12 @@ void SceneItemSelectionWidget::SetScene(const SceneSelection &s)
_scene = s;
_sceneItems->clear();
_idx->hide();
populateSceneItemSelection(_sceneItems, _scene);
PopulateItemSelection();
}
void SceneItemSelectionWidget::SetShowAll(bool value)
{
_showAll = value;
_hasAllEntry = value;
}
void SceneItemSelectionWidget::SetShowAllSelectionType(AllSelectionType t)
@@ -179,21 +384,36 @@ void SceneItemSelectionWidget::SceneChanged(const SceneSelection &s)
void SceneItemSelectionWidget::SelectionChanged(const QString &name)
{
SceneItemSelection s;
_sceneItem = GetWeakSourceByQString(name);
s._sceneItem = _sceneItem;
if (_allType == AllSelectionType::ALL) {
s._target = SceneItemSelection::Target::ALL;
} else {
s._target = SceneItemSelection::Target::ANY;
}
auto stdName = name.toStdString();
int sceneItemCount = getCountOfSceneItemOccurance(_scene, stdName);
int sceneItemCount =
getCountOfSceneItemOccurance(_scene, name.toStdString());
if (sceneItemCount > 1) {
_idx->show();
SetupIdxSelection(sceneItemCount);
} else {
_idx->hide();
}
if (_hasAllEntry) {
switch (_allType) {
case SceneItemSelectionWidget::AllSelectionType::ALL:
s._idxType = SceneItemSelection::IdxType::ALL;
break;
case SceneItemSelectionWidget::AllSelectionType::ANY:
s._idxType = SceneItemSelection::IdxType::ANY;
break;
}
}
const int idx = _sceneItems->currentIndex();
if (idx < _variablesEndIdx) {
s._type = SceneItemSelection::Type::VARIABLE;
s._variable = GetWeakVariableByQString(name);
} else if (idx < _itemsEndIdx) {
s._type = SceneItemSelection::Type::SOURCE;
s._sceneItem = GetWeakSourceByQString(name);
}
_currentSelection = s;
emit SceneItemChanged(s);
}
@@ -203,29 +423,50 @@ void SceneItemSelectionWidget::IdxChanged(int idx)
return;
}
SceneItemSelection s;
s._sceneItem = _sceneItem;
if (_showAll && idx == 0) {
if (_allType == AllSelectionType::ALL) {
s._target = SceneItemSelection::Target::ALL;
} else {
s._target = SceneItemSelection::Target::ANY;
_currentSelection._idx = idx;
if (_hasAllEntry && idx == 0) {
switch (_allType) {
case SceneItemSelectionWidget::AllSelectionType::ALL:
_currentSelection._idxType =
SceneItemSelection::IdxType::ALL;
break;
case SceneItemSelectionWidget::AllSelectionType::ANY:
_currentSelection._idxType =
SceneItemSelection::IdxType::ANY;
break;
}
s._idx = 0;
} else {
s._target = SceneItemSelection::Target::INDIVIDUAL;
if (_showAll) {
idx -= 1;
}
s._idx = idx;
}
emit SceneItemChanged(s);
if (_hasAllEntry && idx > 0) {
_currentSelection._idx -= 1;
_currentSelection._idxType =
SceneItemSelection::IdxType::INDIVIDUAL;
}
emit SceneItemChanged(_currentSelection);
}
void SceneItemSelectionWidget::ItemAdd(const QString &)
{
blockSignals(true);
Reset();
blockSignals(false);
}
void SceneItemSelectionWidget::ItemRemove(const QString &)
{
Reset();
}
void SceneItemSelectionWidget::ItemRename(const QString &, const QString &)
{
blockSignals(true);
Reset();
blockSignals(false);
}
void SceneItemSelectionWidget::SetupIdxSelection(int sceneItemCount)
{
_idx->clear();
if (_showAll) {
if (_hasAllEntry) {
if (_allType == AllSelectionType::ALL) {
_idx->addItem(obs_module_text(
"AdvSceneSwitcher.sceneItemSelection.all"));

View File

@@ -1,28 +1,41 @@
#pragma once
#include "scene-selection.hpp"
#include "variable.hpp"
#include "utility.hpp"
#include <QComboBox>
#include <obs-data.h>
#include "scene-selection.hpp"
#include "utility.hpp"
class SceneItemSelection {
public:
void Save(obs_data_t *obj, const char *name = "sceneItem",
const char *targetName = "sceneItemTarget",
const char *idxName = "sceneItemIdx");
void Load(obs_data_t *obj, const char *name = "sceneItem",
const char *targetName = "sceneItemTarget",
const char *idxName = "sceneItemIdx");
void Save(obs_data_t *obj,
const char *name = "sceneItemSelection") const;
void Load(obs_data_t *obj, const char *name = "sceneItemSelection");
// TODO: Remove in future version
void Load(obs_data_t *obj, const char *name, const char *targetName,
const char *idxName);
enum class Target { ALL, ANY, INDIVIDUAL };
Target GetType() { return _target; }
enum class Type {
SOURCE,
VARIABLE,
};
enum class IdxType {
ALL,
ANY,
INDIVIDUAL,
};
Type GetType() { return _type; }
IdxType GetIndexType() { return _idxType; }
std::vector<obs_scene_item *> GetSceneItems(SceneSelection &s);
std::string ToString();
private:
OBSWeakSource _sceneItem;
Target _target = Target::ALL;
int _idx = 0;
std::weak_ptr<Variable> _variable;
Type _type = Type::SOURCE;
IdxType _idxType = IdxType::ALL;
int _idx = 0; // Multiple items with the same name can exist
friend class SceneItemSelectionWidget;
};
@@ -30,9 +43,10 @@ class SceneItemSelectionWidget : public QWidget {
Q_OBJECT
public:
// Influences what is being displaye for the "all" entry
enum class AllSelectionType { ALL, ANY };
SceneItemSelectionWidget(
QWidget *parent, bool allSelection = true,
QWidget *parent, bool addAllSelection = true,
AllSelectionType allType = AllSelectionType::ALL);
void SetSceneItem(const SceneItemSelection &);
void SetScene(const SceneSelection &);
@@ -45,15 +59,28 @@ private slots:
void SceneChanged(const SceneSelection &);
void SelectionChanged(const QString &name);
void IdxChanged(int);
void ItemAdd(const QString &name);
void ItemRemove(const QString &name);
void ItemRename(const QString &oldName, const QString &newName);
private:
void Reset();
void PopulateItemSelection();
void SetupIdxSelection(int);
QComboBox *_sceneItems;
QComboBox *_idx;
SceneSelection _scene;
OBSWeakSource _sceneItem;
bool _showAll = false;
SceneItemSelection _currentSelection;
bool _hasAllEntry = false;
AllSelectionType _allType = AllSelectionType::ALL;
// Order of entries
// 1. "select entry" entry
// 2. Variables
// 3. Scene items
const int _selectIdx = 0;
int _variablesEndIdx = -1;
int _itemsEndIdx = -1;
};

View File

@@ -1,52 +1,95 @@
#include "scene-selection.hpp"
#include "advanced-scene-switcher.hpp"
#include "utility.hpp"
void SceneSelection::Save(obs_data_t *obj, const char *name,
const char *typeName)
constexpr std::string_view typeSaveName = "type";
constexpr std::string_view nameSaveName = "name";
constexpr std::string_view selectionSaveName = "sceneSelection";
void SceneSelection::Save(obs_data_t *obj) const
{
obs_data_set_int(obj, typeName, static_cast<int>(_type));
auto data = obs_data_create();
obs_data_set_int(data, typeSaveName.data(), static_cast<int>(_type));
switch (_type) {
case SceneSelectionType::SCENE:
obs_data_set_string(obj, name,
case Type::SCENE:
obs_data_set_string(data, nameSaveName.data(),
GetWeakSourceName(_scene).c_str());
break;
case SceneSelectionType::GROUP:
obs_data_set_string(obj, name, _group->name.c_str());
case Type::GROUP:
obs_data_set_string(data, nameSaveName.data(),
_group->name.c_str());
break;
case Type::VARIABLE: {
auto var = _variable.lock();
if (!var) {
break;
}
obs_data_set_string(data, nameSaveName.data(),
var->Name().c_str());
break;
}
default:
break;
}
obs_data_set_obj(obj, selectionSaveName.data(), data);
obs_data_release(data);
}
void SceneSelection::Load(obs_data_t *obj, const char *name,
const char *typeName)
{
_type = static_cast<SceneSelectionType>(
obs_data_get_int(obj, typeName));
auto target = obs_data_get_string(obj, name);
// TODO: Remove in future version
if (!obs_data_has_user_value(obj, selectionSaveName.data())) {
_type = static_cast<Type>(obs_data_get_int(obj, typeName));
auto targetName = obs_data_get_string(obj, name);
switch (_type) {
case Type::SCENE:
_scene = GetWeakSourceByName(targetName);
break;
case Type::GROUP:
_group = GetSceneGroupByName(targetName);
break;
case Type::PREVIOUS:
break;
case Type::CURRENT:
break;
default:
break;
}
return;
}
auto data = obs_data_get_obj(obj, selectionSaveName.data());
_type = static_cast<Type>(obs_data_get_int(data, typeSaveName.data()));
auto targetName = obs_data_get_string(data, nameSaveName.data());
switch (_type) {
case SceneSelectionType::SCENE:
_scene = GetWeakSourceByName(target);
case Type::SCENE:
_scene = GetWeakSourceByName(targetName);
break;
case SceneSelectionType::GROUP:
_group = GetSceneGroupByName(target);
case Type::GROUP:
_group = GetSceneGroupByName(targetName);
break;
case SceneSelectionType::PREVIOUS:
case Type::PREVIOUS:
break;
case SceneSelectionType::CURRENT:
case Type::CURRENT:
break;
case Type::PREVIEW:
break;
case Type::VARIABLE:
_variable = GetWeakVariableByName(targetName);
break;
default:
break;
}
obs_data_release(data);
}
OBSWeakSource SceneSelection::GetScene(bool advance)
OBSWeakSource SceneSelection::GetScene(bool advance) const
{
switch (_type) {
case SceneSelectionType::SCENE:
case Type::SCENE:
return _scene;
case SceneSelectionType::GROUP:
case Type::GROUP:
if (!_group) {
return nullptr;
}
@@ -54,199 +97,339 @@ OBSWeakSource SceneSelection::GetScene(bool advance)
return _group->getNextScene();
}
return _group->getCurrentScene();
case SceneSelectionType::PREVIOUS:
case Type::PREVIOUS:
return switcher->previousScene;
case SceneSelectionType::CURRENT:
case Type::CURRENT:
return switcher->currentScene;
case Type::PREVIEW: {
auto s = obs_frontend_get_current_preview_scene();
auto scene = obs_source_get_weak_source(s);
obs_weak_source_release(scene);
obs_source_release(s);
return scene;
}
case Type::VARIABLE: {
auto var = _variable.lock();
if (!var) {
return nullptr;
}
return GetWeakSourceByName(var->Value().c_str());
}
default:
break;
}
return nullptr;
}
std::string SceneSelection::ToString()
std::string SceneSelection::ToString() const
{
switch (_type) {
case SceneSelectionType::SCENE:
case Type::SCENE:
return GetWeakSourceName(_scene);
case SceneSelectionType::GROUP:
case Type::GROUP:
if (_group) {
return _group->name;
}
break;
case SceneSelectionType::PREVIOUS:
case Type::PREVIOUS:
return obs_module_text("AdvSceneSwitcher.selectPreviousScene");
case SceneSelectionType::CURRENT:
case Type::CURRENT:
return obs_module_text("AdvSceneSwitcher.selectCurrentScene");
case Type::PREVIEW:
return obs_module_text("AdvSceneSwitcher.selectPreviewScene");
case Type::VARIABLE: {
auto var = _variable.lock();
if (!var) {
return "";
}
return var->Name();
}
default:
break;
}
return "";
}
SceneSelectionWidget::SceneSelectionWidget(QWidget *parent, bool sceneGroups,
bool previous, bool current)
: QComboBox(parent)
SceneSelection SceneSelectionWidget::CurrentSelection()
{
SceneSelection s;
const int idx = currentIndex();
const auto name = currentText();
if (idx < _placeholderEndIdx) {
if (IsCurrentSceneSelected(name)) {
s._type = SceneSelection::Type::CURRENT;
}
if (IsPreviousSceneSelected(name)) {
s._type = SceneSelection::Type::PREVIOUS;
}
if (IsPreviewSceneSelected(name)) {
s._type = SceneSelection::Type::PREVIEW;
}
} else if (idx < _variablesEndIdx) {
s._type = SceneSelection::Type::VARIABLE;
s._variable = GetWeakVariableByQString(name);
} else if (idx < _groupsEndIdx) {
s._type = SceneSelection::Type::GROUP;
s._group = GetSceneGroupByQString(name);
} else if (idx < _scenesEndIdx) {
s._type = SceneSelection::Type::SCENE;
s._scene = GetWeakSourceByQString(name);
}
return s;
}
static QStringList getOrderList(bool current, bool previous, bool preview)
{
QStringList list;
if (current) {
list << obs_module_text("AdvSceneSwitcher.selectCurrentScene");
}
if (previous) {
list << obs_module_text("AdvSceneSwitcher.selectPreviousScene");
}
if (preview) {
list << obs_module_text("AdvSceneSwitcher.selectPreviewScene");
}
return list;
}
static QStringList getScenesList()
{
QStringList list;
char **scenes = obs_frontend_get_scene_names();
char **temp = scenes;
while (*temp) {
const char *name = *temp;
list << name;
temp++;
}
bfree(scenes);
return list;
}
static QStringList getSceneGroupsList()
{
QStringList list;
for (const auto &sg : switcher->sceneGroups) {
list << QString::fromStdString(sg.name);
}
list.sort();
return list;
}
void SceneSelectionWidget::Reset()
{
auto previousSel = _currentSelection;
PopulateSelection();
SetScene(previousSel);
}
void SceneSelectionWidget::PopulateSelection()
{
clear();
addSelectionEntry(this,
obs_module_text("AdvSceneSwitcher.selectScene"));
insertSeparator(count());
if (_current || _previous) {
const QStringList order =
getOrderList(_current, _previous, _preview);
addSelectionGroup(this, order);
}
_placeholderEndIdx = count();
if (_variables) {
const QStringList variables = GetVariablesNameList();
addSelectionGroup(this, variables);
}
_variablesEndIdx = count();
if (_sceneGroups) {
const QStringList sceneGroups = getSceneGroupsList();
addSelectionGroup(this, sceneGroups);
}
_groupsEndIdx = count();
const QStringList scenes = getScenesList();
addSelectionGroup(this, scenes);
_scenesEndIdx = count();
// Remove last separator
removeItem(count() - 1);
setCurrentIndex(0);
}
SceneSelectionWidget::SceneSelectionWidget(QWidget *parent, bool variables,
bool sceneGroups, bool previous,
bool current, bool preview)
: QComboBox(parent),
_current(current),
_previous(previous),
_preview(preview),
_variables(variables),
_sceneGroups(sceneGroups)
{
// For the rare occasion of a name conflict with current / previous
setDuplicatesEnabled(true);
populateSceneSelection(this, previous, current, false, sceneGroups,
&switcher->sceneGroups);
PopulateSelection();
QWidget::connect(this, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(SelectionChanged(const QString &)));
QWidget::connect(parent, SIGNAL(SceneGroupAdded(const QString &)), this,
SLOT(SceneGroupAdd(const QString &)));
QWidget::connect(parent, SIGNAL(SceneGroupRemoved(const QString &)),
this, SLOT(SceneGroupRemove(const QString &)));
// Scene groups
QWidget::connect(window(), SIGNAL(SceneGroupAdded(const QString &)),
this, SLOT(ItemAdd(const QString &)));
QWidget::connect(window(), SIGNAL(SceneGroupRemoved(const QString &)),
this, SLOT(ItemRemove(const QString &)));
QWidget::connect(
parent,
window(),
SIGNAL(SceneGroupRenamed(const QString &, const QString &)),
this, SLOT(SceneGroupRename(const QString &, const QString &)));
this, SLOT(ItemRename(const QString &, const QString &)));
// Variables
QWidget::connect(window(), SIGNAL(VariableAdded(const QString &)), this,
SLOT(ItemAdd(const QString &)));
QWidget::connect(window(), SIGNAL(VariableRemoved(const QString &)),
this, SLOT(ItemRemove(const QString &)));
QWidget::connect(
window(),
SIGNAL(VariableRenamed(const QString &, const QString &)), this,
SLOT(ItemRename(const QString &, const QString &)));
}
void SceneSelectionWidget::SetScene(SceneSelection &s)
void SceneSelectionWidget::SetScene(const SceneSelection &s)
{
// Order of entries
// 1. Any Scene (current not used)
// 2. Current Scene
// 3. Previous Scene
// 4. Scenes / Scene Groups
int idx;
int idx = 0;
switch (s.GetType()) {
case SceneSelectionType::SCENE:
case SceneSelectionType::GROUP:
setCurrentText(QString::fromStdString(s.ToString()));
break;
case SceneSelectionType::PREVIOUS:
idx = findText(QString::fromStdString(obs_module_text(
"AdvSceneSwitcher.selectPreviousScene")));
if (idx != -1) {
setCurrentIndex(idx);
case SceneSelection::Type::SCENE: {
if (_scenesEndIdx == -1) {
idx = 0;
break;
}
idx = findIdxInRagne(this, _groupsEndIdx, _scenesEndIdx,
s.ToString());
break;
case SceneSelectionType::CURRENT:
idx = findText(QString::fromStdString(obs_module_text(
"AdvSceneSwitcher.selectCurrentScene")));
if (idx != -1) {
setCurrentIndex(idx);
}
case SceneSelection::Type::GROUP: {
if (_groupsEndIdx == -1) {
idx = 0;
break;
}
idx = findIdxInRagne(this, _variablesEndIdx, _groupsEndIdx,
s.ToString());
break;
}
case SceneSelection::Type::PREVIOUS: {
if (_placeholderEndIdx == -1) {
idx = 0;
break;
}
idx = findIdxInRagne(
this, _selectIdx, _placeholderEndIdx,
obs_module_text(
"AdvSceneSwitcher.selectPreviousScene"));
break;
}
case SceneSelection::Type::CURRENT: {
if (_placeholderEndIdx == -1) {
idx = 0;
break;
}
idx = findIdxInRagne(
this, _selectIdx, _placeholderEndIdx,
obs_module_text("AdvSceneSwitcher.selectCurrentScene"));
break;
}
case SceneSelection::Type::PREVIEW: {
if (_placeholderEndIdx == -1) {
idx = 0;
break;
}
idx = findIdxInRagne(
this, _selectIdx, _placeholderEndIdx,
obs_module_text("AdvSceneSwitcher.selectPreviewScene"));
break;
}
case SceneSelection::Type::VARIABLE: {
if (_variablesEndIdx == -1) {
idx = 0;
break;
}
idx = findIdxInRagne(this, _placeholderEndIdx, _variablesEndIdx,
s.ToString());
break;
default:
setCurrentIndex(0);
idx = 0;
break;
}
}
static bool isFirstEntry(QComboBox *l, QString name, int idx)
{
for (auto i = l->count() - 1; i >= 0; i--) {
if (l->itemText(i) == name) {
return idx == i;
}
}
// If entry cannot be found we dont want the selection to be empty
return false;
setCurrentIndex(idx);
_currentSelection = s;
}
bool SceneSelectionWidget::IsCurrentSceneSelected(const QString &name)
{
if (name == QString::fromStdString((obs_module_text(
"AdvSceneSwitcher.selectCurrentScene")))) {
return isFirstEntry(this, name, currentIndex());
}
return false;
return name == QString::fromStdString((obs_module_text(
"AdvSceneSwitcher.selectCurrentScene")));
}
bool SceneSelectionWidget::IsPreviousSceneSelected(const QString &name)
{
if (name == QString::fromStdString((obs_module_text(
"AdvSceneSwitcher.selectPreviousScene")))) {
return isFirstEntry(this, name, currentIndex());
return name == QString::fromStdString((obs_module_text(
"AdvSceneSwitcher.selectPreviousScene")));
}
bool SceneSelectionWidget::IsPreviewSceneSelected(const QString &name)
{
return name == QString::fromStdString((obs_module_text(
"AdvSceneSwitcher.selectPreviewScene")));
}
void SceneSelectionWidget::SelectionChanged(const QString &)
{
_currentSelection = CurrentSelection();
emit SceneChanged(_currentSelection);
}
void SceneSelectionWidget::ItemAdd(const QString &)
{
blockSignals(true);
Reset();
blockSignals(false);
}
bool SceneSelectionWidget::NameUsed(const QString &name)
{
if (_currentSelection._type == SceneSelection::Type::GROUP &&
_currentSelection._group &&
_currentSelection._group->name == name.toStdString()) {
return true;
}
if (_currentSelection._type == SceneSelection::Type::VARIABLE) {
auto var = _currentSelection._variable.lock();
if (var && var->Name() == name.toStdString()) {
return true;
}
}
return false;
}
void SceneSelectionWidget::SelectionChanged(const QString &name)
void SceneSelectionWidget::ItemRemove(const QString &name)
{
SceneSelection s;
auto scene = GetWeakSourceByQString(name);
if (scene) {
s._type = SceneSelectionType::SCENE;
s._scene = scene;
if (!NameUsed(name)) {
blockSignals(true);
}
auto group = GetSceneGroupByQString(name);
if (group) {
s._type = SceneSelectionType::GROUP;
s._scene = nullptr;
s._group = group;
}
if (!scene && !group) {
if (IsCurrentSceneSelected(name)) {
s._type = SceneSelectionType::CURRENT;
}
if (IsPreviousSceneSelected(name)) {
s._type = SceneSelectionType::PREVIOUS;
}
}
emit SceneChanged(s);
Reset();
blockSignals(false);
}
void SceneSelectionWidget::SceneGroupAdd(const QString &name)
void SceneSelectionWidget::ItemRename(const QString &, const QString &)
{
addItem(name);
}
void SceneSelectionWidget::SceneGroupRemove(const QString &name)
{
int idx = findText(name);
if (idx == -1) {
return;
}
int curIdx = currentIndex();
removeItem(idx);
if (curIdx == idx) {
SceneSelection s;
emit SceneChanged(s);
}
setCurrentIndex(0);
}
static int findLastOf(QComboBox *l, QString name)
{
int idx = 0;
for (auto i = l->count() - 1; i >= 0; i--) {
if (l->itemText(i) == name) {
return idx;
}
}
return idx;
}
void SceneSelectionWidget::SceneGroupRename(const QString &oldName,
const QString &newName)
{
bool renameSelected = currentText() == oldName;
int idx = findText(oldName);
if (idx == -1) {
return;
}
removeItem(idx);
insertItem(idx, newName);
if (renameSelected) {
setCurrentIndex(findLastOf(this, newName));
}
blockSignals(true);
Reset();
blockSignals(false);
}

View File

@@ -1,31 +1,34 @@
#pragma once
#include "scene-group.hpp"
#include "variable.hpp"
#include "utility.hpp"
#include <QComboBox>
enum class SceneSelectionType {
SCENE,
GROUP,
PREVIOUS,
CURRENT,
};
class SceneSelection {
public:
void Save(obs_data_t *obj, const char *name = "scene",
const char *typeName = "sceneType");
void Save(obs_data_t *obj) const;
void Load(obs_data_t *obj, const char *name = "scene",
const char *typeName = "sceneType");
SceneSelectionType GetType() { return _type; }
OBSWeakSource GetScene(bool advance = true);
std::string ToString();
enum class Type {
SCENE,
GROUP,
PREVIOUS,
CURRENT,
PREVIEW,
VARIABLE,
};
Type GetType() const { return _type; }
OBSWeakSource GetScene(bool advance = true) const;
std::string ToString() const;
private:
OBSWeakSource _scene;
SceneGroup *_group = nullptr;
SceneSelectionType _type = SceneSelectionType::SCENE;
std::weak_ptr<Variable> _variable;
Type _type = Type::SCENE;
friend class SceneSelectionWidget;
};
@@ -33,19 +36,45 @@ class SceneSelectionWidget : public QComboBox {
Q_OBJECT
public:
SceneSelectionWidget(QWidget *parent, bool sceneGroups = false,
bool previous = false, bool current = false);
void SetScene(SceneSelection &);
SceneSelectionWidget(QWidget *parent, bool variables = false,
bool sceneGroups = false, bool previous = false,
bool current = false, bool preview = false);
void SetScene(const SceneSelection &);
signals:
void SceneChanged(const SceneSelection &);
private slots:
void SelectionChanged(const QString &name);
void SceneGroupAdd(const QString &name);
void SceneGroupRemove(const QString &name);
void SceneGroupRename(const QString &oldName, const QString &newName);
void ItemAdd(const QString &name);
void ItemRemove(const QString &name);
void ItemRename(const QString &oldName, const QString &newName);
private:
void Reset();
SceneSelection CurrentSelection();
void PopulateSelection();
bool IsCurrentSceneSelected(const QString &name);
bool IsPreviousSceneSelected(const QString &name);
bool IsPreviewSceneSelected(const QString &name);
bool NameUsed(const QString &name);
bool _current;
bool _previous;
bool _preview;
bool _variables;
bool _sceneGroups;
SceneSelection _currentSelection;
// Order of entries
// 1. "select entry" entry
// 2. Current / previous / preview scene
// 3. Variables
// 4. Scene groups
// 5. Regular scenes
const int _selectIdx = 0;
int _placeholderEndIdx = -1;
int _variablesEndIdx = -1;
int _groupsEndIdx = -1;
int _scenesEndIdx = -1;
};

View File

@@ -1,13 +1,35 @@
#include "screenshot-helper.hpp"
#include "advanced-scene-switcher.hpp"
#include <chrono>
static void ScreenshotTick(void *param, float);
ScreenshotHelper::ScreenshotHelper(obs_source_t *source)
: weakSource(OBSGetWeakRef(source))
ScreenshotHelper::ScreenshotHelper(obs_source_t *source, bool blocking,
int timeout, bool saveToFile,
std::string path)
: weakSource(OBSGetWeakRef(source)),
_blocking(blocking),
_saveToFile(saveToFile),
_path(path)
{
std::unique_lock<std::mutex> lock(_mutex);
_initDone = true;
obs_add_tick_callback(ScreenshotTick, this);
if (_blocking) {
auto res =
_cv.wait_for(lock, std::chrono::milliseconds(timeout));
if (res == std::cv_status::timeout) {
if (source) {
blog(LOG_WARNING,
"Failed to get screenshot in time for source %s",
obs_source_get_name(source));
} else {
blog(LOG_WARNING,
"Failed to get screenshot in time");
}
}
}
}
ScreenshotHelper::~ScreenshotHelper()
@@ -20,6 +42,9 @@ ScreenshotHelper::~ScreenshotHelper()
obs_remove_tick_callback(ScreenshotTick, this);
}
if (_saveThread.joinable()) {
_saveThread.join();
}
}
void ScreenshotHelper::Screenshot()
@@ -98,6 +123,26 @@ void ScreenshotHelper::MarkDone()
{
time = std::chrono::high_resolution_clock::now();
done = true;
std::unique_lock<std::mutex> lock(_mutex);
_cv.notify_all();
}
void ScreenshotHelper::WriteToFile()
{
if (!_saveToFile) {
return;
}
_saveThread = std::thread([this]() {
if (image.save(QString::fromStdString(_path))) {
vblog(LOG_INFO, "Wrote screenshot to \"%s\"",
_path.c_str());
} else {
blog(LOG_WARNING,
"Failed to save screenshot to \"%s\"!\nMaybe unknown format?",
_path.c_str());
}
});
}
#define STAGE_SCREENSHOT 0
@@ -124,6 +169,7 @@ static void ScreenshotTick(void *param, float)
break;
case STAGE_COPY_AND_SAVE:
data->Copy();
data->WriteToFile();
data->MarkDone();
obs_remove_tick_callback(ScreenshotTick, data);

Some files were not shown because too many files have changed in this diff Show More