Compare commits

..

59 Commits

Author SHA1 Message Date
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
95 changed files with 4699 additions and 1295 deletions

View File

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

View File

@@ -36,6 +36,10 @@ inputs:
description: 'Create InnoSetup installer (Windows only)' description: 'Create InnoSetup installer (Windows only)'
required: false required: false
default: 'false' default: 'false'
portable:
description: 'Create deb package / portable archive (Linux only)'
required: false
default: 'false'
workingDirectory: workingDirectory:
description: 'Working directory for packaging' description: 'Working directory for packaging'
required: false required: false
@@ -75,6 +79,10 @@ runs:
build_args+=(--debug) build_args+=(--debug)
fi fi
if [[ '${{ inputs.portable }}' == 'true' ]]; then
package_args+=(-z)
fi
${{ inputs.workingDirectory }}/.github/scripts/package-linux.sh "${package_args[@]}" ${{ inputs.workingDirectory }}/.github/scripts/package-linux.sh "${package_args[@]}"
- name: Run Windows packaging - 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-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-c | --config%b Build configuration - default: %B%F{green}RelWithDebInfo%f%b
%B-s | --codesign%b Enable codesigning (macOS only) %B-s | --codesign%b Enable codesigning (macOS only)
%B-p | --portable%b Enable portable mode (Linux only)
%B--generator%b Specify build system to generate - default: %B%F{green}Ninja%f%b %B--generator%b Specify build system to generate - default: %B%F{green}Ninja%f%b
Available generators: Available generators:
- Ninja - Ninja
@@ -130,6 +131,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
shift 2 shift 2
;; ;;
-s|--codesign) CODESIGN=1; shift ;; -s|--codesign) CODESIGN=1; shift ;;
-p|--portable) typeset -g PORTABLE=1; shift ;;
-q|--quiet) (( _verbosity -= 1 )) || true; shift ;; -q|--quiet) (( _verbosity -= 1 )) || true; shift ;;
-v|--verbose) (( _verbosity += 1 )); shift ;; -v|--verbose) (( _verbosity += 1 )); shift ;;
-h|--help) log_output ${_usage}; exit 0 ;; -h|--help) log_output ${_usage}; exit 0 ;;
@@ -245,7 +247,11 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
;; ;;
linux-*) linux-*)
if (( ${+CI} )) { if (( ${+PORTABLE} )) {
cmake_args+=(
-DLINUX_PORTABLE=ON
)
} else {
cmake_args+=( cmake_args+=(
-DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_PREFIX=/usr
-DLINUX_PORTABLE=OFF -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-c | --config%b Build configuration - default: %B%F{green}RelWithDebInfo%f%b
%B-s | --codesign%b Enable codesigning (macOS only) %B-s | --codesign%b Enable codesigning (macOS only)
%B-n | --notarize%b Enable notarization (macOS only) %B-n | --notarize%b Enable notarization (macOS only)
%B-z | --zip%b Zip only (Linux only)
%F{yellow} Output options%f %F{yellow} Output options%f
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
@@ -110,6 +111,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
;; ;;
-s|--codesign) typeset -g CODESIGN=1; shift ;; -s|--codesign) typeset -g CODESIGN=1; shift ;;
-n|--notarize) typeset -g NOTARIZE=1; 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 ;; -q|--quiet) (( _verbosity -= 1 )) || true; shift ;;
-v|--verbose) (( _verbosity += 1 )); shift ;; -v|--verbose) (( _verbosity += 1 )); shift ;;
-h|--help) log_output ${_usage}; exit 0 ;; -h|--help) log_output ${_usage}; exit 0 ;;
@@ -132,7 +134,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
if [[ ${host_os} == 'macos' ]] { if [[ ${host_os} == 'macos' ]] {
autoload -Uz check_packages read_codesign read_codesign_installer read_codesign_pass 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 ]] { if [[ ! -d ${project_root}/release/${product_name}.plugin ]] {
log_error 'No release artifact found. Run the build script or the CMake install procedure first.' 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 popd
} elif [[ ${host_os} == 'linux' ]] { } elif [[ ${host_os} == 'linux' ]] {
local -a cmake_args=() if (( ${+ZIP} )) {
if (( _loglevel > 1 )) cmake_args+=(--verbose) 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} pushd ${project_root}
cmake --build build_${target##*-} --config ${BUILD_CONFIG:-RelWithDebInfo} -t package ${cmake_args} cmake --build build_${target##*-} --config ${BUILD_CONFIG:-RelWithDebInfo} -t package ${cmake_args}
popd popd
}
} }
} }

View File

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

@@ -169,7 +169,7 @@ jobs:
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: ${{ env.PLUGIN_NAME }}-macos-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }} 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: linux_build:
name: 02 - Linux name: 02 - Linux
@@ -178,6 +178,7 @@ jobs:
fail-fast: true fail-fast: true
matrix: matrix:
arch: [x86_64] arch: [x86_64]
portable: [true, false]
if: always() if: always()
needs: [clang_check] needs: [clang_check]
outputs: outputs:
@@ -235,13 +236,7 @@ jobs:
workingDirectory: ${{ github.workspace }}/plugin workingDirectory: ${{ github.workspace }}/plugin
target: ${{ matrix.arch }} target: ${{ matrix.arch }}
config: RelWithDebInfo config: RelWithDebInfo
portable: ${{ matrix.portable }}
- 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/*
- name: Package Plugin - name: Package Plugin
uses: ./plugin/.github/actions/package-plugin uses: ./plugin/.github/actions/package-plugin
@@ -249,13 +244,21 @@ jobs:
workingDirectory: ${{ github.workspace }}/plugin workingDirectory: ${{ github.workspace }}/plugin
target: ${{ matrix.arch }} target: ${{ matrix.arch }}
config: RelWithDebInfo config: RelWithDebInfo
portable: ${{ matrix.portable }}
- name: Upload Package Artifact - 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 uses: actions/upload-artifact@v3
with: with:
name: ${{ env.PLUGIN_NAME }}-linux-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }} 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: windows_build:
name: 02 - Windows name: 02 - Windows
@@ -263,7 +266,7 @@ jobs:
strategy: strategy:
fail-fast: true fail-fast: true
matrix: matrix:
arch: [x86, x64] arch: [x64]
if: always() if: always()
needs: [clang_check] needs: [clang_check]
outputs: outputs:

3
.gitmodules vendored
View File

@@ -10,6 +10,3 @@
[submodule "deps/openvr"] [submodule "deps/openvr"]
path = deps/openvr path = deps/openvr
url = https://github.com/ValveSoftware/openvr.git 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. 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) ## Compiling in tree (recommended for development)
Add the "SceneSwitcher" source directory to your obs-studio source directory under obs-studio/UI/frontend-plugins/: Add the "SceneSwitcher" source directory to your obs-studio source directory under obs-studio/UI/frontend-plugins/:
``` ```
@@ -25,6 +27,7 @@ Note that on Linux systems it might be necessary to additionally install the fol
sudo apt-get install \ sudo apt-get install \
libxtst-dev \ libxtst-dev \
libxss-dev \ libxss-dev \
libprocps-dev \
libopencv-dev libopencv-dev
``` ```

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) project(advanced-scene-switcher VERSION 1.0.0)
set(LIB_NAME "${PROJECT_NAME}-lib") set(LIB_NAME "${PROJECT_NAME}-lib")
@@ -147,6 +147,8 @@ target_sources(
src/macro-core/macro-action-timer.hpp src/macro-core/macro-action-timer.hpp
src/macro-core/macro-action-transition.cpp src/macro-core/macro-action-transition.cpp
src/macro-core/macro-action-transition.hpp 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.cpp
src/macro-core/macro-action-virtual-cam.hpp src/macro-core/macro-action-virtual-cam.hpp
src/macro-core/macro-action-wait.cpp 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-timer.hpp
src/macro-core/macro-condition-transition.cpp src/macro-core/macro-condition-transition.cpp
src/macro-core/macro-condition-transition.hpp 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.cpp
src/macro-core/macro-condition-virtual-cam.hpp src/macro-core/macro-condition-virtual-cam.hpp
src/macro-core/macro-condition-websocket.cpp src/macro-core/macro-condition-websocket.cpp
@@ -238,12 +242,16 @@ target_sources(
src/utils/curl-helper.hpp src/utils/curl-helper.hpp
src/utils/duration-control.cpp src/utils/duration-control.cpp
src/utils/duration-control.hpp src/utils/duration-control.hpp
src/utils/item-selection-helpers.cpp
src/utils/item-selection-helpers.hpp
src/utils/file-selection.cpp src/utils/file-selection.cpp
src/utils/file-selection.hpp src/utils/file-selection.hpp
src/utils/macro-list.cpp src/utils/macro-list.cpp
src/utils/macro-list.hpp src/utils/macro-list.hpp
src/utils/name-dialog.cpp src/utils/name-dialog.cpp
src/utils/name-dialog.hpp 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.cpp
src/utils/resizing-text-edit.hpp src/utils/resizing-text-edit.hpp
src/utils/scene-item-selection.cpp src/utils/scene-item-selection.cpp
@@ -258,29 +266,27 @@ target_sources(
src/utils/transition-selection.hpp src/utils/transition-selection.hpp
src/utils/utility.cpp src/utils/utility.cpp
src/utils/utility.hpp src/utils/utility.hpp
src/utils/variable.cpp
src/utils/variable.hpp
src/utils/volume-control.cpp src/utils/volume-control.cpp
src/utils/volume-control.hpp
src/utils/websocket-helpers.cpp src/utils/websocket-helpers.cpp
src/utils/websocket-helpers.hpp src/utils/websocket-helpers.hpp)
src/utils/volume-control.hpp)
# --- End of section --- # --- End of section ---
target_link_libraries(${PROJECT_NAME} PUBLIC ${LIB_NAME})
if(BUILD_OUT_OF_TREE) if(BUILD_OUT_OF_TREE)
find_package(libobs REQUIRED)
find_package(obs-frontend-api REQUIRED)
include(cmake/ObsPluginHelpers.cmake) 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() 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) find_qt(COMPONENTS Widgets Core)
target_link_libraries(${LIB_NAME} PUBLIC Qt::Core Qt::Widgets) target_link_libraries(${LIB_NAME} PUBLIC Qt::Core Qt::Widgets)
include(cmake/AdvSSHelpers.cmake)
# --- Platform-independent build settings --- # --- Platform-independent build settings ---
target_include_directories( target_include_directories(
@@ -357,9 +363,10 @@ elseif(OS_MACOS)
# --- Linux-specific build settings and tasks --- # --- Linux-specific build settings and tasks ---
else() else()
set_target_properties(${PROJECT_NAME} PROPERTIES INSTALL_RPATH "$ORIGIN")
target_compile_options(${LIB_NAME} PRIVATE -Wall -Wextra) target_compile_options(${LIB_NAME} PRIVATE -Wall -Wextra)
set_target_properties(${LIB_NAME} PROPERTIES PREFIX "") set_target_properties(${LIB_NAME} PROPERTIES PREFIX "")
set_target_properties(${LIB_NAME} PROPERTIES SOVERSION 1)
find_package(X11 REQUIRED COMPONENTS Xtst Xss) find_package(X11 REQUIRED COMPONENTS Xtst Xss)
find_path(PROCPS_INCLUDE_DIR NAMES proc/procps.h) find_path(PROCPS_INCLUDE_DIR NAMES proc/procps.h)
@@ -392,31 +399,26 @@ endif()
# --- End of section --- # --- End of section ---
target_link_libraries(${LIB_NAME} PUBLIC ${${PROJECT_NAME}_PLATFORM_LIBS}) 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) 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") file(GLOB ASS_TRANSLATION_FILES "data/locale/*.ini")
if(NOT LIB_OUT_DIR)
# Linux set(LIB_OUT_DIR "/lib/obs-plugins")
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})
endif() 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() endif()
# --- End of section --- # --- 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 \ sudo apt-get install \
libxtst-dev \ libxtst-dev \
libxss-dev \ libxss-dev \
libprocps-dev \
libopencv-dev libopencv-dev
``` ```

View File

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

View File

@@ -67,18 +67,27 @@ if(OS_MACOS)
else() else()
# --- Windows / Linux section --- # --- Windows / Linux section ---
function(plugin_install_helper what where where_deb) function(plugin_install_helper what where where_deb)
install( if(DEB_INSTALL)
TARGETS ${what} if(NOT LIB_OUT_DIR)
RUNTIME DESTINATION "${where}" COMPONENT ${what}_Runtime set(LIB_OUT_DIR "/lib/obs-plugins")
LIBRARY DESTINATION "${where}" endif()
COMPONENT ${what}_Runtime install(
NAMELINK_COMPONENT ${what}_Development) TARGETS ${what}
install( LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${LIB_OUT_DIR}/${where_deb})
FILES $<TARGET_FILE:${what}> else()
DESTINATION $<CONFIG>/${where} install(
COMPONENT ${what}_rundir TARGETS ${what}
EXCLUDE_FROM_ALL) 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) if(OS_WINDOWS)
install( install(
FILES $<TARGET_PDB_FILE:${what}> FILES $<TARGET_PDB_FILE:${what}>
@@ -93,7 +102,6 @@ else()
COMPONENT ${what}_rundir COMPONENT ${what}_rundir
OPTIONAL EXCLUDE_FROM_ALL) OPTIONAL EXCLUDE_FROM_ALL)
endif() endif()
add_custom_command( add_custom_command(
TARGET ${what} TARGET ${what}
POST_BUILD POST_BUILD
@@ -104,25 +112,23 @@ else()
${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake
COMMENT "Installing ${what} to plugin rundir ${OBS_OUTPUT_DIR}/${where}\n" COMMENT "Installing ${what} to plugin rundir ${OBS_OUTPUT_DIR}/${where}\n"
VERBATIM) 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() endfunction()
function(install_advss_lib target) function(install_advss_lib target)
plugin_install_helper("${target}" "${OBS_PLUGIN_DESTINATION}" "") plugin_install_helper("${target}" "${OBS_PLUGIN_DESTINATION}" "")
if(OS_POSIX)
set_target_properties(${target} PROPERTIES INSTALL_RPATH "$ORIGIN")
endif()
endfunction() endfunction()
function(install_advss_plugin target) function(install_advss_plugin target)
plugin_install_helper( plugin_install_helper(
"${target}" "${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}" "${target}" "${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
"${_PLUGIN_FOLDER}") "${_PLUGIN_FOLDER}")
if(OS_POSIX)
set_target_properties(${target} PROPERTIES INSTALL_RPATH
"$ORIGIN:$ORIGIN/..")
endif()
message(STATUS "ADVSS: ENABLED PLUGIN ${target}") message(STATUS "ADVSS: ENABLED PLUGIN ${target}")
endfunction() endfunction()
@@ -205,14 +211,50 @@ else()
endif() endif()
# --- End of section --- # --- End of section ---
function(setup_obs_lib_dependency target)
function(setup_advss_plugin target)
if(BUILD_OUT_OF_TREE) 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() else()
target_link_libraries(${target} PUBLIC OBS::libobs OBS::frontend-api) target_link_libraries(${target} PUBLIC OBS::libobs OBS::frontend-api)
endif() endif()
endfunction()
function(setup_advss_plugin target)
setup_obs_lib_dependency(${target})
find_qt(COMPONENTS Widgets Core) find_qt(COMPONENTS Widgets Core)
target_link_libraries(${target} PRIVATE Qt::Core Qt::Widgets) 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_PACKAGE_NAME "${CMAKE_PROJECT_NAME}")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${LINUX_MAINTAINER_EMAIL}") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${LINUX_MAINTAINER_EMAIL}")
set(CPACK_PACKAGE_VERSION "${CMAKE_PROJECT_VERSION}") set(CPACK_PACKAGE_VERSION "${CMAKE_PROJECT_VERSION}")
set(CPACK_PACKAGE_FILE_NAME set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-linux-x86_64")
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-linux-x86_64")
set(CPACK_GENERATOR "DEB") set(CPACK_GENERATOR "DEB")
set(CPACK_DEBIAN_PACKAGE_DEPENDS if(${QT_VERSION} STREQUAL "6")
"obs-studio (>= 27.0.0), libqt5core5a (>= 5.9.0~beta), libqt5gui5 (>= 5.3.0), libqt5widgets5 (>= 5.7.0)" 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) set(CPACK_OUTPUT_FILE_PREFIX ${CMAKE_SOURCE_DIR}/release)

View File

@@ -83,7 +83,8 @@ AdvSceneSwitcher.macroTab.minimize="Minimize"
AdvSceneSwitcher.macroTab.highlightExecutedMacros="Highlight recently executed macros" AdvSceneSwitcher.macroTab.highlightExecutedMacros="Highlight recently executed macros"
AdvSceneSwitcher.macroTab.highlightTrueConditions="Highlight conditions of currently selected macro that evaluated to true recently" AdvSceneSwitcher.macroTab.highlightTrueConditions="Highlight conditions of currently selected macro that evaluated to true recently"
AdvSceneSwitcher.macroTab.highlightPerformedActions="Highlight recently performed actions of currently selected macro" AdvSceneSwitcher.macroTab.highlightPerformedActions="Highlight recently performed actions of currently selected macro"
AdvSceneSwitcher.macroTab.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 ; Macro List
AdvSceneSwitcher.macroList.deleted="deleted" AdvSceneSwitcher.macroList.deleted="deleted"
@@ -128,7 +129,10 @@ AdvSceneSwitcher.condition.window="Window"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} exist and ..." 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.line2="... is {{fullscreen}} fullscreen {{maximized}} maximized {{focused}} focused {{windowFocusChanged}} foreground window changed"
AdvSceneSwitcher.condition.file="File" 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.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}" AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.condition.media="Media" AdvSceneSwitcher.condition.media="Media"
@@ -148,6 +152,8 @@ AdvSceneSwitcher.condition.video.condition.object="contains object"
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="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.file="Use existing file"
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot" 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="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.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.patternThreshold="Threshold: "
@@ -169,7 +175,8 @@ AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePa
AdvSceneSwitcher.condition.video.entry.modelPath="Model data (haar cascade classifier): {{modelDataPath}}" AdvSceneSwitcher.condition.video.entry.modelPath="Model data (haar cascade classifier): {{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum neighbors: {{minNeighbors}}" AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum neighbors: {{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU load by performing check only every {{throttleCount}} milliseconds" AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU load by performing check only every {{throttleCount}} milliseconds"
AdvSceneSwitcher.condition.video.entry.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.minSize="Minimum size:"
AdvSceneSwitcher.condition.video.maxSize="Maximum size:" AdvSceneSwitcher.condition.video.maxSize="Maximum size:"
AdvSceneSwitcher.condition.video.selectArea="Select area" AdvSceneSwitcher.condition.video.selectArea="Select area"
@@ -227,7 +234,6 @@ AdvSceneSwitcher.condition.source="Source"
AdvSceneSwitcher.condition.source.type.active="Is active" AdvSceneSwitcher.condition.source.type.active="Is active"
AdvSceneSwitcher.condition.source.type.showing="Is showing" AdvSceneSwitcher.condition.source.type.showing="Is showing"
AdvSceneSwitcher.condition.source.type.settings="Settings match" AdvSceneSwitcher.condition.source.type.settings="Settings match"
AdvSceneSwitcher.condition.source.regex="Use regular expressions"
AdvSceneSwitcher.condition.source.getSettings="Get current settings" AdvSceneSwitcher.condition.source.getSettings="Get current settings"
AdvSceneSwitcher.condition.source.entry.line1="{{sources}} {{conditions}}" AdvSceneSwitcher.condition.source.entry.line1="{{sources}} {{conditions}}"
AdvSceneSwitcher.condition.source.entry.line2="{{settings}}" AdvSceneSwitcher.condition.source.entry.line2="{{settings}}"
@@ -240,7 +246,6 @@ AdvSceneSwitcher.condition.filter="Filter"
AdvSceneSwitcher.condition.filter.type.active="Is enabled" AdvSceneSwitcher.condition.filter.type.active="Is enabled"
AdvSceneSwitcher.condition.filter.type.showing="Is disabled" AdvSceneSwitcher.condition.filter.type.showing="Is disabled"
AdvSceneSwitcher.condition.filter.type.settings="Settings match" AdvSceneSwitcher.condition.filter.type.settings="Settings match"
AdvSceneSwitcher.condition.filter.regex="Use regular expressions"
AdvSceneSwitcher.condition.filter.getSettings="Get current settings" AdvSceneSwitcher.condition.filter.getSettings="Get current settings"
AdvSceneSwitcher.condition.filter.entry.line1="On {{sources}} {{filters}} {{conditions}}" AdvSceneSwitcher.condition.filter.entry.line1="On {{sources}} {{filters}} {{conditions}}"
AdvSceneSwitcher.condition.filter.entry.line2="{{settings}}" AdvSceneSwitcher.condition.filter.entry.line2="{{settings}}"
@@ -286,7 +291,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.date.entry.updateOnRepeat="{{updateOnRepeat}} On repeat update selected date to repeat date"
AdvSceneSwitcher.condition.sceneTransform="Scene item transform" AdvSceneSwitcher.condition.sceneTransform="Scene item transform"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform" AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
AdvSceneSwitcher.condition.sceneTransform.regex="Use regular expressions"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="On{{scenes}}{{sources}}matches transform" AdvSceneSwitcher.condition.sceneTransform.entry.line1="On{{scenes}}{{sources}}matches transform"
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}" AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}" AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}"
@@ -335,12 +339,25 @@ AdvSceneSwitcher.condition.stats.entry="{{stats}} is {{condition}} {{value}}"
AdvSceneSwitcher.condition.profile="Profile" AdvSceneSwitcher.condition.profile="Profile"
AdvSceneSwitcher.condition.profile.entry="Current active profile is {{profiles}}" AdvSceneSwitcher.condition.profile.entry="Current active profile is {{profiles}}"
AdvSceneSwitcher.condition.websocket="Websocket" AdvSceneSwitcher.condition.websocket="Websocket"
AdvSceneSwitcher.condition.websocket.type.request="Request"
AdvSceneSwitcher.condition.websocket.type.event="Event"
AdvSceneSwitcher.condition.websocket.useRegex="Use regular expressions" 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 ; Macro Actions
AdvSceneSwitcher.action.switchScene="Switch scene" 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.scene.blockUntilTransitionDone="Wait until transition to target scene is complete"
AdvSceneSwitcher.action.wait="Wait" AdvSceneSwitcher.action.wait="Wait"
AdvSceneSwitcher.action.wait.type.fixed="fixed" AdvSceneSwitcher.action.wait.type.fixed="fixed"
@@ -481,8 +498,13 @@ AdvSceneSwitcher.action.random.entry="Randomly run any of the following macros (
AdvSceneSwitcher.action.systray="System tray notification" AdvSceneSwitcher.action.systray="System tray notification"
AdvSceneSwitcher.action.systray.entry="Show notification: {{message}}" AdvSceneSwitcher.action.systray.entry="Show notification: {{message}}"
AdvSceneSwitcher.action.screenshot="Screenshot" 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.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="Profile"
AdvSceneSwitcher.action.profile.entry="Switch active profile to {{profiles}}" AdvSceneSwitcher.action.profile.entry="Switch active profile to {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="Scene collection" AdvSceneSwitcher.action.sceneCollection="Scene collection"
@@ -495,12 +517,22 @@ AdvSceneSwitcher.action.sequence.status.none="none"
AdvSceneSwitcher.action.sequence.restart="Restart from beginning once end of list is reached" AdvSceneSwitcher.action.sequence.restart="Restart from beginning once end of list is reached"
AdvSceneSwitcher.action.sequence.continueFrom="Continue with selected item" AdvSceneSwitcher.action.sequence.continueFrom="Continue with selected item"
AdvSceneSwitcher.action.websocket="Websocket" 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="Http"
AdvSceneSwitcher.action.http.type.get="GET" AdvSceneSwitcher.action.http.type.get="GET"
AdvSceneSwitcher.action.http.type.post="POST" AdvSceneSwitcher.action.http.type.post="POST"
AdvSceneSwitcher.action.http.entry.line1="Send {{method}} to {{url}}" AdvSceneSwitcher.action.http.entry.line1="Send {{method}} to {{url}}"
AdvSceneSwitcher.action.http.entry.line2="Timeout: {{timeout}} seconds" AdvSceneSwitcher.action.http.entry.line2="Timeout: {{timeout}} seconds"
AdvSceneSwitcher.action.variable="Variable"
AdvSceneSwitcher.action.variable.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 ; Transition Tab
@@ -757,15 +789,27 @@ AdvSceneSwitcher.askForMacro="Select macro {{macroSelection}}"
AdvSceneSwitcher.close="Close" AdvSceneSwitcher.close="Close"
AdvSceneSwitcher.browse="Browse" 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.select="--select connection--"
AdvSceneSwitcher.connection.add="Add new 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.name="Name:"
AdvSceneSwitcher.connection.address="Address:" AdvSceneSwitcher.connection.address="Address:"
AdvSceneSwitcher.connection.port="Port:" AdvSceneSwitcher.connection.port="Port:"
@@ -779,9 +823,17 @@ AdvSceneSwitcher.connection.status.connecting="Connecting"
AdvSceneSwitcher.connection.status.connected="Connected, but not authenticated" AdvSceneSwitcher.connection.status.connected="Connected, but not authenticated"
AdvSceneSwitcher.connection.status.authenticated="Connected and 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.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene" AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene" AdvSceneSwitcher.selectCurrentScene="Current Scene"
AdvSceneSwitcher.selectPreviewScene="Preview Scene"
AdvSceneSwitcher.selectAnyScene="Any Scene" AdvSceneSwitcher.selectAnyScene="Any Scene"
AdvSceneSwitcher.currentTransition="Current Transition" AdvSceneSwitcher.currentTransition="Current Transition"
AdvSceneSwitcher.anyTransition="Any Transition" AdvSceneSwitcher.anyTransition="Any Transition"
@@ -790,7 +842,7 @@ AdvSceneSwitcher.selectWindow="--select window--"
AdvSceneSwitcher.selectSource="--select source--" AdvSceneSwitcher.selectSource="--select source--"
AdvSceneSwitcher.selectAudioSource="--select audio source--" AdvSceneSwitcher.selectAudioSource="--select audio source--"
AdvSceneSwitcher.selectVideoSource="--select video source--" AdvSceneSwitcher.selectVideoSource="--select video source--"
AdvSceneSwitcher.OBSVideoOutput="OBS video output" AdvSceneSwitcher.OBSVideoOutput="OBS's main output"
AdvSceneSwitcher.selectMediaSource="--select media source--" AdvSceneSwitcher.selectMediaSource="--select media source--"
AdvSceneSwitcher.selectProcess="--select process--" AdvSceneSwitcher.selectProcess="--select process--"
AdvSceneSwitcher.selectFilter="--select filter--" AdvSceneSwitcher.selectFilter="--select filter--"

View File

@@ -123,7 +123,7 @@ AdvSceneSwitcher.condition.window="Ventana"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} existen y ..." 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.window.entry.line2="... es {{fullscreen}} pantalla completa {{maximized}} maximizada {{focused}} enfocada {{windowFocusChanged}} ventana de primer plano cambiada"
AdvSceneSwitcher.condition.file="Archivo" AdvSceneSwitcher.condition.file="Archivo"
AdvSceneSwitcher.condition.file.entry.line1="Contenido de {{fileType}} {{filePath}} coincidencias:" AdvSceneSwitcher.condition.file.entry.line1="Contenido de{{fileType}}{{filePath}}{{conditions}}"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}" AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}" AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.condition.media="Medios" AdvSceneSwitcher.condition.media="Medios"
@@ -164,6 +164,7 @@ AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePa
AdvSceneSwitcher.condition.video.entry.modelPath="Datos del modelo (haar cascade classifier): {{modelDataPath}}" 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.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.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.entry.checkArea="{{checkAreaEnable}}Realizar comprobación solo en el área {{checkArea}} {{selectArea}}"
AdvSceneSwitcher.condition.video.minSize="Tamaño mínimo:" AdvSceneSwitcher.condition.video.minSize="Tamaño mínimo:"
AdvSceneSwitcher.condition.video.maxSize="Tamaño máximo:" AdvSceneSwitcher.condition.video.maxSize="Tamaño máximo:"
@@ -463,7 +464,6 @@ AdvSceneSwitcher.action.systray="Notificación de la bandeja del sistema"
AdvSceneSwitcher.action.systray.entry="Mostrar notificación: {{message}}" AdvSceneSwitcher.action.systray.entry="Mostrar notificación: {{message}}"
AdvSceneSwitcher.action.screenshot="Captura de pantalla" AdvSceneSwitcher.action.screenshot="Captura de pantalla"
AdvSceneSwitcher.action.screenshot.mainOutput="Salida principal de OBS" AdvSceneSwitcher.action.screenshot.mainOutput="Salida principal de OBS"
AdvSceneSwitcher.action.screenshot.entry="Captura de pantalla {{sources}}"
AdvSceneSwitcher.action.profile="Perfil" AdvSceneSwitcher.action.profile="Perfil"
AdvSceneSwitcher.action.profile.entry="Cambiar perfil activo a {{profiles}}" AdvSceneSwitcher.action.profile.entry="Cambiar perfil activo a {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="Colección de escenas" AdvSceneSwitcher.action.sceneCollection="Colección de escenas"

View File

@@ -111,7 +111,7 @@ AdvSceneSwitcher.condition.window="Pencere"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} Varolan ve ..." 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.window.entry.line2="... {{fullscreen}} Tam ekran {{maximized}} büyütüldü {{focused}} odaklanıldı {{windowFocusChanged}} ön plan penceresi değişikliği"
AdvSceneSwitcher.condition.file="Dosya" AdvSceneSwitcher.condition.file="Dosya"
AdvSceneSwitcher.condition.file.entry.line1="İçerik {{fileType}} {{filePath}} eşleştirmeler:" AdvSceneSwitcher.condition.file.entry.line1="İçerik{{fileType}}{{filePath}}{{conditions}}"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}" AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}" AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.condition.media="Medya" AdvSceneSwitcher.condition.media="Medya"
@@ -383,7 +383,6 @@ AdvSceneSwitcher.action.systray="Sistem tepsisi bildirimi"
AdvSceneSwitcher.action.systray.entry="Bildirimleri göster: {{message}}" AdvSceneSwitcher.action.systray.entry="Bildirimleri göster: {{message}}"
AdvSceneSwitcher.action.screenshot="Ekran görüntüsü" AdvSceneSwitcher.action.screenshot="Ekran görüntüsü"
AdvSceneSwitcher.action.screenshot.mainOutput="OBS'nin ana çıkışı" 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="Profil"
AdvSceneSwitcher.action.profile.entry="Aktif profili şununla değiştir: {{profiles}}" AdvSceneSwitcher.action.profile.entry="Aktif profili şununla değiştir: {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="Sahne koleksiyonu" AdvSceneSwitcher.action.sceneCollection="Sahne koleksiyonu"

View File

@@ -116,7 +116,6 @@ AdvSceneSwitcher.condition.window="窗口"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} 存在..." AdvSceneSwitcher.condition.window.entry.line1="{{windows}} 存在..."
AdvSceneSwitcher.condition.window.entry.line2="... 并且是 {{fullscreen}} 全屏 {{maximized}} 最大化 {{focused}} 获得焦点 {{windowFocusChanged}} 焦点窗口已更改" AdvSceneSwitcher.condition.window.entry.line2="... 并且是 {{fullscreen}} 全屏 {{maximized}} 最大化 {{focused}} 获得焦点 {{windowFocusChanged}} 焦点窗口已更改"
AdvSceneSwitcher.condition.file="文件" AdvSceneSwitcher.condition.file="文件"
AdvSceneSwitcher.condition.file.entry.line1="内容 {{fileType}} {{filePath}} (本地是文本,网络是获取的网页)完全匹配:"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}" AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}" AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.condition.media="媒体" AdvSceneSwitcher.condition.media="媒体"
@@ -424,7 +423,6 @@ AdvSceneSwitcher.action.systray="系统托盘通知"
AdvSceneSwitcher.action.systray.entry="显示通知: {{message}}" AdvSceneSwitcher.action.systray.entry="显示通知: {{message}}"
AdvSceneSwitcher.action.screenshot="截图" AdvSceneSwitcher.action.screenshot="截图"
AdvSceneSwitcher.action.screenshot.mainOutput="OBS 预览画面" AdvSceneSwitcher.action.screenshot.mainOutput="OBS 预览画面"
AdvSceneSwitcher.action.screenshot.entry="截图 {{sources}}"
AdvSceneSwitcher.action.profile="配置文件" AdvSceneSwitcher.action.profile="配置文件"
AdvSceneSwitcher.action.profile.entry="将活动配置文件切换到 {{profiles}}" AdvSceneSwitcher.action.profile.entry="将活动配置文件切换到 {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="场景集合" AdvSceneSwitcher.action.sceneCollection="场景集合"

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

@@ -642,7 +642,7 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>22</width> <width>22</width>
<height>22</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="flat"> <property name="flat">
@@ -658,7 +658,7 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>22</width> <width>22</width>
<height>22</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="flat"> <property name="flat">
@@ -713,17 +713,14 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>22</width> <width>22</width>
<height>22</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="text">
<string/>
</property>
<property name="flat"> <property name="flat">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="themeID" stdset="0"> <property name="themeID" stdset="0">
<string>upArrowIconSmall</string> <string notr="true">upArrowIconSmall</string>
</property> </property>
</widget> </widget>
</item> </item>
@@ -732,17 +729,14 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>22</width> <width>22</width>
<height>22</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="text">
<string/>
</property>
<property name="flat"> <property name="flat">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="themeID" stdset="0"> <property name="themeID" stdset="0">
<string>downArrowIconSmall</string> <string notr="true">downArrowIconSmall</string>
</property> </property>
</widget> </widget>
</item> </item>
@@ -819,7 +813,7 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>22</width> <width>22</width>
<height>22</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="text"> <property name="text">
@@ -864,7 +858,7 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>22</width> <width>22</width>
<height>22</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="flat"> <property name="flat">
@@ -880,7 +874,7 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>22</width> <width>22</width>
<height>22</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="flat"> <property name="flat">
@@ -935,7 +929,7 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>22</width> <width>22</width>
<height>22</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="text"> <property name="text">
@@ -954,7 +948,7 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>22</width> <width>22</width>
<height>22</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="flat"> <property name="flat">
@@ -1006,7 +1000,7 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>22</width> <width>22</width>
<height>22</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="flat"> <property name="flat">
@@ -1022,7 +1016,7 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>22</width> <width>22</width>
<height>22</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="flat"> <property name="flat">
@@ -1077,7 +1071,7 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>22</width> <width>22</width>
<height>22</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="flat"> <property name="flat">
@@ -1093,7 +1087,7 @@
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>22</width> <width>22</width>
<height>22</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="flat"> <property name="flat">

View File

@@ -227,7 +227,7 @@ void SwitcherData::Thread()
} }
} }
websocketMessages.clear(); ClearWebsocketMessages();
// After this point we will call frontend functions like // After this point we will call frontend functions like
// obs_frontend_set_current_scene() and // obs_frontend_set_current_scene() and
@@ -629,9 +629,11 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
case OBS_FRONTEND_EVENT_STREAMING_STOPPED: case OBS_FRONTEND_EVENT_STREAMING_STOPPED:
resetLiveTime(); resetLiveTime();
break; break;
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(26, 0, 0)
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED: case OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED:
setReplayBufferSaved(); setReplayBufferSaved();
break; break;
#endif
case OBS_FRONTEND_EVENT_TRANSITION_STOPPED: case OBS_FRONTEND_EVENT_TRANSITION_STOPPED:
setTranstionEnd(); setTranstionEnd();
break; break;

View File

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

View File

@@ -192,7 +192,9 @@ void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
bool isLegacyTab(const QString &name) 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( name == obs_module_text(
"AdvSceneSwitcher.transitionTab.title") || "AdvSceneSwitcher.transitionTab.title") ||
name == obs_module_text( name == obs_module_text(
@@ -221,13 +223,13 @@ void AdvSceneSwitcher::on_hideLegacyTabs_stateChanged(int state)
for (int idx = 0; idx < ui->tabWidget->count(); idx++) { for (int idx = 0; idx < ui->tabWidget->count(); idx++) {
if (isLegacyTab(ui->tabWidget->tabText(idx))) { if (isLegacyTab(ui->tabWidget->tabText(idx))) {
#if defined(_WIN32) || defined(__APPLE__) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
ui->tabWidget->setTabVisible(idx, !state);
#else
// TODO: Switch to setTabVisible() once QT 5.15 is more wide spread // TODO: Switch to setTabVisible() once QT 5.15 is more wide spread
ui->tabWidget->setTabEnabled(idx, !state); ui->tabWidget->setTabEnabled(idx, !state);
ui->tabWidget->setStyleSheet( ui->tabWidget->setStyleSheet(
"QTabBar::tab::disabled {width: 0; height: 0; margin: 0; padding: 0; border: none;} "); "QTabBar::tab::disabled {width: 0; height: 0; margin: 0; padding: 0; border: none;} ");
#else
ui->tabWidget->setTabVisible(idx, !state);
#endif #endif
} }
} }
@@ -503,6 +505,7 @@ void SwitcherData::loadSettings(obs_data_t *obj)
// Needs to be loaded before any entries which might rely on scene group // Needs to be loaded before any entries which might rely on scene group
// selections to be available. // selections to be available.
loadSceneGroups(obj); loadSceneGroups(obj);
loadVariables(obj);
loadMacros(obj); loadMacros(obj);
loadConnections(obj); loadConnections(obj);
loadWindowTitleSwitches(obj); loadWindowTitleSwitches(obj);
@@ -537,6 +540,7 @@ void SwitcherData::saveSettings(obs_data_t *obj)
saveSceneGroups(obj); saveSceneGroups(obj);
saveMacros(obj); saveMacros(obj);
saveConnections(obj); saveConnections(obj);
saveVariables(obj);
saveWindowTitleSwitches(obj); saveWindowTitleSwitches(obj);
saveScreenRegionSwitches(obj); saveScreenRegionSwitches(obj);
savePauseSwitches(obj); savePauseSwitches(obj);
@@ -687,6 +691,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) 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()); obs_data_array_t *array = obs_data_get_array(obj, name.c_str());
size_t count = obs_data_array_count(array); size_t count = obs_data_array_count(array);
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -55,7 +55,7 @@ int getTransitionOverrideDuration(OBSWeakSource &scene)
return duration; return duration;
} }
bool isUsingFixedLengthTransition(OBSWeakSource &transition) bool isUsingFixedLengthTransition(const OBSWeakSource &transition)
{ {
obs_source_t *source = obs_weak_source_get_source(transition); obs_source_t *source = obs_weak_source_get_source(transition);
bool ret = obs_transition_fixed(source); bool ret = obs_transition_fixed(source);
@@ -123,15 +123,15 @@ void MacroActionSwitchScene::LogAction()
auto t = _scene.GetType(); auto t = _scene.GetType();
auto sceneName = GetWeakSourceName(_scene.GetScene(false)); auto sceneName = GetWeakSourceName(_scene.GetScene(false));
switch (t) { switch (t) {
case SceneSelectionType::SCENE: case SceneSelection::Type::SCENE:
vblog(LOG_INFO, "switch to scene '%s'", vblog(LOG_INFO, "switch to scene '%s'",
_scene.ToString().c_str()); _scene.ToString().c_str());
break; break;
case SceneSelectionType::GROUP: case SceneSelection::Type::GROUP:
vblog(LOG_INFO, "switch to scene '%s' (scene group '%s')", vblog(LOG_INFO, "switch to scene '%s' (scene group '%s')",
sceneName.c_str(), _scene.ToString().c_str()); sceneName.c_str(), _scene.ToString().c_str());
break; break;
case SceneSelectionType::PREVIOUS: case SceneSelection::Type::PREVIOUS:
vblog(LOG_INFO, "switch to previous scene '%s'", vblog(LOG_INFO, "switch to previous scene '%s'",
sceneName.c_str()); sceneName.c_str());
break; break;
@@ -153,30 +153,6 @@ bool MacroActionSwitchScene::Save(obs_data_t *obj)
bool MacroActionSwitchScene::Load(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); MacroAction::Load(obj);
_scene.Load(obj); _scene.Load(obj);
_transition.Load(obj); _transition.Load(obj);
@@ -193,14 +169,14 @@ std::string MacroActionSwitchScene::GetShortDesc()
MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit( MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
QWidget *parent, std::shared_ptr<MacroActionSwitchScene> entryData) 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("-"); _duration->SpinBox()->setSpecialValueText("-");
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)), QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
@@ -214,7 +190,6 @@ MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
QWidget::connect(_blockUntilTransitionDone, SIGNAL(stateChanged(int)), QWidget::connect(_blockUntilTransitionDone, SIGNAL(stateChanged(int)),
this, SLOT(BlockUntilTransitionDoneChanged(int))); this, SLOT(BlockUntilTransitionDoneChanged(int)));
QHBoxLayout *entryLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = { std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes}, {"{{scenes}}", _scenes},
{"{{transitions}}", _transitions}, {"{{transitions}}", _transitions},
@@ -222,10 +197,10 @@ MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
{"{{blockUntilTransitionDone}}", _blockUntilTransitionDone}, {"{{blockUntilTransitionDone}}", _blockUntilTransitionDone},
}; };
placeWidgets(obs_module_text("AdvSceneSwitcher.action.scene.entry"), placeWidgets(obs_module_text("AdvSceneSwitcher.action.scene.entry"),
entryLayout, widgetPlaceholders); _entryLayout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout; QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLayout); mainLayout->addLayout(_entryLayout);
mainLayout->addWidget(_blockUntilTransitionDone); mainLayout->addWidget(_blockUntilTransitionDone);
setLayout(mainLayout); setLayout(mainLayout);
@@ -235,6 +210,7 @@ MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
_duration->SetDuration(_entryData->_duration); _duration->SetDuration(_entryData->_duration);
_blockUntilTransitionDone->setChecked( _blockUntilTransitionDone->setChecked(
_entryData->_blockUntilTransitionDone); _entryData->_blockUntilTransitionDone);
SetDurationVisibility();
_loading = false; _loading = false;
} }
@@ -258,6 +234,37 @@ void MacroActionSwitchSceneEdit::BlockUntilTransitionDoneChanged(int state)
_entryData->_blockUntilTransitionDone = 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) void MacroActionSwitchSceneEdit::SceneChanged(const SceneSelection &s)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
@@ -278,4 +285,5 @@ void MacroActionSwitchSceneEdit::TransitionChanged(const TransitionSelection &t)
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_transition = t; _entryData->_transition = t;
SetDurationVisibility();
} }

View File

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

View File

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

View File

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

View File

@@ -9,73 +9,155 @@ bool MacroActionScreenshot::_registered = MacroActionFactory::Register(
{MacroActionScreenshot::Create, MacroActionScreenshotEdit::Create, {MacroActionScreenshot::Create, MacroActionScreenshotEdit::Create,
"AdvSceneSwitcher.action.screenshot"}); "AdvSceneSwitcher.action.screenshot"});
bool MacroActionScreenshot::PerformAction() void MacroActionScreenshot::FrontendScreenshot(OBSWeakSource &source)
{ {
if (_source) { #if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(26, 0, 0)
auto s = obs_weak_source_get_source(_source); if (source) {
auto s = obs_weak_source_get_source(source);
obs_frontend_take_source_screenshot(s); obs_frontend_take_source_screenshot(s);
obs_source_release(s); obs_source_release(s);
} else { } else {
obs_frontend_take_screenshot(); 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; return true;
} }
void MacroActionScreenshot::LogAction() void MacroActionScreenshot::LogAction()
{ {
vblog(LOG_INFO, "trigger screenshot for source \"%s\"", vblog(LOG_INFO, "trigger screenshot for \"%s\"",
GetWeakSourceName(_source).c_str()); _targetType == TargetType::SOURCE
? GetWeakSourceName(_source).c_str()
: _scene.ToString().c_str());
} }
bool MacroActionScreenshot::Save(obs_data_t *obj) bool MacroActionScreenshot::Save(obs_data_t *obj)
{ {
MacroAction::Save(obj); MacroAction::Save(obj);
_scene.Save(obj);
obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str()); 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; return true;
} }
bool MacroActionScreenshot::Load(obs_data_t *obj) bool MacroActionScreenshot::Load(obs_data_t *obj)
{ {
MacroAction::Load(obj); MacroAction::Load(obj);
_scene.Load(obj);
const char *sourceName = obs_data_get_string(obj, "source"); const char *sourceName = obs_data_get_string(obj, "source");
_source = GetWeakSourceByName(sourceName); _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; return true;
} }
std::string MacroActionScreenshot::GetShortDesc() std::string MacroActionScreenshot::GetShortDesc()
{ {
if (_source) { if (_targetType == TargetType::SCENE) {
return _scene.ToString();
} else {
return GetWeakSourceName(_source); return GetWeakSourceName(_source);
} }
return ""; return "";
} }
void addOBSMainOutputEntry(QComboBox *cb) static void populateSaveTypeSelection(QComboBox *list)
{ {
cb->insertItem( list->addItem(obs_module_text(
0, obs_module_text( "AdvSceneSwitcher.action.screenshot.save.default"));
"AdvSceneSwitcher.action.screenshot.mainOutput")); 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( MacroActionScreenshotEdit::MacroActionScreenshotEdit(
QWidget *parent, std::shared_ptr<MacroActionScreenshot> entryData) 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(); setToolTip(obs_module_text(
populateVideoSelection(_sources, true, false); "AdvSceneSwitcher.action.screenshot.blackscreenNote"));
addOBSMainOutputEntry(_sources); 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 &)), QWidget::connect(_sources, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(SourceChanged(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 = { std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{sources}}", _sources}, {"{{sources}}", _sources},
{"{{scenes}}", _scenes},
{"{{saveType}}", _saveType},
{"{{targetType}}", _targetType},
}; };
placeWidgets( placeWidgets(
obs_module_text("AdvSceneSwitcher.action.screenshot.entry"), obs_module_text("AdvSceneSwitcher.action.screenshot.entry"),
mainLayout, widgetPlaceholders); layout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(layout);
mainLayout->addWidget(_savePath);
setLayout(mainLayout); setLayout(mainLayout);
_entryData = entryData; _entryData = entryData;
@@ -93,8 +175,55 @@ void MacroActionScreenshotEdit::UpdateEntryData()
_sources->setCurrentText( _sources->setCurrentText(
GetWeakSourceName(_entryData->_source).c_str()); GetWeakSourceName(_entryData->_source).c_str());
} else { } 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) void MacroActionScreenshotEdit::SourceChanged(const QString &text)
@@ -108,3 +237,18 @@ void MacroActionScreenshotEdit::SourceChanged(const QString &text)
emit HeaderInfoChanged( emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc())); 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 #pragma once
#include "macro-action-edit.hpp" #include "macro-action-edit.hpp"
#include "file-selection.hpp"
#include "scene-selection.hpp"
#include "screenshot-helper.hpp"
#include <QComboBox> #include <QComboBox>
@@ -16,9 +19,25 @@ public:
{ {
return std::make_shared<MacroActionScreenshot>(m); 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; OBSWeakSource _source;
std::string _path = obs_module_text("AdvSceneSwitcher.enterPath");
private: private:
void FrontendScreenshot(OBSWeakSource &);
void CustomScreenshot(OBSWeakSource &);
ScreenshotHelper _screenshot;
static bool _registered; static bool _registered;
static const std::string id; static const std::string id;
}; };
@@ -40,14 +59,24 @@ public:
action)); action));
} }
private slots: private slots:
void SceneChanged(const SceneSelection &);
void SourceChanged(const QString &text); void SourceChanged(const QString &text);
void SaveTypeChanged(int index);
void TargetTypeChanged(int index);
void PathChanged(const QString &text);
signals: signals:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
protected: protected:
SceneSelectionWidget *_scenes;
QComboBox *_sources; QComboBox *_sources;
QComboBox *_saveType;
QComboBox *_targetType;
FileSelection *_savePath;
std::shared_ptr<MacroActionScreenshot> _entryData; std::shared_ptr<MacroActionScreenshot> _entryData;
private: private:
void SetWidgetVisibility();
bool _loading = true; bool _loading = true;
}; };

View File

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

View File

@@ -49,8 +49,32 @@ void MacroActionTransition::SetTransitionOverride()
obs_source_release(scene); 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) void MacroActionTransition::SetSourceTransition(bool show)
{ {
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
auto transition = auto transition =
obs_weak_source_get_source(_transition.GetTransition()); obs_weak_source_get_source(_transition.GetTransition());
obs_data_t *settings = obs_source_get_settings(transition); obs_data_t *settings = obs_source_get_settings(transition);
@@ -73,6 +97,7 @@ void MacroActionTransition::SetSourceTransition(bool show)
} }
obs_source_release(t); obs_source_release(t);
#endif
} }
bool MacroActionTransition::PerformAction() bool MacroActionTransition::PerformAction()
@@ -182,7 +207,7 @@ MacroActionTransitionEdit::MacroActionTransitionEdit(
: QWidget(parent), : QWidget(parent),
_actions(new QComboBox), _actions(new QComboBox),
_sources(new SceneItemSelectionWidget(parent, false)), _sources(new SceneItemSelectionWidget(parent, false)),
_scenes(new SceneSelectionWidget(this, false, false, true)), _scenes(new SceneSelectionWidget(this, true, false, false, true)),
_setTransition(new QCheckBox), _setTransition(new QCheckBox),
_setDuration(new QCheckBox), _setDuration(new QCheckBox),
_transitions(new TransitionSelectionWidget(this, false)), _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() bool MacroActionVCam::PerformAction()
{ {
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
switch (_action) { switch (_action) {
case VCamAction::STOP: case VCamAction::STOP:
if (obs_frontend_virtualcam_active()) { if (obs_frontend_virtualcam_active()) {
@@ -30,6 +31,7 @@ bool MacroActionVCam::PerformAction()
default: default:
break; break;
} }
#endif
return true; return true;
} }

View File

@@ -9,25 +9,58 @@ bool MacroActionWebsocket::_registered = MacroActionFactory::Register(
{MacroActionWebsocket::Create, MacroActionWebsocketEdit::Create, {MacroActionWebsocket::Create, MacroActionWebsocketEdit::Create,
"AdvSceneSwitcher.action.websocket"}); "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); auto connection = GetConnectionByName(_connection);
if (!connection) { if (!connection) {
return true; return;
} }
connection->SendMsg(_message); 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; return true;
} }
void MacroActionWebsocket::LogAction() void MacroActionWebsocket::LogAction()
{ {
vblog(LOG_INFO, "sent msg \"%s\" via \"%s\"", _message.c_str(), switch (_type) {
_connection.c_str()); 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) bool MacroActionWebsocket::Save(obs_data_t *obj)
{ {
MacroAction::Save(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, "message", _message.c_str());
obs_data_set_string(obj, "connection", _connection.c_str()); obs_data_set_string(obj, "connection", _connection.c_str());
return true; return true;
@@ -36,6 +69,7 @@ bool MacroActionWebsocket::Save(obs_data_t *obj)
bool MacroActionWebsocket::Load(obs_data_t *obj) bool MacroActionWebsocket::Load(obs_data_t *obj)
{ {
MacroAction::Load(obj); MacroAction::Load(obj);
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
_message = obs_data_get_string(obj, "message"); _message = obs_data_get_string(obj, "message");
_connection = obs_data_get_string(obj, "connection"); _connection = obs_data_get_string(obj, "connection");
return true; return true;
@@ -43,30 +77,39 @@ bool MacroActionWebsocket::Load(obs_data_t *obj)
std::string MacroActionWebsocket::GetShortDesc() 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( MacroActionWebsocketEdit::MacroActionWebsocketEdit(
QWidget *parent, std::shared_ptr<MacroActionWebsocket> entryData) QWidget *parent, std::shared_ptr<MacroActionWebsocket> entryData)
: QWidget(parent), : QWidget(parent),
_actions(new QComboBox(this)),
_message(new ResizingPlainTextEdit(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, QWidget::connect(_message, SIGNAL(textChanged()), this,
SLOT(MessageChanged())); SLOT(MessageChanged()));
QWidget::connect(_connection, SIGNAL(SelectionChanged(const QString &)), QWidget::connect(_connection, SIGNAL(SelectionChanged(const QString &)),
this, this,
SLOT(ConnectionSelectionChanged(const QString &))); 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; QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(connectionLayout); mainLayout->addLayout(_editLayout);
mainLayout->addWidget(_message); mainLayout->addWidget(_message);
setLayout(mainLayout); setLayout(mainLayout);
@@ -75,19 +118,73 @@ MacroActionWebsocketEdit::MacroActionWebsocketEdit(
_loading = false; _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() void MacroActionWebsocketEdit::UpdateEntryData()
{ {
if (!_entryData) { if (!_entryData) {
return; return;
} }
_actions->setCurrentIndex(static_cast<int>(_entryData->_type));
_message->setPlainText(QString::fromStdString(_entryData->_message)); _message->setPlainText(QString::fromStdString(_entryData->_message));
_connection->SetConnection(_entryData->_connection); _connection->SetConnection(_entryData->_connection);
if (_entryData->_type == MacroActionWebsocket::Type::REQUEST) {
SetupRequestEdit();
} else {
SetupEventEdit();
}
adjustSize(); adjustSize();
updateGeometry(); 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() void MacroActionWebsocketEdit::MessageChanged()
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {

View File

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

View File

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

View File

@@ -40,7 +40,7 @@ static std::string getRemoteData(std::string &url)
return readBuffer; return readBuffer;
} }
bool MacroConditionFile::matchFileContent(QString &filedata) bool MacroConditionFile::MatchFileContent(QString &filedata)
{ {
if (_onlyMatchIfChanged) { if (_onlyMatchIfChanged) {
size_t newHash = strHash(filedata.toUtf8().constData()); size_t newHash = strHash(filedata.toUtf8().constData());
@@ -50,31 +50,30 @@ bool MacroConditionFile::matchFileContent(QString &filedata)
_lastHash = newHash; _lastHash = newHash;
} }
if (_useRegex) { if (_regex.Enabled()) {
try { auto expr = _regex.GetRegularExpression(_text);
std::regex expr(_text); if (!expr.isValid()) {
return std::regex_match(filedata.toStdString(), expr);
} catch (const std::regex_error &) {
return false; return false;
} }
auto match = expr.match(filedata);
return match.hasMatch();
} }
QString text = QString::fromStdString(_text); QString text = QString::fromStdString(_text);
return compareIgnoringLineEnding(text, filedata); return compareIgnoringLineEnding(text, filedata);
} }
bool MacroConditionFile::checkRemoteFileContent() bool MacroConditionFile::CheckRemoteFileContent()
{ {
std::string data = getRemoteData(_file); std::string data = getRemoteData(_file);
QString qdata = QString::fromStdString(data); 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)); QFile file(QString::fromStdString(_file));
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false; return false;
} }
@@ -87,28 +86,78 @@ bool MacroConditionFile::checkLocalFileContent()
} }
QString filedata = QTextStream(&file).readAll(); QString filedata = QTextStream(&file).readAll();
bool match = matchFileContent(filedata); bool match = MatchFileContent(filedata);
file.close(); file.close();
return match; 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) { if (_fileType == FileType::REMOTE) {
return checkRemoteFileContent(); return false;
} else {
return checkLocalFileContent();
} }
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) bool MacroConditionFile::Save(obs_data_t *obj)
{ {
MacroCondition::Save(obj); MacroCondition::Save(obj);
_regex.Save(obj);
obs_data_set_string(obj, "file", _file.c_str()); obs_data_set_string(obj, "file", _file.c_str());
obs_data_set_string(obj, "text", _text.c_str()); obs_data_set_string(obj, "text", _text.c_str());
obs_data_set_int(obj, "fileType", static_cast<int>(_fileType)); 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, "useTime", _useTime);
obs_data_set_bool(obj, "onlyMatchIfChanged", _onlyMatchIfChanged); obs_data_set_bool(obj, "onlyMatchIfChanged", _onlyMatchIfChanged);
return true; return true;
@@ -117,10 +166,17 @@ bool MacroConditionFile::Save(obs_data_t *obj)
bool MacroConditionFile::Load(obs_data_t *obj) bool MacroConditionFile::Load(obs_data_t *obj)
{ {
MacroCondition::Load(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"); _file = obs_data_get_string(obj, "file");
_text = obs_data_get_string(obj, "text"); _text = obs_data_get_string(obj, "text");
_fileType = static_cast<FileType>(obs_data_get_int(obj, "fileType")); _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"); _useTime = obs_data_get_bool(obj, "useTime");
_onlyMatchIfChanged = obs_data_get_bool(obj, "onlyMatchIfChanged"); _onlyMatchIfChanged = obs_data_get_bool(obj, "onlyMatchIfChanged");
return true; return true;
@@ -131,41 +187,59 @@ std::string MacroConditionFile::GetShortDesc()
return _file; 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( MacroConditionFileEdit::MacroConditionFileEdit(
QWidget *parent, std::shared_ptr<MacroConditionFile> entryData) 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(); populateFileTypes(_fileTypes);
_filePath = new FileSelection(); populateConditions(_conditions);
_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"));
QWidget::connect(_fileType, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_fileTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(FileTypeChanged(int))); SLOT(FileTypeChanged(int)));
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this, QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this,
SLOT(PathChanged(const QString &))); SLOT(PathChanged(const QString &)));
QWidget::connect(_matchText, SIGNAL(textChanged()), this, QWidget::connect(_matchText, SIGNAL(textChanged()), this,
SLOT(MatchTextChanged())); SLOT(MatchTextChanged()));
QWidget::connect(_useRegex, SIGNAL(stateChanged(int)), this, QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
SLOT(UseRegexChanged(int))); SLOT(RegexChanged(RegexConfig)));
QWidget::connect(_checkModificationDate, SIGNAL(stateChanged(int)), QWidget::connect(_checkModificationDate, SIGNAL(stateChanged(int)),
this, SLOT(CheckModificationDateChanged(int))); this, SLOT(CheckModificationDateChanged(int)));
QWidget::connect(_checkFileContent, SIGNAL(stateChanged(int)), this, QWidget::connect(_checkFileContent, SIGNAL(stateChanged(int)), this,
SLOT(OnlyMatchIfChangedChanged(int))); 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 = { std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{fileType}}", _fileType}, {"{{fileType}}", _fileTypes},
{"{{conditions}}", _conditions},
{"{{filePath}}", _filePath}, {"{{filePath}}", _filePath},
{"{{matchText}}", _matchText}, {"{{matchText}}", _matchText},
{"{{useRegex}}", _useRegex}, {"{{useRegex}}", _regex},
{"{{checkModificationDate}}", _checkModificationDate}, {"{{checkModificationDate}}", _checkModificationDate},
{"{{checkFileContent}}", _checkFileContent}, {"{{checkFileContent}}", _checkFileContent},
}; };
@@ -200,16 +274,23 @@ void MacroConditionFileEdit::UpdateEntryData()
return; 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)); _filePath->SetPath(QString::fromStdString(_entryData->_file));
_matchText->setPlainText(QString::fromStdString(_entryData->_text)); _matchText->setPlainText(QString::fromStdString(_entryData->_text));
_useRegex->setChecked(_entryData->_useRegex); _regex->SetRegexConfig(_entryData->_regex);
_checkModificationDate->setChecked(_entryData->_useTime); _checkModificationDate->setChecked(_entryData->_useTime);
_checkFileContent->setChecked(_entryData->_onlyMatchIfChanged); _checkFileContent->setChecked(_entryData->_onlyMatchIfChanged);
adjustSize(); // TODO: Remove in future version
updateGeometry(); if (!_entryData->_useTime) {
_checkModificationDate->hide();
}
if (!_entryData->_onlyMatchIfChanged) {
_checkFileContent->hide();
}
SetWidgetVisibility();
} }
void MacroConditionFileEdit::FileTypeChanged(int index) void MacroConditionFileEdit::FileTypeChanged(int index)
@@ -218,9 +299,10 @@ void MacroConditionFileEdit::FileTypeChanged(int index)
return; 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); _filePath->Button()->setDisabled(false);
_checkModificationDate->setDisabled(false); _checkModificationDate->setDisabled(false);
} else { } else {
@@ -232,6 +314,18 @@ void MacroConditionFileEdit::FileTypeChanged(int index)
_entryData->_fileType = type; _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) void MacroConditionFileEdit::PathChanged(const QString &text)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
@@ -257,14 +351,16 @@ void MacroConditionFileEdit::MatchTextChanged()
updateGeometry(); updateGeometry();
} }
void MacroConditionFileEdit::UseRegexChanged(int state) void MacroConditionFileEdit::RegexChanged(RegexConfig conf)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
return; return;
} }
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_useRegex = state; _entryData->_regex = conf;
adjustSize();
updateGeometry();
} }
void MacroConditionFileEdit::CheckModificationDateChanged(int state) void MacroConditionFileEdit::CheckModificationDateChanged(int state)
@@ -286,3 +382,25 @@ void MacroConditionFileEdit::OnlyMatchIfChangedChanged(int state)
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_onlyMatchIfChanged = state; _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 "macro.hpp"
#include "file-selection.hpp" #include "file-selection.hpp"
#include "resizing-text-edit.hpp" #include "resizing-text-edit.hpp"
#include "regex-config.hpp"
#include <QWidget> #include <QWidget>
#include <QComboBox> #include <QComboBox>
@@ -10,11 +11,6 @@
#include <QPushButton> #include <QPushButton>
#include <QCheckBox> #include <QCheckBox>
enum class FileType {
LOCAL,
REMOTE,
};
class MacroConditionFile : public MacroCondition { class MacroConditionFile : public MacroCondition {
public: public:
MacroConditionFile(Macro *m) : MacroCondition(m) {} MacroConditionFile(Macro *m) : MacroCondition(m) {}
@@ -28,17 +24,33 @@ public:
return std::make_shared<MacroConditionFile>(m); 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 _file = obs_module_text("AdvSceneSwitcher.enterPath");
std::string _text = obs_module_text("AdvSceneSwitcher.enterText"); std::string _text = obs_module_text("AdvSceneSwitcher.enterText");
FileType _fileType = FileType::LOCAL; FileType _fileType = FileType::LOCAL;
bool _useRegex = false; ConditionType _condition = ConditionType::MATCH;
RegexConfig _regex;
// TODO: Remove in future version
bool _useTime = false; bool _useTime = false;
bool _onlyMatchIfChanged = false; bool _onlyMatchIfChanged = false;
private: private:
bool matchFileContent(QString &filedata); bool MatchFileContent(QString &filedata);
bool checkRemoteFileContent(); bool CheckRemoteFileContent();
bool checkLocalFileContent(); bool CheckLocalFileContent();
bool CheckChangeContent();
bool CheckChangeDate();
QDateTime _lastMod; QDateTime _lastMod;
size_t _lastHash = 0; size_t _lastHash = 0;
@@ -64,23 +76,27 @@ public:
private slots: private slots:
void FileTypeChanged(int index); void FileTypeChanged(int index);
void ConditionChanged(int index);
void PathChanged(const QString &text); void PathChanged(const QString &text);
void MatchTextChanged(); void MatchTextChanged();
void UseRegexChanged(int state); void RegexChanged(RegexConfig);
void CheckModificationDateChanged(int state); void CheckModificationDateChanged(int state);
void OnlyMatchIfChangedChanged(int state); void OnlyMatchIfChangedChanged(int state);
signals: signals:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
protected: protected:
QComboBox *_fileType; QComboBox *_fileTypes;
QComboBox *_conditions;
FileSelection *_filePath; FileSelection *_filePath;
ResizingPlainTextEdit *_matchText; ResizingPlainTextEdit *_matchText;
QCheckBox *_useRegex; RegexConfigWidget *_regex;
QCheckBox *_checkModificationDate; QCheckBox *_checkModificationDate;
QCheckBox *_checkFileContent; QCheckBox *_checkFileContent;
std::shared_ptr<MacroConditionFile> _entryData; std::shared_ptr<MacroConditionFile> _entryData;
private: private:
void SetWidgetVisibility();
bool _loading = true; 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_string(obj, "filter", GetWeakSourceName(_filter).c_str());
obs_data_set_int(obj, "condition", static_cast<int>(_condition)); obs_data_set_int(obj, "condition", static_cast<int>(_condition));
obs_data_set_string(obj, "settings", _settings.c_str()); obs_data_set_string(obj, "settings", _settings.c_str());
obs_data_set_bool(obj, "regex", _regex); _regex.Save(obj);
return true; return true;
} }
@@ -70,7 +70,12 @@ bool MacroConditionFilter::Load(obs_data_t *obj)
_condition = static_cast<FilterCondition>( _condition = static_cast<FilterCondition>(
obs_data_get_int(obj, "condition")); obs_data_get_int(obj, "condition"));
_settings = obs_data_get_string(obj, "settings"); _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; return true;
} }
@@ -92,18 +97,16 @@ static inline void populateConditionSelection(QComboBox *list)
MacroConditionFilterEdit::MacroConditionFilterEdit( MacroConditionFilterEdit::MacroConditionFilterEdit(
QWidget *parent, std::shared_ptr<MacroConditionFilter> entryData) 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); _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); populateConditionSelection(_conditions);
populateSourcesWithFilterSelection(_sources); populateSourcesWithFilterSelection(_sources);
@@ -117,8 +120,8 @@ MacroConditionFilterEdit::MacroConditionFilterEdit(
SLOT(GetSettingsClicked())); SLOT(GetSettingsClicked()));
QWidget::connect(_settings, SIGNAL(textChanged()), this, QWidget::connect(_settings, SIGNAL(textChanged()), this,
SLOT(SettingsChanged())); SLOT(SettingsChanged()));
QWidget::connect(_regex, SIGNAL(stateChanged(int)), this, QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
SLOT(RegexChanged(int))); SLOT(RegexChanged(RegexConfig)));
QHBoxLayout *line1Layout = new QHBoxLayout; QHBoxLayout *line1Layout = new QHBoxLayout;
QHBoxLayout *line2Layout = new QHBoxLayout; QHBoxLayout *line2Layout = new QHBoxLayout;
@@ -194,7 +197,7 @@ void MacroConditionFilterEdit::GetSettingsClicked()
} }
QString json = formatJsonString(getSourceSettings(_entryData->_filter)); QString json = formatJsonString(getSourceSettings(_entryData->_filter));
if (_entryData->_regex) { if (_entryData->_regex.Enabled()) {
json = escapeForRegex(json); json = escapeForRegex(json);
} }
_settings->setPlainText(json); _settings->setPlainText(json);
@@ -213,14 +216,17 @@ void MacroConditionFilterEdit::SettingsChanged()
updateGeometry(); updateGeometry();
} }
void MacroConditionFilterEdit::RegexChanged(int state) void MacroConditionFilterEdit::RegexChanged(RegexConfig conf)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
return; return;
} }
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_regex = state; _entryData->_regex = conf;
adjustSize();
updateGeometry();
} }
void MacroConditionFilterEdit::SetSettingsSelectionVisible(bool visible) void MacroConditionFilterEdit::SetSettingsSelectionVisible(bool visible)
@@ -244,7 +250,7 @@ void MacroConditionFilterEdit::UpdateEntryData()
GetWeakSourceName(_entryData->_filter).c_str()); GetWeakSourceName(_entryData->_filter).c_str());
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition)); _conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
_settings->setPlainText(QString::fromStdString(_entryData->_settings)); _settings->setPlainText(QString::fromStdString(_entryData->_settings));
_regex->setChecked(_entryData->_regex); _regex->SetRegexConfig(_entryData->_regex);
SetSettingsSelectionVisible(_entryData->_condition == SetSettingsSelectionVisible(_entryData->_condition ==
FilterCondition::SETTINGS); FilterCondition::SETTINGS);

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "macro.hpp" #include "macro.hpp"
#include "resizing-text-edit.hpp" #include "resizing-text-edit.hpp"
#include "regex-config.hpp"
#include <QComboBox> #include <QComboBox>
#include <QPushButton> #include <QPushButton>
@@ -29,7 +30,7 @@ public:
OBSWeakSource _filter; OBSWeakSource _filter;
FilterCondition _condition = FilterCondition::ENABLED; FilterCondition _condition = FilterCondition::ENABLED;
std::string _settings = ""; std::string _settings = "";
bool _regex = false; RegexConfig _regex;
private: private:
static bool _registered; static bool _registered;
@@ -58,7 +59,7 @@ private slots:
void ConditionChanged(int cond); void ConditionChanged(int cond);
void GetSettingsClicked(); void GetSettingsClicked();
void SettingsChanged(); void SettingsChanged();
void RegexChanged(int); void RegexChanged(RegexConfig);
signals: signals:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
@@ -68,7 +69,7 @@ protected:
QComboBox *_conditions; QComboBox *_conditions;
QPushButton *_getSettings; QPushButton *_getSettings;
ResizingPlainTextEdit *_settings; ResizingPlainTextEdit *_settings;
QCheckBox *_regex; RegexConfigWidget *_regex;
std::shared_ptr<MacroConditionFilter> _entryData; 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, "type", static_cast<int>(_type));
obs_data_set_int(obj, "condition", static_cast<int>(_counterCondition)); obs_data_set_int(obj, "condition", static_cast<int>(_counterCondition));
obs_data_set_int(obj, "count", _count); obs_data_set_int(obj, "count", _count);
obs_data_set_int(obj, "multiStateCount", _multiSateCount);
return true; return true;
} }
@@ -136,6 +137,7 @@ bool MacroConditionMacro::Load(obs_data_t *obj)
_counterCondition = static_cast<CounterCondition>( _counterCondition = static_cast<CounterCondition>(
obs_data_get_int(obj, "condition")); obs_data_get_int(obj, "condition"));
_count = obs_data_get_int(obj, "count"); _count = obs_data_get_int(obj, "count");
_multiSateCount = obs_data_get_int(obj, "multiStateCount");
return true; return true;
} }

View File

@@ -140,7 +140,7 @@ bool MacroConditionSceneOrder::Save(obs_data_t *obj)
MacroCondition::Save(obj); MacroCondition::Save(obj);
_scene.Save(obj); _scene.Save(obj);
_source.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, "condition", static_cast<int>(_condition));
obs_data_set_int(obj, "position", _position); obs_data_set_int(obj, "position", _position);
return true; return true;
@@ -160,7 +160,14 @@ bool MacroConditionSceneOrder::Load(obs_data_t *obj)
MacroCondition::Load(obj); MacroCondition::Load(obj);
_scene.Load(obj); _scene.Load(obj);
_source.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>( _condition = static_cast<SceneOrderCondition>(
obs_data_get_int(obj, "condition")); obs_data_get_int(obj, "condition"));
_position = obs_data_get_int(obj, "position"); _position = obs_data_get_int(obj, "position");
@@ -191,7 +198,7 @@ MacroConditionSceneOrderEdit::MacroConditionSceneOrderEdit(
QWidget *parent, std::shared_ptr<MacroConditionSceneOrder> entryData) QWidget *parent, std::shared_ptr<MacroConditionSceneOrder> entryData)
: QWidget(parent) : QWidget(parent)
{ {
_scenes = new SceneSelectionWidget(window(), false, false, true); _scenes = new SceneSelectionWidget(window(), true, false, false, true);
_sources = new SceneItemSelectionWidget(parent); _sources = new SceneItemSelectionWidget(parent);
_sources2 = new SceneItemSelectionWidget(parent); _sources2 = new SceneItemSelectionWidget(parent);
_conditions = new QComboBox(); _conditions = new QComboBox();

View File

@@ -33,7 +33,7 @@ bool MacroConditionSceneTransform::Save(obs_data_t *obj)
_scene.Save(obj); _scene.Save(obj);
_source.Save(obj); _source.Save(obj);
obs_data_set_string(obj, "settings", _settings.c_str()); obs_data_set_string(obj, "settings", _settings.c_str());
obs_data_set_bool(obj, "regex", _regex); _regex.Save(obj);
return true; return true;
} }
@@ -50,7 +50,12 @@ bool MacroConditionSceneTransform::Load(obs_data_t *obj)
_scene.Load(obj); _scene.Load(obj);
_source.Load(obj); _source.Load(obj);
_settings = obs_data_get_string(obj, "settings"); _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; return true;
} }
@@ -66,17 +71,16 @@ std::string MacroConditionSceneTransform::GetShortDesc()
MacroConditionSceneTransformEdit::MacroConditionSceneTransformEdit( MacroConditionSceneTransformEdit::MacroConditionSceneTransformEdit(
QWidget *parent, QWidget *parent,
std::shared_ptr<MacroConditionSceneTransform> entryData) 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 &)), QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &))); this, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)), QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
@@ -88,8 +92,8 @@ MacroConditionSceneTransformEdit::MacroConditionSceneTransformEdit(
SLOT(GetSettingsClicked())); SLOT(GetSettingsClicked()));
QWidget::connect(_settings, SIGNAL(textChanged()), this, QWidget::connect(_settings, SIGNAL(textChanged()), this,
SLOT(SettingsChanged())); SLOT(SettingsChanged()));
QWidget::connect(_regex, SIGNAL(stateChanged(int)), this, QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
SLOT(RegexChanged(int))); SLOT(RegexChanged(RegexConfig)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = { std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes}, {"{{sources}}", _sources}, {"{{scenes}}", _scenes}, {"{{sources}}", _sources},
@@ -132,7 +136,7 @@ void MacroConditionSceneTransformEdit::UpdateEntryData()
_scenes->SetScene(_entryData->_scene); _scenes->SetScene(_entryData->_scene);
_sources->SetSceneItem(_entryData->_source); _sources->SetSceneItem(_entryData->_source);
_regex->setChecked(_entryData->_regex); _regex->SetRegexConfig(_entryData->_regex);
_settings->setPlainText(QString::fromStdString(_entryData->_settings)); _settings->setPlainText(QString::fromStdString(_entryData->_settings));
adjustSize(); adjustSize();
@@ -175,7 +179,7 @@ void MacroConditionSceneTransformEdit::GetSettingsClicked()
} }
auto settings = formatJsonString(getSceneItemTransform(items[0])); auto settings = formatJsonString(getSceneItemTransform(items[0]));
if (_entryData->_regex) { if (_entryData->_regex.Enabled()) {
settings = escapeForRegex(settings); settings = escapeForRegex(settings);
} }
_settings->setPlainText(settings); _settings->setPlainText(settings);
@@ -198,12 +202,15 @@ void MacroConditionSceneTransformEdit::SettingsChanged()
updateGeometry(); updateGeometry();
} }
void MacroConditionSceneTransformEdit::RegexChanged(int state) void MacroConditionSceneTransformEdit::RegexChanged(RegexConfig conf)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
return; return;
} }
std::lock_guard<std::mutex> lock(switcher->m); 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-selection.hpp"
#include "scene-item-selection.hpp" #include "scene-item-selection.hpp"
#include "resizing-text-edit.hpp" #include "resizing-text-edit.hpp"
#include "regex-config.hpp"
#include <QSpinBox> #include <QSpinBox>
#include <QCheckBox> #include <QCheckBox>
@@ -22,7 +23,7 @@ public:
SceneSelection _scene; SceneSelection _scene;
SceneItemSelection _source; SceneItemSelection _source;
bool _regex = false; RegexConfig _regex;
std::string _settings = ""; std::string _settings = "";
private: private:
@@ -53,7 +54,7 @@ private slots:
void SourceChanged(const SceneItemSelection &); void SourceChanged(const SceneItemSelection &);
void GetSettingsClicked(); void GetSettingsClicked();
void SettingsChanged(); void SettingsChanged();
void RegexChanged(int); void RegexChanged(RegexConfig);
signals: signals:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
@@ -62,7 +63,7 @@ protected:
SceneItemSelectionWidget *_sources; SceneItemSelectionWidget *_sources;
QPushButton *_getSettings; QPushButton *_getSettings;
ResizingPlainTextEdit *_settings; ResizingPlainTextEdit *_settings;
QCheckBox *_regex; RegexConfigWidget *_regex;
std::shared_ptr<MacroConditionSceneTransform> _entryData; std::shared_ptr<MacroConditionSceneTransform> _entryData;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -54,7 +54,7 @@ bool MacroConditionTransition::CheckCondition()
bool transitionStarted = false; bool transitionStarted = false;
bool transitionEnded = false; bool transitionEnded = false;
if (_transition.GetType() == TransitionSelectionType::ANY) { if (_transition.GetType() == TransitionSelection::Type::ANY) {
transitionStarted = anyTransitionStarted; transitionStarted = anyTransitionStarted;
transitionEnded = anyTransitionEnded; transitionEnded = anyTransitionEnded;
} else { } else {
@@ -183,7 +183,7 @@ MacroConditionTransitionEdit::MacroConditionTransitionEdit(
{ {
_conditions = new QComboBox(); _conditions = new QComboBox();
_transitions = new TransitionSelectionWidget(this, true, true); _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); _duration = new DurationSelection(this, false);
_durationSuffix = new QLabel(obs_module_text( _durationSuffix = new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.transition.durationSuffix")); "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 MacroConditionVCam::CheckCondition()
{ {
bool stateMatch = false; bool stateMatch = false;
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
switch (_state) { switch (_state) {
case VCamState::STOP: case VCamState::STOP:
stateMatch = !obs_frontend_virtualcam_active(); stateMatch = !obs_frontend_virtualcam_active();
@@ -30,6 +31,7 @@ bool MacroConditionVCam::CheckCondition()
default: default:
break; break;
} }
#endif
return stateMatch; return stateMatch;
} }

View File

@@ -12,21 +12,50 @@ bool MacroConditionWebsocket::_registered = MacroConditionFactory::Register(
{MacroConditionWebsocket::Create, MacroConditionWebsocketEdit::Create, {MacroConditionWebsocket::Create, MacroConditionWebsocketEdit::Create,
"AdvSceneSwitcher.condition.websocket"}); "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 { auto regex = conf.GetRegularExpression(expr);
std::regex regExpr(expr); if (!regex.isValid()) {
return std::regex_match(msg, regExpr); return false;
} catch (const std::regex_error &) {
} }
return false; auto match = regex.match(QString::fromStdString(msg));
return match.hasMatch();
} }
bool MacroConditionWebsocket::CheckCondition() bool MacroConditionWebsocket::CheckCondition()
{ {
for (const auto &msg : switcher->websocketMessages) { const std::vector<std::string> *messages = nullptr;
if (_useRegex) { switch (_type) {
if (matchRegex(msg, _message)) { 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; return true;
} }
} else { } else {
@@ -41,42 +70,72 @@ bool MacroConditionWebsocket::CheckCondition()
bool MacroConditionWebsocket::Save(obs_data_t *obj) bool MacroConditionWebsocket::Save(obs_data_t *obj)
{ {
MacroCondition::Save(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_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; return true;
} }
bool MacroConditionWebsocket::Load(obs_data_t *obj) bool MacroConditionWebsocket::Load(obs_data_t *obj)
{ {
MacroCondition::Load(obj); MacroCondition::Load(obj);
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
_message = obs_data_get_string(obj, "message"); _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; return true;
} }
std::string MacroConditionWebsocket::GetShortDesc() 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( MacroConditionWebsocketEdit::MacroConditionWebsocketEdit(
QWidget *parent, std::shared_ptr<MacroConditionWebsocket> entryData) QWidget *parent, std::shared_ptr<MacroConditionWebsocket> entryData)
: QWidget(parent), : QWidget(parent),
_conditions(new QComboBox(this)),
_message(new ResizingPlainTextEdit(this)), _message(new ResizingPlainTextEdit(this)),
_useRegex(new QCheckBox(obs_module_text( _regex(new RegexConfigWidget(parent)),
"AdvSceneSwitcher.condition.websocket.useRegex"))) _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, QWidget::connect(_message, SIGNAL(textChanged()), this,
SLOT(MessageChanged())); SLOT(MessageChanged()));
QWidget::connect(_useRegex, SIGNAL(stateChanged(int)), this, QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
SLOT(UseRegexChanged(int))); SLOT(RegexChanged(RegexConfig)));
QWidget::connect(_connection, SIGNAL(SelectionChanged(const QString &)),
this,
SLOT(ConnectionSelectionChanged(const QString &)));
QVBoxLayout *mainLayout = new QVBoxLayout; QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(_editLayout);
mainLayout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.condition.websocket.entry")));
mainLayout->addWidget(_message); 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); setLayout(mainLayout);
_entryData = entryData; _entryData = entryData;
@@ -84,19 +143,76 @@ MacroConditionWebsocketEdit::MacroConditionWebsocketEdit(
_loading = false; _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() void MacroConditionWebsocketEdit::UpdateEntryData()
{ {
if (!_entryData) { if (!_entryData) {
return; return;
} }
_conditions->setCurrentIndex(static_cast<int>(_entryData->_type));
_message->setPlainText(QString::fromStdString(_entryData->_message)); _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(); adjustSize();
updateGeometry(); 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() void MacroConditionWebsocketEdit::MessageChanged()
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
@@ -110,12 +226,27 @@ void MacroConditionWebsocketEdit::MessageChanged()
updateGeometry(); updateGeometry();
} }
void MacroConditionWebsocketEdit::UseRegexChanged(int state) void MacroConditionWebsocketEdit::ConnectionSelectionChanged(
const QString &connection)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
return; return;
} }
std::lock_guard<std::mutex> lock(switcher->m); 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 #pragma once
#include "macro.hpp" #include "macro.hpp"
#include "connection-manager.hpp"
#include "resizing-text-edit.hpp" #include "resizing-text-edit.hpp"
#include "regex-config.hpp"
#include <QCheckBox> #include <QCheckBox>
@@ -17,8 +19,15 @@ public:
return std::make_shared<MacroConditionWebsocket>(m); return std::make_shared<MacroConditionWebsocket>(m);
} }
enum class Type {
REQUEST,
EVENT,
};
Type _type = Type::REQUEST;
std::string _message = obs_module_text("AdvSceneSwitcher.enterText"); std::string _message = obs_module_text("AdvSceneSwitcher.enterText");
bool _useRegex = false; RegexConfig _regex;
std::string _connection;
private: private:
static bool _registered; static bool _registered;
@@ -43,16 +52,24 @@ public:
} }
private slots: private slots:
void ConditionChanged(int);
void MessageChanged(); void MessageChanged();
void UseRegexChanged(int state); void RegexChanged(RegexConfig);
void ConnectionSelectionChanged(const QString &);
signals: signals:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
protected: protected:
ResizingPlainTextEdit *_message;
QCheckBox *_useRegex;
std::shared_ptr<MacroConditionWebsocket> _entryData; std::shared_ptr<MacroConditionWebsocket> _entryData;
private: private:
void SetupRequestEdit();
void SetupEventEdit();
QComboBox *_conditions;
ResizingPlainTextEdit *_message;
RegexConfigWidget *_regex;
ConnectionSelection *_connection;
QHBoxLayout *_editLayout;
bool _loading = true; 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, "highlightExecuted", _highlightExecuted);
obs_data_set_bool(data, "highlightConditions", _highlightConditions); obs_data_set_bool(data, "highlightConditions", _highlightConditions);
obs_data_set_bool(data, "highlightActions", _highlightActions); obs_data_set_bool(data, "highlightActions", _highlightActions);
obs_data_set_bool(data, "newMacroRegisterHotkey",
_newMacroRegisterHotkeys);
obs_data_set_obj(obj, "macroProperties", data); obs_data_set_obj(obj, "macroProperties", data);
obs_data_release(data); obs_data_release(data);
} }
@@ -17,16 +19,11 @@ void MacroProperties::Save(obs_data_t *obj)
void MacroProperties::Load(obs_data_t *obj) void MacroProperties::Load(obs_data_t *obj)
{ {
auto data = obs_data_get_obj(obj, "macroProperties"); auto data = obs_data_get_obj(obj, "macroProperties");
// TODO: Remove in future version _highlightExecuted = obs_data_get_bool(data, "highlightExecuted");
if (obs_data_has_user_value(obj, "highlightExecutedMacros")) {
_highlightExecuted =
obs_data_get_bool(obj, "highlightExecutedMacros");
} else {
_highlightExecuted =
obs_data_get_bool(data, "highlightExecuted");
}
_highlightConditions = obs_data_get_bool(data, "highlightConditions"); _highlightConditions = obs_data_get_bool(data, "highlightConditions");
_highlightActions = obs_data_get_bool(data, "highlightActions"); _highlightActions = obs_data_get_bool(data, "highlightActions");
_newMacroRegisterHotkeys =
obs_data_get_bool(data, "newMacroRegisterHotkey");
obs_data_release(data); obs_data_release(data);
} }
@@ -40,8 +37,10 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
"AdvSceneSwitcher.macroTab.highlightTrueConditions"))), "AdvSceneSwitcher.macroTab.highlightTrueConditions"))),
_actions(new QCheckBox(obs_module_text( _actions(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.highlightPerformedActions"))), "AdvSceneSwitcher.macroTab.highlightPerformedActions"))),
_hotkeys(new QCheckBox( _newMacroRegisterHotkeys(new QCheckBox(obs_module_text(
obs_module_text("AdvSceneSwitcher.macroTab.disableHotkeys"))) "AdvSceneSwitcher.macroTab.newMacroRegisterHotkey"))),
_currentMacroRegisterHotkeys(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.currentDisableHotkeys")))
{ {
setModal(true); setModal(true);
setWindowModality(Qt::WindowModality::WindowModal); setWindowModality(Qt::WindowModality::WindowModal);
@@ -52,17 +51,26 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
_executed->setChecked(prop._highlightExecuted); _executed->setChecked(prop._highlightExecuted);
_conditions->setChecked(prop._highlightConditions); _conditions->setChecked(prop._highlightConditions);
_actions->setChecked(prop._highlightActions); _actions->setChecked(prop._highlightActions);
_newMacroRegisterHotkeys->setChecked(prop._newMacroRegisterHotkeys);
if (macro) { if (macro) {
_hotkeys->setChecked(macro->PauseHotkeysEnabled()); _currentMacroRegisterHotkeys->setChecked(
macro->PauseHotkeysEnabled());
} else { } else {
_hotkeys->hide(); _currentMacroRegisterHotkeys->hide();
} }
QVBoxLayout *layout = new QVBoxLayout; QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(_executed); layout->addWidget(_executed);
layout->addWidget(_conditions); layout->addWidget(_conditions);
layout->addWidget(_actions); 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); setLayout(layout);
QDialogButtonBox *buttonbox = new QDialogButtonBox( QDialogButtonBox *buttonbox = new QDialogButtonBox(
@@ -85,8 +93,11 @@ bool MacroPropertiesDialog::AskForSettings(QWidget *parent,
userInput._highlightExecuted = dialog._executed->isChecked(); userInput._highlightExecuted = dialog._executed->isChecked();
userInput._highlightConditions = dialog._conditions->isChecked(); userInput._highlightConditions = dialog._conditions->isChecked();
userInput._highlightActions = dialog._actions->isChecked(); userInput._highlightActions = dialog._actions->isChecked();
userInput._newMacroRegisterHotkeys =
dialog._newMacroRegisterHotkeys->isChecked();
if (macro) { if (macro) {
macro->EnablePauseHotkeys(dialog._hotkeys->isChecked()); macro->EnablePauseHotkeys(
dialog._currentMacroRegisterHotkeys->isChecked());
} }
return true; return true;
} }

View File

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

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <string> #include <string>
#include <vector>
#include <QString> #include <QString>
#include <obs-module.h> #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); std::lock_guard<std::mutex> lock(switcher->m);
switcher->macros.emplace_back( switcher->macros.emplace_back(std::make_shared<Macro>(
std::make_shared<Macro>(name, true)); name,
switcher->macroProperties._newMacroRegisterHotkeys));
} }
return true; return true;
} }

View File

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

View File

@@ -82,6 +82,10 @@ bool MacroConditionVideo::CheckCondition()
return _lastMatchResult; return _lastMatchResult;
} }
if (_blockUntilScreenshotDone) {
GetScreenshot(true);
}
if (_screenshotData.done) { if (_screenshotData.done) {
match = Compare(); match = Compare();
_lastMatchResult = match; _lastMatchResult = match;
@@ -94,7 +98,7 @@ bool MacroConditionVideo::CheckCondition()
match = _lastMatchResult; match = _lastMatchResult;
} }
if (_getNextScreenshot) { if (!_blockUntilScreenshotDone && _getNextScreenshot) {
GetScreenshot(); GetScreenshot();
} }
return match; return match;
@@ -106,6 +110,8 @@ bool MacroConditionVideo::Save(obs_data_t *obj)
_video.Save(obj); _video.Save(obj);
obs_data_set_int(obj, "condition", static_cast<int>(_condition)); obs_data_set_int(obj, "condition", static_cast<int>(_condition));
obs_data_set_string(obj, "filePath", _file.c_str()); obs_data_set_string(obj, "filePath", _file.c_str());
obs_data_set_bool(obj, "blockUntilScreenshotDone",
_blockUntilScreenshotDone);
obs_data_set_bool(obj, "usePatternForChangedCheck", obs_data_set_bool(obj, "usePatternForChangedCheck",
_usePatternForChangedCheck); _usePatternForChangedCheck);
obs_data_set_double(obj, "threshold", _patternThreshold); obs_data_set_double(obj, "threshold", _patternThreshold);
@@ -143,6 +149,8 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
_condition = _condition =
static_cast<VideoCondition>(obs_data_get_int(obj, "condition")); static_cast<VideoCondition>(obs_data_get_int(obj, "condition"));
_file = obs_data_get_string(obj, "filePath"); _file = obs_data_get_string(obj, "filePath");
_blockUntilScreenshotDone =
obs_data_get_bool(obj, "blockUntilScreenshotDone");
_usePatternForChangedCheck = _usePatternForChangedCheck =
obs_data_get_bool(obj, "usePatternForChangedCheck"); obs_data_get_bool(obj, "usePatternForChangedCheck");
_patternThreshold = obs_data_get_double(obj, "threshold"); _patternThreshold = obs_data_get_double(obj, "threshold");
@@ -186,11 +194,12 @@ std::string MacroConditionVideo::GetShortDesc()
return _video.ToString(); return _video.ToString();
} }
void MacroConditionVideo::GetScreenshot() void MacroConditionVideo::GetScreenshot(bool blocking)
{ {
auto source = obs_weak_source_get_source(_video.GetVideo()); auto source = obs_weak_source_get_source(_video.GetVideo());
_screenshotData.~ScreenshotHelper(); _screenshotData.~ScreenshotHelper();
new (&_screenshotData) ScreenshotHelper(source); new (&_screenshotData)
ScreenshotHelper(source, blocking, GetSwitcher()->interval);
obs_source_release(source); obs_source_release(source);
_getNextScreenshot = false; _getNextScreenshot = false;
} }
@@ -200,6 +209,8 @@ bool MacroConditionVideo::LoadImageFromFile()
if (!_matchImage.load(QString::fromStdString(_file))) { if (!_matchImage.load(QString::fromStdString(_file))) {
blog(LOG_WARNING, "Cannot load image data from file '%s'", blog(LOG_WARNING, "Cannot load image data from file '%s'",
_file.c_str()); _file.c_str());
(&_matchImage)->~QImage();
new (&_matchImage) QImage();
return false; return false;
} }
@@ -285,6 +296,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
: QWidget(parent), : QWidget(parent),
_videoSelection(new VideoSelectionWidget(this)), _videoSelection(new VideoSelectionWidget(this)),
_condition(new QComboBox()), _condition(new QComboBox()),
_reduceLatency(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.reduceLatency"))),
_usePatternForChangedCheck(new QCheckBox(obs_module_text( _usePatternForChangedCheck(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck"))), "AdvSceneSwitcher.condition.video.usePatternForChangedCheck"))),
_imagePath(new FileSelection()), _imagePath(new FileSelection()),
@@ -312,7 +325,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
_minSize(new SizeSelection(0, 1024)), _minSize(new SizeSelection(0, 1024)),
_maxSize(new SizeSelection(0, 4096)), _maxSize(new SizeSelection(0, 4096)),
_checkAreaControlLayout(new QHBoxLayout), _checkAreaControlLayout(new QHBoxLayout),
_checkAreaEnable(new QCheckBox()), _checkAreaEnable(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.entry.checkAreaEnable"))),
_checkArea(new AreaSelection(0, 99999)), _checkArea(new AreaSelection(0, 99999)),
_selectArea(new QPushButton(obs_module_text( _selectArea(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.video.selectArea"))), "AdvSceneSwitcher.condition.video.selectArea"))),
@@ -323,6 +337,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
"AdvSceneSwitcher.condition.video.showMatch"))), "AdvSceneSwitcher.condition.video.showMatch"))),
_previewDialog(this, entryData.get(), &GetSwitcher()->m) _previewDialog(this, entryData.get(), &GetSwitcher()->m)
{ {
_reduceLatency->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.reduceLatency.tooltip"));
_imagePath->Button()->disconnect(); _imagePath->Button()->disconnect();
_usePatternForChangedCheck->setToolTip(obs_module_text( _usePatternForChangedCheck->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip")); "AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip"));
@@ -338,6 +354,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
SLOT(VideoSelectionChanged(const VideoSelection &))); SLOT(VideoSelectionChanged(const VideoSelection &)));
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int))); SLOT(ConditionChanged(int)));
QWidget::connect(_reduceLatency, SIGNAL(stateChanged(int)), this,
SLOT(ReduceLatencyChanged(int)));
QWidget::connect(_imagePath, SIGNAL(PathChanged(const QString &)), this, QWidget::connect(_imagePath, SIGNAL(PathChanged(const QString &)), this,
SLOT(ImagePathChanged(const QString &))); SLOT(ImagePathChanged(const QString &)));
QWidget::connect(_imagePath->Button(), SIGNAL(clicked()), this, QWidget::connect(_imagePath->Button(), SIGNAL(clicked()), this,
@@ -380,6 +398,7 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
std::unordered_map<std::string, QWidget *> widgetPlaceholders = { std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{videoSources}}", _videoSelection}, {"{{videoSources}}", _videoSelection},
{"{{condition}}", _condition}, {"{{condition}}", _condition},
{"{{reduceLatency}}", _reduceLatency},
{"{{imagePath}}", _imagePath}, {"{{imagePath}}", _imagePath},
{"{{minNeighbors}}", _minNeighbors}, {"{{minNeighbors}}", _minNeighbors},
{"{{minSize}}", _minSize}, {"{{minSize}}", _minSize},
@@ -437,9 +456,10 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
mainLayout->addLayout(_neighborsControlLayout); mainLayout->addLayout(_neighborsControlLayout);
mainLayout->addWidget(_minNeighborsDescription); mainLayout->addWidget(_minNeighborsDescription);
mainLayout->addLayout(_sizeLayout); mainLayout->addLayout(_sizeLayout);
mainLayout->addLayout(showMatchLayout);
mainLayout->addLayout(_throttleControlLayout); mainLayout->addLayout(_throttleControlLayout);
mainLayout->addLayout(_checkAreaControlLayout); mainLayout->addLayout(_checkAreaControlLayout);
mainLayout->addWidget(_reduceLatency);
mainLayout->addLayout(showMatchLayout);
setLayout(mainLayout); setLayout(mainLayout);
_entryData = entryData; _entryData = entryData;
@@ -632,6 +652,16 @@ void MacroConditionVideoEdit::PatternThresholdChanged(double value)
_entryData->_patternThreshold = 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) void MacroConditionVideoEdit::UseAlphaAsMaskChanged(int value)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
@@ -693,6 +723,9 @@ void MacroConditionVideoEdit::CheckAreaEnableChanged(int value)
_entryData->_checkAreaEnable = value; _entryData->_checkAreaEnable = value;
_checkArea->setEnabled(value); _checkArea->setEnabled(value);
_selectArea->setEnabled(value); _selectArea->setEnabled(value);
_checkArea->setVisible(value);
_selectArea->setVisible(value);
adjustSize();
} }
void MacroConditionVideoEdit::CheckAreaChanged(advss::Area value) void MacroConditionVideoEdit::CheckAreaChanged(advss::Area value)
@@ -825,6 +858,8 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
needsThrottleControls(_entryData->_condition)); needsThrottleControls(_entryData->_condition));
setLayoutVisible(_checkAreaControlLayout, setLayoutVisible(_checkAreaControlLayout,
needsAreaControls(_entryData->_condition)); needsAreaControls(_entryData->_condition));
_checkArea->setVisible(_entryData->_checkAreaEnable);
_selectArea->setVisible(_entryData->_checkAreaEnable);
if (_entryData->_condition == VideoCondition::HAS_CHANGED || if (_entryData->_condition == VideoCondition::HAS_CHANGED ||
_entryData->_condition == VideoCondition::HAS_NOT_CHANGED) { _entryData->_condition == VideoCondition::HAS_NOT_CHANGED) {
@@ -843,6 +878,7 @@ void MacroConditionVideoEdit::UpdateEntryData()
_videoSelection->SetVideoSelection(_entryData->_video); _videoSelection->SetVideoSelection(_entryData->_video);
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition)); _condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
_reduceLatency->setChecked(_entryData->_blockUntilScreenshotDone);
_imagePath->SetPath(QString::fromStdString(_entryData->_file)); _imagePath->SetPath(QString::fromStdString(_entryData->_file));
_usePatternForChangedCheck->setChecked( _usePatternForChangedCheck->setChecked(
_entryData->_usePatternForChangedCheck); _entryData->_usePatternForChangedCheck);

View File

@@ -40,7 +40,7 @@ public:
{ {
return std::make_shared<MacroConditionVideo>(m); return std::make_shared<MacroConditionVideo>(m);
} }
void GetScreenshot(); void GetScreenshot(bool blocking = false);
bool LoadImageFromFile(); bool LoadImageFromFile();
bool LoadModelData(std::string &path); bool LoadModelData(std::string &path);
std::string GetModelDataPath() { return _modelDataPath; } std::string GetModelDataPath() { return _modelDataPath; }
@@ -49,6 +49,13 @@ public:
VideoSelection _video; VideoSelection _video;
VideoCondition _condition = VideoCondition::MATCH; VideoCondition _condition = VideoCondition::MATCH;
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath"); 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 _useAlphaAsMask = false;
bool _usePatternForChangedCheck = false; bool _usePatternForChangedCheck = false;
PatternMatchData _patternData; PatternMatchData _patternData;
@@ -107,8 +114,11 @@ public:
private slots: private slots:
void VideoSelectionChanged(const VideoSelection &); void VideoSelectionChanged(const VideoSelection &);
void ConditionChanged(int cond); void ConditionChanged(int cond);
void ReduceLatencyChanged(int value);
void ImagePathChanged(const QString &text); void ImagePathChanged(const QString &text);
void ImageBrowseButtonClicked(); void ImageBrowseButtonClicked();
void UsePatternForChangedCheckChanged(int value); void UsePatternForChangedCheckChanged(int value);
void PatternThresholdChanged(double); void PatternThresholdChanged(double);
void UseAlphaAsMaskChanged(int value); void UseAlphaAsMaskChanged(int value);
@@ -134,8 +144,11 @@ protected:
VideoSelectionWidget *_videoSelection; VideoSelectionWidget *_videoSelection;
QComboBox *_condition; QComboBox *_condition;
QCheckBox *_reduceLatency;
QCheckBox *_usePatternForChangedCheck; QCheckBox *_usePatternForChangedCheck;
FileSelection *_imagePath; FileSelection *_imagePath;
ThresholdSlider *_patternThreshold; ThresholdSlider *_patternThreshold;
QCheckBox *_useAlphaAsMask; QCheckBox *_useAlphaAsMask;

View File

@@ -2,7 +2,7 @@
PatternMatchData createPatternData(QImage &pattern) PatternMatchData createPatternData(QImage &pattern)
{ {
PatternMatchData data; PatternMatchData data{};
if (pattern.isNull()) { if (pattern.isNull()) {
return data; return data;
} }

View File

@@ -72,6 +72,7 @@ typedef struct transitionData {
} transitionData; } transitionData;
class Item;
class SwitcherThread; class SwitcherThread;
/******************************************************************************** /********************************************************************************
@@ -138,9 +139,11 @@ struct SwitcherData {
Curlhelper curl; Curlhelper curl;
std::deque<Connection> connections; std::deque<std::shared_ptr<Item>> connections;
std::vector<std::string> websocketMessages; std::vector<std::string> websocketMessages;
std::deque<std::shared_ptr<Item>> variables;
std::deque<WindowSwitch> windowSwitches; std::deque<WindowSwitch> windowSwitches;
std::vector<std::string> ignoreIdleWindows; std::vector<std::string> ignoreIdleWindows;
std::string lastTitle; std::string lastTitle;
@@ -291,6 +294,7 @@ struct SwitcherData {
void saveSettings(obs_data_t *obj); void saveSettings(obs_data_t *obj);
void saveMacros(obs_data_t *obj); void saveMacros(obs_data_t *obj);
void saveConnections(obs_data_t *obj); void saveConnections(obs_data_t *obj);
void saveVariables(obs_data_t *obj);
void saveWindowTitleSwitches(obs_data_t *obj); void saveWindowTitleSwitches(obs_data_t *obj);
void saveScreenRegionSwitches(obs_data_t *obj); void saveScreenRegionSwitches(obs_data_t *obj);
void savePauseSwitches(obs_data_t *obj); void savePauseSwitches(obs_data_t *obj);
@@ -315,6 +319,7 @@ struct SwitcherData {
void loadSettings(obs_data_t *obj); void loadSettings(obs_data_t *obj);
void loadMacros(obs_data_t *obj); void loadMacros(obs_data_t *obj);
void loadConnections(obs_data_t *obj); void loadConnections(obs_data_t *obj);
void loadVariables(obs_data_t *obj);
void loadWindowTitleSwitches(obs_data_t *obj); void loadWindowTitleSwitches(obs_data_t *obj);
void loadScreenRegionSwitches(obs_data_t *obj); void loadScreenRegionSwitches(obs_data_t *obj);
void loadPauseSwitches(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) void SwitcherData::saveConnections(obs_data_t *obj)
{ {
obs_data_array_t *connectionArray = obs_data_array_create(); 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(); 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_array_push_back(connectionArray, array_obj);
obs_data_release(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++) { for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(connectionArray, i); obs_data_t *array_obj = obs_data_array_item(connectionArray, i);
connections.emplace_back(); auto con = Connection::Create();
connections.back().Load(array_obj); connections.emplace_back(con);
connections.back()->Load(array_obj);
obs_data_release(array_obj); obs_data_release(array_obj);
} }
obs_data_array_release(connectionArray); 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, Connection::Connection(std::string name, std::string address, uint64_t port,
std::string pass, bool connectOnStart, bool reconnect, std::string pass, bool connectOnStart, bool reconnect,
int reconnectDelay) int reconnectDelay)
: _name(name), : Item(name),
_address(address), _address(address),
_port(port), _port(port),
_password(pass), _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; _name = other._name;
_address = other._address; _address = other._address;
@@ -125,8 +126,8 @@ Connection *GetConnectionByName(const QString &name)
Connection *GetConnectionByName(const std::string &name) Connection *GetConnectionByName(const std::string &name)
{ {
for (auto &con : switcher->connections) { for (auto &con : switcher->connections) {
if (con.GetName() == name) { if (con->Name() == name) {
return &con; return dynamic_cast<Connection *>(con.get());
} }
} }
return nullptr; return nullptr;
@@ -142,58 +143,38 @@ bool ConnectionNameAvailable(const std::string &name)
return ConnectionNameAvailable(QString::fromStdString(name)); return ConnectionNameAvailable(QString::fromStdString(name));
} }
ConnectionSelection::ConnectionSelection(QWidget *parent) static bool AskForSettingsWrapper(QWidget *parent, Item &settings)
: QWidget(parent), _selection(new QComboBox), _modify(new QPushButton)
{ {
_modify->setMaximumSize(22, 22); Connection &ConnectionSettings = dynamic_cast<Connection &>(settings);
QIcon icon; return ConnectionSettingsDialog::AskForSettings(parent,
icon.addFile( ConnectionSettings);
QString::fromUtf8(":/settings/images/settings/general.svg"), }
QSize(), QIcon::Normal, QIcon::Off);
_modify->setIcon(icon);
_modify->setFlat(true);
ConnectionSelection::ConnectionSelection(QWidget *parent)
: ItemSelection(switcher->connections, Connection::Create,
AskForSettingsWrapper,
"AdvSceneSwitcher.connection.select",
"AdvSceneSwitcher.connection.add", parent)
{
// Connect to slots // 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( QWidget::connect(
window(), window(),
SIGNAL(ConnectionRenamed(const QString &, const QString &)), 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 &)), QWidget::connect(window(), SIGNAL(ConnectionAdded(const QString &)),
this, SLOT(AddConnection(const QString &))); this, SLOT(AddItem(const QString &)));
QWidget::connect(window(), SIGNAL(ConnectionRemoved(const QString &)), QWidget::connect(window(), SIGNAL(ConnectionRemoved(const QString &)),
this, SLOT(RemoveConnection(const QString &))); this, SLOT(RemoveItem(const QString &)));
// Forward signals // Forward signals
QWidget::connect( QWidget::connect(
this, this, SIGNAL(ItemRenamed(const QString &, const QString &)),
SIGNAL(ConnectionRenamed(const QString &, const QString &)),
window(), window(),
SIGNAL(ConnectionRenamed(const QString &, const QString &))); SIGNAL(ConnectionRenamed(const QString &, const QString &)));
QWidget::connect(this, SIGNAL(ConnectionAdded(const QString &)), QWidget::connect(this, SIGNAL(ItemAdded(const QString &)), window(),
window(), SIGNAL(ConnectionAdded(const QString &))); SIGNAL(ConnectionAdded(const QString &)));
QWidget::connect(this, SIGNAL(ConnectionRemoved(const QString &)), QWidget::connect(this, SIGNAL(ItemRemoved(const QString &)), window(),
window(), SIGNAL(ConnectionRemoved(const QString &))); 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"));
} }
void ConnectionSelection::SetConnection(const std::string &con) 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, ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
const Connection &settings) const Connection &settings)
: QDialog(parent), : ItemSettingsDialog(settings, switcher->connections,
_name(new QLineEdit()), "AdvSceneSwitcher.connection.select",
_nameHint(new QLabel), "AdvSceneSwitcher.connection.add", parent),
_address(new QLineEdit()), _address(new QLineEdit()),
_port(new QSpinBox()), _port(new QSpinBox()),
_password(new QLineEdit()), _password(new QLineEdit()),
_showPassword(new QPushButton()),
_connectOnStart(new QCheckBox()), _connectOnStart(new QCheckBox()),
_reconnect(new QCheckBox()), _reconnect(new QCheckBox()),
_reconnectDelay(new QSpinBox()), _reconnectDelay(new QSpinBox()),
_test(new QPushButton( _test(new QPushButton(
obs_module_text("AdvSceneSwitcher.connection.test"))), obs_module_text("AdvSceneSwitcher.connection.test"))),
_status(new QLabel()), _status(new QLabel())
_buttonbox(new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel))
{ {
setModal(true);
setWindowModality(Qt::WindowModality::WindowModal);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setFixedWidth(555);
setMinimumHeight(100);
_port->setMaximum(65535); _port->setMaximum(65535);
_showPassword->setMaximumWidth(22);
_showPassword->setFlat(true);
_showPassword->setStyleSheet(
"QPushButton { background-color: transparent; border: 0px }");
_reconnectDelay->setMaximum(9999); _reconnectDelay->setMaximum(9999);
_reconnectDelay->setSuffix("s"); _reconnectDelay->setSuffix("s");
_buttonbox->setCenterButtons(true);
_buttonbox->button(QDialogButtonBox::Ok)->setDisabled(true);
_name->setText(QString::fromStdString(settings._name));
_address->setText(QString::fromStdString(settings._address)); _address->setText(QString::fromStdString(settings._address));
_port->setValue(settings._port); _port->setValue(settings._port);
_password->setText(QString::fromStdString(settings._password)); _password->setText(QString::fromStdString(settings._password));
@@ -398,18 +218,15 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
_reconnect->setChecked(settings._reconnect); _reconnect->setChecked(settings._reconnect);
_reconnectDelay->setValue(settings._reconnectDelay); _reconnectDelay->setValue(settings._reconnectDelay);
QWidget::connect(_name, SIGNAL(textEdited(const QString &)), this,
SLOT(NameChanged(const QString &)));
QWidget::connect(_reconnect, SIGNAL(stateChanged(int)), this, QWidget::connect(_reconnect, SIGNAL(stateChanged(int)), this,
SLOT(ReconnectChanged(int))); 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, QWidget::connect(_test, SIGNAL(clicked()), this,
SLOT(TestConnection())); SLOT(TestConnection()));
QWidget::connect(_buttonbox, &QDialogButtonBox::accepted, this,
&QDialog::accept);
QWidget::connect(_buttonbox, &QDialogButtonBox::rejected, this,
&QDialog::reject);
QGridLayout *layout = new QGridLayout; QGridLayout *layout = new QGridLayout;
int row = 0; int row = 0;
layout->addWidget( layout->addWidget(
@@ -433,7 +250,10 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
layout->addWidget(new QLabel(obs_module_text( layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.connection.password")), "AdvSceneSwitcher.connection.password")),
row, 0); row, 0);
layout->addWidget(_password, row, 1); QHBoxLayout *passLayout = new QHBoxLayout;
passLayout->addWidget(_password);
passLayout->addWidget(_showPassword);
layout->addLayout(passLayout, row, 1);
++row; ++row;
layout->addWidget( layout->addWidget(
new QLabel(obs_module_text( new QLabel(obs_module_text(
@@ -458,30 +278,8 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
layout->addWidget(_buttonbox, row, 0, 1, -1); layout->addWidget(_buttonbox, row, 0, 1, -1);
setLayout(layout); setLayout(layout);
NameChanged(_name->text());
ReconnectChanged(_reconnect->isChecked()); ReconnectChanged(_reconnect->isChecked());
} HidePassword();
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("");
} }
void ConnectionSettingsDialog::ReconnectChanged(int state) 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() void ConnectionSettingsDialog::TestConnection()
{ {
_testConnection.Disconnect(); _testConnection.Disconnect();
@@ -545,21 +355,9 @@ bool ConnectionSettingsDialog::AskForSettings(QWidget *parent,
return true; 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) void Connection::Load(obs_data_t *obj)
{ {
_name = obs_data_get_string(obj, "name"); Item::Load(obj);
_address = obs_data_get_string(obj, "address"); _address = obs_data_get_string(obj, "address");
_port = obs_data_get_int(obj, "port"); _port = obs_data_get_int(obj, "port");
_password = obs_data_get_string(obj, "password"); _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_string(obj, "address", _address.c_str());
obs_data_set_int(obj, "port", _port); obs_data_set_int(obj, "port", _port);
obs_data_set_string(obj, "password", _password.c_str()); obs_data_set_string(obj, "password", _password.c_str());

View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "item-selection-helpers.hpp"
#include <QComboBox> #include <QComboBox>
#include <QPushButton> #include <QPushButton>
@@ -7,7 +8,6 @@
#include <QCheckBox> #include <QCheckBox>
#include <QSpinBox> #include <QSpinBox>
#include <QLabel> #include <QLabel>
#include <QDialogButtonBox>
#include <QSpinBox> #include <QSpinBox>
#include <QTimer> #include <QTimer>
#include <QWidget> #include <QWidget>
@@ -18,7 +18,7 @@
class ConnectionSelection; class ConnectionSelection;
class ConnectionSettingsDialog; class ConnectionSettingsDialog;
class Connection { class Connection : public Item {
public: public:
Connection(std::string name, std::string address, uint64_t port, Connection(std::string name, std::string address, uint64_t port,
std::string pass, bool connectOnStart, bool reconnect, std::string pass, bool connectOnStart, bool reconnect,
@@ -27,15 +27,19 @@ public:
Connection(const Connection &); Connection(const Connection &);
Connection &operator=(const Connection &); Connection &operator=(const Connection &);
~Connection(); ~Connection();
static std::shared_ptr<Item> Create()
{
return std::make_shared<Connection>();
}
void Reconnect(); void Reconnect();
void SendMsg(const std::string &msg); void SendMsg(const std::string &msg);
void Load(obs_data_t *obj); 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::string GetName() { return _name; }
std::vector<std::string> &Events() { return _client.Events(); }
private: private:
std::string _name = "";
std::string _address = "localhost"; std::string _address = "localhost";
uint64_t _port = 4455; uint64_t _port = 4455;
std::string _password = "password"; std::string _password = "password";
@@ -53,7 +57,7 @@ private:
Connection *GetConnectionByName(const QString &); Connection *GetConnectionByName(const QString &);
Connection *GetConnectionByName(const std::string &); Connection *GetConnectionByName(const std::string &);
class ConnectionSettingsDialog : public QDialog { class ConnectionSettingsDialog : public ItemSettingsDialog {
Q_OBJECT Q_OBJECT
public: public:
@@ -61,54 +65,31 @@ public:
static bool AskForSettings(QWidget *parent, Connection &settings); static bool AskForSettings(QWidget *parent, Connection &settings);
private slots: private slots:
void NameChanged(const QString &);
void ReconnectChanged(int); void ReconnectChanged(int);
void ShowPassword();
void HidePassword();
void SetStatus(); void SetStatus();
void TestConnection(); void TestConnection();
private: private:
void SetNameWarning(const QString);
QLineEdit *_name;
QLabel *_nameHint;
QLineEdit *_address; QLineEdit *_address;
QSpinBox *_port; QSpinBox *_port;
QLineEdit *_password; QLineEdit *_password;
QPushButton *_showPassword;
QCheckBox *_connectOnStart; QCheckBox *_connectOnStart;
QCheckBox *_reconnect; QCheckBox *_reconnect;
QSpinBox *_reconnectDelay; QSpinBox *_reconnectDelay;
QPushButton *_test; QPushButton *_test;
QLabel *_status; QLabel *_status;
QDialogButtonBox *_buttonbox;
QTimer _statusTimer; QTimer _statusTimer;
WSConnection _testConnection; WSConnection _testConnection;
}; };
class ConnectionSelection : public QWidget { class ConnectionSelection : public ItemSelection {
Q_OBJECT Q_OBJECT
public: public:
ConnectionSelection(QWidget *parent = 0); ConnectionSelection(QWidget *parent = 0);
void SetConnection(const std::string &); 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 <obs-module.h>
#include <QLayout> #include <QLayout>
#include <QFileDialog> #include <QFileDialog>
#include <QStandardPaths>
#include <filesystem>
FileSelection::FileSelection(FileSelection::Type type, QWidget *parent) FileSelection::FileSelection(FileSelection::Type type, QWidget *parent)
: QWidget(parent), _type(type) : QWidget(parent), _type(type)
@@ -29,12 +31,22 @@ void FileSelection::SetPath(const QString &path)
void FileSelection::BrowseButtonClicked() 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; QString path;
if (_type == FileSelection::Type::WRITE) { if (_type == FileSelection::Type::WRITE) {
path = QFileDialog::getSaveFileName(this); path = QFileDialog::getSaveFileName(this, "", defaultPath);
} else { } else {
path = QFileDialog::getOpenFileName(this); path = QFileDialog::getOpenFileName(this, "", defaultPath);
} }
if (path.isEmpty()) { if (path.isEmpty()) {
return; 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); _up->setVisible(reorder);
_down->setVisible(reorder); _down->setVisible(reorder);
_add->setMaximumSize(QSize(22, 22)); _add->setMaximumWidth(22);
_add->setProperty("themeID", _add->setProperty("themeID",
QVariant(QString::fromUtf8("addIconSmall"))); QVariant(QString::fromUtf8("addIconSmall")));
_add->setFlat(true); _add->setFlat(true);
_remove->setMaximumSize(QSize(22, 22)); _remove->setMaximumWidth(22);
_remove->setProperty("themeID", _remove->setProperty("themeID",
QVariant(QString::fromUtf8("removeIconSmall"))); QVariant(QString::fromUtf8("removeIconSmall")));
_remove->setFlat(true); _remove->setFlat(true);
_up->setMaximumSize(QSize(22, 22)); _up->setMaximumWidth(22);
_up->setProperty("themeID", _up->setProperty("themeID",
QVariant(QString::fromUtf8("upArrowIconSmall"))); QVariant(QString::fromUtf8("upArrowIconSmall")));
_up->setFlat(true); _up->setFlat(true);
_down->setMaximumSize(QSize(22, 22)); _down->setMaximumWidth(22);
_down->setProperty("themeID", _down->setProperty("themeID",
QVariant(QString::fromUtf8("downArrowIconSmall"))); QVariant(QString::fromUtf8("downArrowIconSmall")));
_down->setFlat(true); _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> #include <obs-module.h>
void SceneItemSelection::Save(obs_data_t *obj, const char *name, constexpr std::string_view typeSaveName = "type";
const char *targetName, const char *idxName) 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)); auto data = obs_data_create();
if (_target == SceneItemSelection::Target::INDIVIDUAL) { obs_data_set_int(data, typeSaveName.data(), static_cast<int>(_type));
obs_data_set_int(obj, idxName, _idx); obs_data_set_int(data, idxTypeSaveName.data(),
static_cast<int>(_idxType));
if (_idxType != IdxType::INDIVIDUAL) {
obs_data_set_int(data, idxSaveName.data(), _idx);
} else { } 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, 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>( _type = Type::SOURCE;
obs_data_get_int(obj, targetName)); _idxType = static_cast<IdxType>(obs_data_get_int(obj, typeName));
_idx = obs_data_get_int(obj, idxName); _idx = obs_data_get_int(obj, idxName);
auto sceneItemName = obs_data_get_string(obj, name); auto sceneItemName = obs_data_get_string(obj, name);
_sceneItem = GetWeakSourceByName(sceneItemName); _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 { struct ItemCountData {
std::string name; std::string name;
int count = 0; int count = 0;
@@ -44,12 +121,12 @@ static bool countSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
return true; return true;
} }
int getCountOfSceneItemOccurance(SceneSelection &s, std::string &name, int getCountOfSceneItemOccurance(const SceneSelection &s,
const std::string &name,
bool enumAllScenes = true) bool enumAllScenes = true)
{ {
ItemCountData data{name}; ItemCountData data{name};
if (enumAllScenes && (s.GetType() == SceneSelectionType::CURRENT || if (enumAllScenes && (s.GetType() != SceneSelection::Type::SCENE)) {
s.GetType() == SceneSelectionType::PREVIOUS)) {
auto enumScenes = [](void *param, obs_source_t *source) { auto enumScenes = [](void *param, obs_source_t *source) {
if (!source) { if (!source) {
return true; return true;
@@ -72,17 +149,25 @@ int getCountOfSceneItemOccurance(SceneSelection &s, std::string &name,
std::vector<obs_scene_item *> std::vector<obs_scene_item *>
SceneItemSelection::GetSceneItems(SceneSelection &sceneSelection) SceneItemSelection::GetSceneItems(SceneSelection &sceneSelection)
{ {
std::vector<obs_scene_item *> ret;
auto s = obs_weak_source_get_source(sceneSelection.GetScene(false)); auto s = obs_weak_source_get_source(sceneSelection.GetScene(false));
auto scene = obs_scene_from_source(s); 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); int count = getCountOfSceneItemOccurance(sceneSelection, name, false);
auto items = getSceneItemsWithName(scene, name); auto items = getSceneItemsWithName(scene, name);
obs_source_release(s); obs_source_release(s);
std::vector<obs_scene_item *> ret; if (_idxType == SceneItemSelection::IdxType::ALL ||
_idxType == SceneItemSelection::IdxType::ANY) {
if (_target == SceneItemSelection::Target::ALL ||
_target == SceneItemSelection::Target::ANY) {
ret = items; ret = items;
} else { } else {
// Index order starts at the bottom and increases to the top // Index order starts at the bottom and increases to the top
@@ -103,13 +188,96 @@ SceneItemSelection::GetSceneItems(SceneSelection &sceneSelection)
std::string SceneItemSelection::ToString() std::string SceneItemSelection::ToString()
{ {
if (_type == Type::VARIABLE) {
auto var = _variable.lock();
if (!var) {
return "";
}
return var->Name();
}
return GetWeakSourceName(_sceneItem); 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, SceneItemSelectionWidget::SceneItemSelectionWidget(QWidget *parent,
bool showAll, bool showAll,
AllSelectionType type) AllSelectionType type)
: QWidget(parent), _showAll(showAll), _allType(type) : QWidget(parent), _hasAllEntry(showAll), _allType(type)
{ {
_sceneItems = new QComboBox(); _sceneItems = new QComboBox();
_idx = new QComboBox(); _idx = new QComboBox();
@@ -124,6 +292,16 @@ SceneItemSelectionWidget::SceneItemSelectionWidget(QWidget *parent,
SLOT(SelectionChanged(const QString &))); SLOT(SelectionChanged(const QString &)));
QWidget::connect(_idx, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_idx, SIGNAL(currentIndexChanged(int)), this,
SLOT(IdxChanged(int))); 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; auto layout = new QHBoxLayout;
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(_idx); layout->addWidget(_idx);
@@ -134,21 +312,48 @@ SceneItemSelectionWidget::SceneItemSelectionWidget(QWidget *parent,
void SceneItemSelectionWidget::SetSceneItem(const SceneItemSelection &item) void SceneItemSelectionWidget::SetSceneItem(const SceneItemSelection &item)
{ {
_sceneItems->setCurrentText( int itemIdx = 0;
QString::fromStdString(GetWeakSourceName(item._sceneItem))); switch (item._type) {
if (item._target == SceneItemSelection::Target::ALL) { case SceneItemSelection::Type::SOURCE: {
_allType = AllSelectionType::ALL;
_idx->setCurrentIndex(0);
} else if (item._target == SceneItemSelection::Target::ANY) {
_allType = AllSelectionType::ANY;
_idx->setCurrentIndex(0);
} else {
int idx = item._idx; int idx = item._idx;
if (_showAll) { if (_hasAllEntry) {
idx += 1; idx += 1;
} }
_idx->setCurrentIndex(idx); _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) void SceneItemSelectionWidget::SetScene(const SceneSelection &s)
@@ -156,12 +361,12 @@ void SceneItemSelectionWidget::SetScene(const SceneSelection &s)
_scene = s; _scene = s;
_sceneItems->clear(); _sceneItems->clear();
_idx->hide(); _idx->hide();
populateSceneItemSelection(_sceneItems, _scene); PopulateItemSelection();
} }
void SceneItemSelectionWidget::SetShowAll(bool value) void SceneItemSelectionWidget::SetShowAll(bool value)
{ {
_showAll = value; _hasAllEntry = value;
} }
void SceneItemSelectionWidget::SetShowAllSelectionType(AllSelectionType t) void SceneItemSelectionWidget::SetShowAllSelectionType(AllSelectionType t)
@@ -179,21 +384,36 @@ void SceneItemSelectionWidget::SceneChanged(const SceneSelection &s)
void SceneItemSelectionWidget::SelectionChanged(const QString &name) void SceneItemSelectionWidget::SelectionChanged(const QString &name)
{ {
SceneItemSelection s; SceneItemSelection s;
_sceneItem = GetWeakSourceByQString(name); int sceneItemCount =
s._sceneItem = _sceneItem; getCountOfSceneItemOccurance(_scene, name.toStdString());
if (_allType == AllSelectionType::ALL) {
s._target = SceneItemSelection::Target::ALL;
} else {
s._target = SceneItemSelection::Target::ANY;
}
auto stdName = name.toStdString();
int sceneItemCount = getCountOfSceneItemOccurance(_scene, stdName);
if (sceneItemCount > 1) { if (sceneItemCount > 1) {
_idx->show(); _idx->show();
SetupIdxSelection(sceneItemCount); SetupIdxSelection(sceneItemCount);
} else { } else {
_idx->hide(); _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); emit SceneItemChanged(s);
} }
@@ -203,29 +423,50 @@ void SceneItemSelectionWidget::IdxChanged(int idx)
return; return;
} }
SceneItemSelection s; _currentSelection._idx = idx;
s._sceneItem = _sceneItem; if (_hasAllEntry && idx == 0) {
if (_showAll && idx == 0) { switch (_allType) {
if (_allType == AllSelectionType::ALL) { case SceneItemSelectionWidget::AllSelectionType::ALL:
s._target = SceneItemSelection::Target::ALL; _currentSelection._idxType =
} else { SceneItemSelection::IdxType::ALL;
s._target = SceneItemSelection::Target::ANY; 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) void SceneItemSelectionWidget::SetupIdxSelection(int sceneItemCount)
{ {
_idx->clear(); _idx->clear();
if (_showAll) { if (_hasAllEntry) {
if (_allType == AllSelectionType::ALL) { if (_allType == AllSelectionType::ALL) {
_idx->addItem(obs_module_text( _idx->addItem(obs_module_text(
"AdvSceneSwitcher.sceneItemSelection.all")); "AdvSceneSwitcher.sceneItemSelection.all"));

View File

@@ -1,28 +1,41 @@
#pragma once #pragma once
#include "scene-selection.hpp"
#include "variable.hpp"
#include "utility.hpp"
#include <QComboBox> #include <QComboBox>
#include <obs-data.h> #include <obs-data.h>
#include "scene-selection.hpp"
#include "utility.hpp"
class SceneItemSelection { class SceneItemSelection {
public: public:
void Save(obs_data_t *obj, const char *name = "sceneItem", void Save(obs_data_t *obj,
const char *targetName = "sceneItemTarget", const char *name = "sceneItemSelection") const;
const char *idxName = "sceneItemIdx"); void Load(obs_data_t *obj, const char *name = "sceneItemSelection");
void Load(obs_data_t *obj, const char *name = "sceneItem", // TODO: Remove in future version
const char *targetName = "sceneItemTarget", void Load(obs_data_t *obj, const char *name, const char *targetName,
const char *idxName = "sceneItemIdx"); const char *idxName);
enum class Target { ALL, ANY, INDIVIDUAL }; enum class Type {
Target GetType() { return _target; } 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::vector<obs_scene_item *> GetSceneItems(SceneSelection &s);
std::string ToString(); std::string ToString();
private: private:
OBSWeakSource _sceneItem; OBSWeakSource _sceneItem;
Target _target = Target::ALL; std::weak_ptr<Variable> _variable;
int _idx = 0; Type _type = Type::SOURCE;
IdxType _idxType = IdxType::ALL;
int _idx = 0; // Multiple items with the same name can exist
friend class SceneItemSelectionWidget; friend class SceneItemSelectionWidget;
}; };
@@ -30,9 +43,10 @@ class SceneItemSelectionWidget : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
// Influences what is being displaye for the "all" entry
enum class AllSelectionType { ALL, ANY }; enum class AllSelectionType { ALL, ANY };
SceneItemSelectionWidget( SceneItemSelectionWidget(
QWidget *parent, bool allSelection = true, QWidget *parent, bool addAllSelection = true,
AllSelectionType allType = AllSelectionType::ALL); AllSelectionType allType = AllSelectionType::ALL);
void SetSceneItem(const SceneItemSelection &); void SetSceneItem(const SceneItemSelection &);
void SetScene(const SceneSelection &); void SetScene(const SceneSelection &);
@@ -45,15 +59,28 @@ private slots:
void SceneChanged(const SceneSelection &); void SceneChanged(const SceneSelection &);
void SelectionChanged(const QString &name); void SelectionChanged(const QString &name);
void IdxChanged(int); void IdxChanged(int);
void ItemAdd(const QString &name);
void ItemRemove(const QString &name);
void ItemRename(const QString &oldName, const QString &newName);
private: private:
void Reset();
void PopulateItemSelection();
void SetupIdxSelection(int); void SetupIdxSelection(int);
QComboBox *_sceneItems; QComboBox *_sceneItems;
QComboBox *_idx; QComboBox *_idx;
SceneSelection _scene; SceneSelection _scene;
OBSWeakSource _sceneItem; SceneItemSelection _currentSelection;
bool _showAll = false; bool _hasAllEntry = false;
AllSelectionType _allType = AllSelectionType::ALL; 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 "scene-selection.hpp"
#include "advanced-scene-switcher.hpp" #include "advanced-scene-switcher.hpp"
#include "utility.hpp"
void SceneSelection::Save(obs_data_t *obj, const char *name, constexpr std::string_view typeSaveName = "type";
const char *typeName) 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) { switch (_type) {
case SceneSelectionType::SCENE: case Type::SCENE:
obs_data_set_string(obj, name, obs_data_set_string(data, nameSaveName.data(),
GetWeakSourceName(_scene).c_str()); GetWeakSourceName(_scene).c_str());
break; break;
case SceneSelectionType::GROUP: case Type::GROUP:
obs_data_set_string(obj, name, _group->name.c_str()); obs_data_set_string(data, nameSaveName.data(),
_group->name.c_str());
break; break;
case Type::VARIABLE: {
auto var = _variable.lock();
if (!var) {
break;
}
obs_data_set_string(data, nameSaveName.data(),
var->Name().c_str());
break;
}
default: default:
break; break;
} }
obs_data_set_obj(obj, selectionSaveName.data(), data);
obs_data_release(data);
} }
void SceneSelection::Load(obs_data_t *obj, const char *name, void SceneSelection::Load(obs_data_t *obj, const char *name,
const char *typeName) const char *typeName)
{ {
_type = static_cast<SceneSelectionType>( // TODO: Remove in future version
obs_data_get_int(obj, typeName)); if (!obs_data_has_user_value(obj, selectionSaveName.data())) {
auto target = obs_data_get_string(obj, name); _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) { switch (_type) {
case SceneSelectionType::SCENE: case Type::SCENE:
_scene = GetWeakSourceByName(target); _scene = GetWeakSourceByName(targetName);
break; break;
case SceneSelectionType::GROUP: case Type::GROUP:
_group = GetSceneGroupByName(target); _group = GetSceneGroupByName(targetName);
break; break;
case SceneSelectionType::PREVIOUS: case Type::PREVIOUS:
break; break;
case SceneSelectionType::CURRENT: case Type::CURRENT:
break;
case Type::PREVIEW:
break;
case Type::VARIABLE:
_variable = GetWeakVariableByName(targetName);
break; break;
default: default:
break; break;
} }
obs_data_release(data);
} }
OBSWeakSource SceneSelection::GetScene(bool advance) OBSWeakSource SceneSelection::GetScene(bool advance) const
{ {
switch (_type) { switch (_type) {
case SceneSelectionType::SCENE: case Type::SCENE:
return _scene; return _scene;
case SceneSelectionType::GROUP: case Type::GROUP:
if (!_group) { if (!_group) {
return nullptr; return nullptr;
} }
@@ -54,199 +97,339 @@ OBSWeakSource SceneSelection::GetScene(bool advance)
return _group->getNextScene(); return _group->getNextScene();
} }
return _group->getCurrentScene(); return _group->getCurrentScene();
case SceneSelectionType::PREVIOUS: case Type::PREVIOUS:
return switcher->previousScene; return switcher->previousScene;
case SceneSelectionType::CURRENT: case Type::CURRENT:
return switcher->currentScene; 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: default:
break; break;
} }
return nullptr; return nullptr;
} }
std::string SceneSelection::ToString() std::string SceneSelection::ToString() const
{ {
switch (_type) { switch (_type) {
case SceneSelectionType::SCENE: case Type::SCENE:
return GetWeakSourceName(_scene); return GetWeakSourceName(_scene);
case SceneSelectionType::GROUP: case Type::GROUP:
if (_group) { if (_group) {
return _group->name; return _group->name;
} }
break; break;
case SceneSelectionType::PREVIOUS: case Type::PREVIOUS:
return obs_module_text("AdvSceneSwitcher.selectPreviousScene"); return obs_module_text("AdvSceneSwitcher.selectPreviousScene");
case SceneSelectionType::CURRENT: case Type::CURRENT:
return obs_module_text("AdvSceneSwitcher.selectCurrentScene"); 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: default:
break; break;
} }
return ""; return "";
} }
SceneSelectionWidget::SceneSelectionWidget(QWidget *parent, bool sceneGroups, SceneSelection SceneSelectionWidget::CurrentSelection()
bool previous, bool current) {
: QComboBox(parent) 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); setDuplicatesEnabled(true);
populateSceneSelection(this, previous, current, false, sceneGroups, PopulateSelection();
&switcher->sceneGroups);
QWidget::connect(this, SIGNAL(currentTextChanged(const QString &)), QWidget::connect(this, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(SelectionChanged(const QString &))); this, SLOT(SelectionChanged(const QString &)));
QWidget::connect(parent, SIGNAL(SceneGroupAdded(const QString &)), this,
SLOT(SceneGroupAdd(const QString &))); // Scene groups
QWidget::connect(parent, SIGNAL(SceneGroupRemoved(const QString &)), QWidget::connect(window(), SIGNAL(SceneGroupAdded(const QString &)),
this, SLOT(SceneGroupRemove(const QString &))); this, SLOT(ItemAdd(const QString &)));
QWidget::connect(window(), SIGNAL(SceneGroupRemoved(const QString &)),
this, SLOT(ItemRemove(const QString &)));
QWidget::connect( QWidget::connect(
parent, window(),
SIGNAL(SceneGroupRenamed(const QString &, const QString &)), 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 int idx = 0;
// 1. Any Scene (current not used)
// 2. Current Scene
// 3. Previous Scene
// 4. Scenes / Scene Groups
int idx;
switch (s.GetType()) { switch (s.GetType()) {
case SceneSelectionType::SCENE: case SceneSelection::Type::SCENE: {
case SceneSelectionType::GROUP: if (_scenesEndIdx == -1) {
setCurrentText(QString::fromStdString(s.ToString())); idx = 0;
break; break;
case SceneSelectionType::PREVIOUS:
idx = findText(QString::fromStdString(obs_module_text(
"AdvSceneSwitcher.selectPreviousScene")));
if (idx != -1) {
setCurrentIndex(idx);
} }
idx = findIdxInRagne(this, _groupsEndIdx, _scenesEndIdx,
s.ToString());
break; break;
case SceneSelectionType::CURRENT: }
idx = findText(QString::fromStdString(obs_module_text( case SceneSelection::Type::GROUP: {
"AdvSceneSwitcher.selectCurrentScene"))); if (_groupsEndIdx == -1) {
if (idx != -1) { idx = 0;
setCurrentIndex(idx); 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; break;
default: default:
setCurrentIndex(0); idx = 0;
break; 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;
}
} }
setCurrentIndex(idx);
// If entry cannot be found we dont want the selection to be empty _currentSelection = s;
return false;
} }
bool SceneSelectionWidget::IsCurrentSceneSelected(const QString &name) bool SceneSelectionWidget::IsCurrentSceneSelected(const QString &name)
{ {
if (name == QString::fromStdString((obs_module_text( return name == QString::fromStdString((obs_module_text(
"AdvSceneSwitcher.selectCurrentScene")))) { "AdvSceneSwitcher.selectCurrentScene")));
return isFirstEntry(this, name, currentIndex());
}
return false;
} }
bool SceneSelectionWidget::IsPreviousSceneSelected(const QString &name) bool SceneSelectionWidget::IsPreviousSceneSelected(const QString &name)
{ {
if (name == QString::fromStdString((obs_module_text( return name == QString::fromStdString((obs_module_text(
"AdvSceneSwitcher.selectPreviousScene")))) { "AdvSceneSwitcher.selectPreviousScene")));
return isFirstEntry(this, name, currentIndex()); }
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; return false;
} }
void SceneSelectionWidget::SelectionChanged(const QString &name) void SceneSelectionWidget::ItemRemove(const QString &name)
{ {
SceneSelection s; if (!NameUsed(name)) {
auto scene = GetWeakSourceByQString(name); blockSignals(true);
if (scene) {
s._type = SceneSelectionType::SCENE;
s._scene = scene;
} }
Reset();
auto group = GetSceneGroupByQString(name); blockSignals(false);
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);
} }
void SceneSelectionWidget::SceneGroupAdd(const QString &name) void SceneSelectionWidget::ItemRename(const QString &, const QString &)
{ {
addItem(name); blockSignals(true);
} Reset();
blockSignals(false);
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));
}
} }

View File

@@ -1,31 +1,34 @@
#pragma once #pragma once
#include "scene-group.hpp" #include "scene-group.hpp"
#include "variable.hpp"
#include "utility.hpp" #include "utility.hpp"
#include <QComboBox> #include <QComboBox>
enum class SceneSelectionType {
SCENE,
GROUP,
PREVIOUS,
CURRENT,
};
class SceneSelection { class SceneSelection {
public: public:
void Save(obs_data_t *obj, const char *name = "scene", void Save(obs_data_t *obj) const;
const char *typeName = "sceneType");
void Load(obs_data_t *obj, const char *name = "scene", void Load(obs_data_t *obj, const char *name = "scene",
const char *typeName = "sceneType"); const char *typeName = "sceneType");
SceneSelectionType GetType() { return _type; } enum class Type {
OBSWeakSource GetScene(bool advance = true); SCENE,
std::string ToString(); GROUP,
PREVIOUS,
CURRENT,
PREVIEW,
VARIABLE,
};
Type GetType() const { return _type; }
OBSWeakSource GetScene(bool advance = true) const;
std::string ToString() const;
private: private:
OBSWeakSource _scene; OBSWeakSource _scene;
SceneGroup *_group = nullptr; SceneGroup *_group = nullptr;
SceneSelectionType _type = SceneSelectionType::SCENE; std::weak_ptr<Variable> _variable;
Type _type = Type::SCENE;
friend class SceneSelectionWidget; friend class SceneSelectionWidget;
}; };
@@ -33,19 +36,45 @@ class SceneSelectionWidget : public QComboBox {
Q_OBJECT Q_OBJECT
public: public:
SceneSelectionWidget(QWidget *parent, bool sceneGroups = false, SceneSelectionWidget(QWidget *parent, bool variables = false,
bool previous = false, bool current = false); bool sceneGroups = false, bool previous = false,
void SetScene(SceneSelection &); bool current = false, bool preview = false);
void SetScene(const SceneSelection &);
signals: signals:
void SceneChanged(const SceneSelection &); void SceneChanged(const SceneSelection &);
private slots: private slots:
void SelectionChanged(const QString &name); void SelectionChanged(const QString &name);
void SceneGroupAdd(const QString &name); void ItemAdd(const QString &name);
void SceneGroupRemove(const QString &name); void ItemRemove(const QString &name);
void SceneGroupRename(const QString &oldName, const QString &newName); void ItemRename(const QString &oldName, const QString &newName);
private: private:
void Reset();
SceneSelection CurrentSelection();
void PopulateSelection();
bool IsCurrentSceneSelected(const QString &name); bool IsCurrentSceneSelected(const QString &name);
bool IsPreviousSceneSelected(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 "screenshot-helper.hpp"
#include "advanced-scene-switcher.hpp" #include "advanced-scene-switcher.hpp"
#include <chrono>
static void ScreenshotTick(void *param, float); static void ScreenshotTick(void *param, float);
ScreenshotHelper::ScreenshotHelper(obs_source_t *source) ScreenshotHelper::ScreenshotHelper(obs_source_t *source, bool blocking,
: weakSource(OBSGetWeakRef(source)) 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; _initDone = true;
obs_add_tick_callback(ScreenshotTick, this); 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() ScreenshotHelper::~ScreenshotHelper()
@@ -20,6 +42,9 @@ ScreenshotHelper::~ScreenshotHelper()
obs_remove_tick_callback(ScreenshotTick, this); obs_remove_tick_callback(ScreenshotTick, this);
} }
if (_saveThread.joinable()) {
_saveThread.join();
}
} }
void ScreenshotHelper::Screenshot() void ScreenshotHelper::Screenshot()
@@ -98,6 +123,26 @@ void ScreenshotHelper::MarkDone()
{ {
time = std::chrono::high_resolution_clock::now(); time = std::chrono::high_resolution_clock::now();
done = true; 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 #define STAGE_SCREENSHOT 0
@@ -124,6 +169,7 @@ static void ScreenshotTick(void *param, float)
break; break;
case STAGE_COPY_AND_SAVE: case STAGE_COPY_AND_SAVE:
data->Copy(); data->Copy();
data->WriteToFile();
data->MarkDone(); data->MarkDone();
obs_remove_tick_callback(ScreenshotTick, data); obs_remove_tick_callback(ScreenshotTick, data);

View File

@@ -3,11 +3,16 @@
#include <string> #include <string>
#include <QImage> #include <QImage>
#include <chrono> #include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
class ScreenshotHelper { class ScreenshotHelper {
public: public:
ScreenshotHelper() = default; ScreenshotHelper() = default;
ScreenshotHelper(obs_source_t *source); ScreenshotHelper(obs_source_t *source, bool blocking = false,
int timeout = 1000, bool saveToFile = false,
std::string path = "");
ScreenshotHelper &operator=(const ScreenshotHelper &) = delete; ScreenshotHelper &operator=(const ScreenshotHelper &) = delete;
ScreenshotHelper(const ScreenshotHelper &) = delete; ScreenshotHelper(const ScreenshotHelper &) = delete;
~ScreenshotHelper(); ~ScreenshotHelper();
@@ -16,6 +21,7 @@ public:
void Download(); void Download();
void Copy(); void Copy();
void MarkDone(); void MarkDone();
void WriteToFile();
gs_texrender_t *texrender = nullptr; gs_texrender_t *texrender = nullptr;
gs_stagesurf_t *stagesurf = nullptr; gs_stagesurf_t *stagesurf = nullptr;
@@ -31,4 +37,10 @@ public:
private: private:
bool _initDone = false; bool _initDone = false;
bool _blocking = false;
std::thread _saveThread;
bool _saveToFile = false;
std::string _path = "";
std::mutex _mutex;
std::condition_variable _cv;
}; };

View File

@@ -7,7 +7,7 @@ void TransitionSelection::Save(obs_data_t *obj, const char *name,
obs_data_set_int(obj, typeName, static_cast<int>(_type)); obs_data_set_int(obj, typeName, static_cast<int>(_type));
switch (_type) { switch (_type) {
case TransitionSelectionType::TRANSITION: case Type::TRANSITION:
obs_data_set_string(obj, name, obs_data_set_string(obj, name,
GetWeakSourceName(_transition).c_str()); GetWeakSourceName(_transition).c_str());
break; break;
@@ -19,11 +19,10 @@ void TransitionSelection::Save(obs_data_t *obj, const char *name,
void TransitionSelection::Load(obs_data_t *obj, const char *name, void TransitionSelection::Load(obs_data_t *obj, const char *name,
const char *typeName) const char *typeName)
{ {
_type = static_cast<TransitionSelectionType>( _type = static_cast<Type>(obs_data_get_int(obj, typeName));
obs_data_get_int(obj, typeName));
auto target = obs_data_get_string(obj, name); auto target = obs_data_get_string(obj, name);
switch (_type) { switch (_type) {
case TransitionSelectionType::TRANSITION: case Type::TRANSITION:
_transition = GetWeakTransitionByName(target); _transition = GetWeakTransitionByName(target);
break; break;
default: default:
@@ -34,9 +33,9 @@ void TransitionSelection::Load(obs_data_t *obj, const char *name,
OBSWeakSource TransitionSelection::GetTransition() OBSWeakSource TransitionSelection::GetTransition()
{ {
switch (_type) { switch (_type) {
case TransitionSelectionType::TRANSITION: case Type::TRANSITION:
return _transition; return _transition;
case TransitionSelectionType::CURRENT: { case Type::CURRENT: {
auto source = obs_frontend_get_current_transition(); auto source = obs_frontend_get_current_transition();
auto weakSource = obs_source_get_weak_source(source); auto weakSource = obs_source_get_weak_source(source);
obs_weak_source_release(weakSource); obs_weak_source_release(weakSource);
@@ -52,11 +51,11 @@ OBSWeakSource TransitionSelection::GetTransition()
std::string TransitionSelection::ToString() std::string TransitionSelection::ToString()
{ {
switch (_type) { switch (_type) {
case TransitionSelectionType::TRANSITION: case Type::TRANSITION:
return GetWeakSourceName(_transition); return GetWeakSourceName(_transition);
case TransitionSelectionType::CURRENT: case Type::CURRENT:
return obs_module_text("AdvSceneSwitcher.currentTransition"); return obs_module_text("AdvSceneSwitcher.currentTransition");
case TransitionSelectionType::ANY: case Type::ANY:
return obs_module_text("AdvSceneSwitcher.anyTransition"); return obs_module_text("AdvSceneSwitcher.anyTransition");
default: default:
break; break;
@@ -85,17 +84,17 @@ void TransitionSelectionWidget::SetTransition(TransitionSelection &t)
int idx; int idx;
switch (t.GetType()) { switch (t.GetType()) {
case TransitionSelectionType::TRANSITION: case TransitionSelection::Type::TRANSITION:
setCurrentText(QString::fromStdString(t.ToString())); setCurrentText(QString::fromStdString(t.ToString()));
break; break;
case TransitionSelectionType::CURRENT: case TransitionSelection::Type::CURRENT:
idx = findText(QString::fromStdString( idx = findText(QString::fromStdString(
obs_module_text("AdvSceneSwitcher.currentTransition"))); obs_module_text("AdvSceneSwitcher.currentTransition")));
if (idx != -1) { if (idx != -1) {
setCurrentIndex(idx); setCurrentIndex(idx);
} }
break; break;
case TransitionSelectionType::ANY: case TransitionSelection::Type::ANY:
idx = findText(QString::fromStdString( idx = findText(QString::fromStdString(
obs_module_text("AdvSceneSwitcher.anyTransition"))); obs_module_text("AdvSceneSwitcher.anyTransition")));
if (idx != -1) { if (idx != -1) {
@@ -155,16 +154,16 @@ void TransitionSelectionWidget::SelectionChanged(const QString &name)
TransitionSelection t; TransitionSelection t;
auto transition = GetWeakTransitionByQString(name); auto transition = GetWeakTransitionByQString(name);
if (transition) { if (transition) {
t._type = TransitionSelectionType::TRANSITION; t._type = TransitionSelection::Type::TRANSITION;
t._transition = transition; t._transition = transition;
} }
if (!transition) { if (!transition) {
if (IsCurrentTransitionSelected(name)) { if (IsCurrentTransitionSelected(name)) {
t._type = TransitionSelectionType::CURRENT; t._type = TransitionSelection::Type::CURRENT;
} }
if (IsAnyTransitionSelected(name)) { if (IsAnyTransitionSelected(name)) {
t._type = TransitionSelectionType::ANY; t._type = TransitionSelection::Type::ANY;
} }
} }

View File

@@ -3,12 +3,6 @@
#include <QComboBox> #include <QComboBox>
enum class TransitionSelectionType {
TRANSITION,
CURRENT,
ANY,
};
class TransitionSelection { class TransitionSelection {
public: public:
void Save(obs_data_t *obj, const char *name = "transition", void Save(obs_data_t *obj, const char *name = "transition",
@@ -16,13 +10,19 @@ public:
void Load(obs_data_t *obj, const char *name = "transition", void Load(obs_data_t *obj, const char *name = "transition",
const char *typeName = "transitionType"); const char *typeName = "transitionType");
TransitionSelectionType GetType() { return _type; } enum class Type {
TRANSITION,
CURRENT,
ANY,
};
Type GetType() { return _type; }
OBSWeakSource GetTransition(); OBSWeakSource GetTransition();
std::string ToString(); std::string ToString();
private: private:
OBSWeakSource _transition; OBSWeakSource _transition;
TransitionSelectionType _type = TransitionSelectionType::TRANSITION; Type _type = Type::TRANSITION;
friend class TransitionSelectionWidget; friend class TransitionSelectionWidget;
}; };

View File

@@ -1,6 +1,7 @@
#include "utility.hpp" #include "utility.hpp"
#include "platform-funcs.hpp" #include "platform-funcs.hpp"
#include "scene-selection.hpp" #include "scene-selection.hpp"
#include "regex-config.hpp"
#include <QTextStream> #include <QTextStream>
#include <QLabel> #include <QLabel>
@@ -301,41 +302,10 @@ void setSourceSettings(obs_source_t *s, const std::string &settings)
obs_data_release(data); 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;
}
// Match json1 with pattern json2 // Match json1 with pattern json2
bool matchJson(const std::string &json1, const std::string &json2, bool matchJson(const std::string &json1, const std::string &json2,
bool useRegex) const RegexConfig &regex)
{ {
bool ret = false;
auto j1 = formatJsonString(json1).toStdString(); auto j1 = formatJsonString(json1).toStdString();
auto j2 = formatJsonString(json2).toStdString(); auto j2 = formatJsonString(json2).toStdString();
@@ -346,24 +316,23 @@ bool matchJson(const std::string &json1, const std::string &json2,
j2 = json2; j2 = json2;
} }
if (useRegex) { if (regex.Enabled()) {
try { auto expr = regex.GetRegularExpression(j2);
std::regex expr(j2); if (!expr.isValid()) {
ret = std::regex_match( return false;
j1, expr, std::regex_constants::match_default);
} catch (const std::regex_error &) {
} }
} else { auto match = expr.match(QString::fromStdString(j1));
ret = j1 == j2; return match.hasMatch();
} }
return ret; return j1 == j2;
} }
bool compareSourceSettings(const OBSWeakSource &source, bool compareSourceSettings(const OBSWeakSource &source,
const std::string &settings, bool useRegex) const std::string &settings,
const RegexConfig &regex)
{ {
std::string currentSettings = getSourceSettings(source); std::string currentSettings = getSourceSettings(source);
return matchJson(currentSettings, settings, useRegex); return matchJson(currentSettings, settings, regex);
} }
std::string getDataFilePath(const std::string &file) std::string getDataFilePath(const std::string &file)
@@ -652,7 +621,8 @@ void populateMediaSelection(QComboBox *sel, bool addSelect)
reinterpret_cast<std::vector<std::string> *>(data); reinterpret_cast<std::vector<std::string> *>(data);
std::string sourceId = obs_source_get_id(source); std::string sourceId = obs_source_get_id(source);
if (sourceId.compare("ffmpeg_source") == 0 || if (sourceId.compare("ffmpeg_source") == 0 ||
sourceId.compare("vlc_source") == 0) { sourceId.compare("vlc_source") == 0 ||
sourceId.compare("slideshow") == 0) {
list->push_back(obs_source_get_name(source)); list->push_back(obs_source_get_name(source));
} }
return true; return true;
@@ -793,68 +763,6 @@ void populateFilterSelection(QComboBox *list, OBSWeakSource weakSource)
list->setCurrentIndex(0); list->setCurrentIndex(0);
} }
static bool enumSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
{
std::set<QString> *names = reinterpret_cast<std::set<QString> *>(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->emplace(name);
return true;
}
void populateSceneItemSelection(QComboBox *list, OBSWeakSource sceneWeakSource)
{
std::set<QString> names;
auto s = obs_weak_source_get_source(sceneWeakSource);
auto scene = obs_scene_from_source(s);
obs_scene_enum_items(scene, enumSceneItem, &names);
obs_source_release(s);
for (auto &name : names) {
list->addItem(name);
}
list->model()->sort(0);
addSelectionEntry(list, obs_module_text("AdvSceneSwitcher.selectItem"));
list->setCurrentIndex(0);
}
void populateSceneItemSelection(QComboBox *list, SceneSelection &s)
{
std::set<QString> names;
if (s.GetType() == SceneSelectionType::CURRENT ||
s.GetType() == SceneSelectionType::PREVIOUS) {
auto enumScenes = [](void *param, obs_source_t *source) {
if (!source) {
return true;
}
std::set<QString> *names =
reinterpret_cast<std::set<QString> *>(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);
}
for (auto &name : names) {
list->addItem(name);
}
list->model()->sort(0);
addSelectionEntry(list, obs_module_text("AdvSceneSwitcher.selectItem"));
list->setCurrentIndex(0);
}
void populateSourceGroupSelection(QComboBox *list) void populateSourceGroupSelection(QComboBox *list)
{ {
std::set<QString> sourceTypeNames; std::set<QString> sourceTypeNames;
@@ -1020,3 +928,41 @@ bool doubleEquals(double left, double right, double epsilon)
{ {
return (fabs(left - right) < epsilon); return (fabs(left - right) < epsilon);
} }
void setButtonIcon(QPushButton *button, const char *path)
{
QIcon icon;
icon.addFile(QString::fromUtf8(path), QSize(), QIcon::Normal,
QIcon::Off);
button->setIcon(icon);
}
void addSelectionGroup(QComboBox *selection, const QStringList &group,
bool addSeparator)
{
selection->addItems(group);
if (addSeparator) {
selection->insertSeparator(selection->count());
}
}
int findIdxInRagne(QComboBox *list, int start, int stop,
const std::string &value)
{
if (value.empty()) {
return 0;
}
auto model = list->model();
auto startIdx = model->index(start, 0);
auto match = model->match(startIdx, Qt::DisplayRole,
QString::fromStdString(value), 1,
Qt::MatchExactly | Qt::MatchCaseSensitive);
if (match.isEmpty()) {
return 0;
}
int foundIdx = match.first().row();
if (foundIdx > stop) {
return 0;
}
return foundIdx;
}

View File

@@ -13,6 +13,7 @@
#include "scene-group.hpp" #include "scene-group.hpp"
class SceneSelection; class SceneSelection;
class RegexConfig;
bool WeakSourceValid(obs_weak_source_t *ws); bool WeakSourceValid(obs_weak_source_t *ws);
std::string GetWeakSourceName(obs_weak_source_t *weak_source); std::string GetWeakSourceName(obs_weak_source_t *weak_source);
@@ -26,12 +27,13 @@ bool compareIgnoringLineEnding(QString &s1, QString &s2);
std::string getSourceSettings(OBSWeakSource ws); std::string getSourceSettings(OBSWeakSource ws);
void setSourceSettings(obs_source_t *s, const std::string &settings); void setSourceSettings(obs_source_t *s, const std::string &settings);
bool compareSourceSettings(const OBSWeakSource &source, bool compareSourceSettings(const OBSWeakSource &source,
const std::string &settings, bool regex); const std::string &settings,
const RegexConfig &regex);
std::vector<obs_scene_item *> getSceneItemsWithName(obs_scene_t *scene, std::vector<obs_scene_item *> getSceneItemsWithName(obs_scene_t *scene,
std::string &name); std::string &name);
std::string getDataFilePath(const std::string &file); std::string getDataFilePath(const std::string &file);
bool matchJson(const std::string &json1, const std::string &json2, bool matchJson(const std::string &json1, const std::string &json2,
bool useRegex); const RegexConfig &regex);
QString formatJsonString(std::string); QString formatJsonString(std::string);
QString formatJsonString(QString); QString formatJsonString(QString);
QString escapeForRegex(QString &s); QString escapeForRegex(QString &s);
@@ -77,10 +79,12 @@ void populateSceneSelection(QComboBox *sel, bool addPrevious = false,
void populateSourcesWithFilterSelection(QComboBox *list); void populateSourcesWithFilterSelection(QComboBox *list);
void populateFilterSelection(QComboBox *list, void populateFilterSelection(QComboBox *list,
OBSWeakSource weakSource = nullptr); OBSWeakSource weakSource = nullptr);
void populateSceneItemSelection(QComboBox *list,
OBSWeakSource sceneWeakSource = nullptr);
void populateSceneItemSelection(QComboBox *list, SceneSelection &s);
void populateSourceGroupSelection(QComboBox *list); void populateSourceGroupSelection(QComboBox *list);
void populateProfileSelection(QComboBox *list); void populateProfileSelection(QComboBox *list);
bool windowPosValid(QPoint pos); bool windowPosValid(QPoint pos);
bool doubleEquals(double left, double right, double epsilon); bool doubleEquals(double left, double right, double epsilon);
void setButtonIcon(QPushButton *button, const char *path);
void addSelectionGroup(QComboBox *selection, const QStringList &group,
bool addSeparator = true);
int findIdxInRagne(QComboBox *list, int start, int stop,
const std::string &value);

235
src/utils/variable.cpp Normal file
View File

@@ -0,0 +1,235 @@
#include "variable.hpp"
#include <switcher-data-structs.hpp>
Variable::Variable() : Item() {}
void Variable::Load(obs_data_t *obj)
{
Item::Load(obj);
_saveAction =
static_cast<SaveAction>(obs_data_get_int(obj, "saveAction"));
_defaultValue = obs_data_get_string(obj, "defaultValue");
if (_saveAction == SaveAction::SAVE) {
_value = obs_data_get_string(obj, "value");
} else if (_saveAction == SaveAction::SET_DEFAULT) {
_value = _defaultValue;
}
}
void Variable::Save(obs_data_t *obj) const
{
Item::Save(obj);
obs_data_set_int(obj, "saveAction", static_cast<int>(_saveAction));
if (_saveAction == SaveAction::SAVE) {
obs_data_set_string(obj, "value", _value.c_str());
}
obs_data_set_string(obj, "defaultValue", _defaultValue.c_str());
}
bool Variable::DoubleValue(double &value) const
{
char *end = nullptr;
value = strtod(_value.c_str(), &end);
return end != _value.c_str() && *end == '\0' && value != HUGE_VAL;
}
void Variable::SetValue(double value)
{
_value = std::to_string(value);
}
Variable *GetVariableByName(const std::string &name)
{
for (const auto &v : switcher->variables) {
if (v->Name() == name) {
return dynamic_cast<Variable *>(v.get());
}
}
return nullptr;
}
Variable *GetVariableByQString(const QString &name)
{
return GetVariableByName(name.toStdString());
}
std::weak_ptr<Variable> GetWeakVariableByName(const std::string &name)
{
for (const auto &v : switcher->variables) {
if (v->Name() == name) {
std::weak_ptr<Variable> wp =
std::dynamic_pointer_cast<Variable>(v);
return wp;
}
}
return std::weak_ptr<Variable>();
}
std::weak_ptr<Variable> GetWeakVariableByQString(const QString &name)
{
return GetWeakVariableByName(name.toStdString());
}
QStringList GetVariablesNameList()
{
QStringList list;
for (const auto &var : switcher->variables) {
list << QString::fromStdString(var->Name());
}
list.sort();
return list;
}
void SwitcherData::saveVariables(obs_data_t *obj)
{
obs_data_array_t *variablesArray = obs_data_array_create();
for (const auto &v : variables) {
obs_data_t *array_obj = obs_data_create();
v->Save(array_obj);
obs_data_array_push_back(variablesArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "variables", variablesArray);
obs_data_array_release(variablesArray);
}
void SwitcherData::loadVariables(obs_data_t *obj)
{
variables.clear();
obs_data_array_t *variablesArray = obs_data_get_array(obj, "variables");
size_t count = obs_data_array_count(variablesArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(variablesArray, i);
auto var = Variable::Create();
variables.emplace_back(var);
variables.back()->Load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(variablesArray);
}
static void populateSaveActionSelection(QComboBox *list)
{
list->addItem(
obs_module_text("AdvSceneSwitcher.variable.save.dontSave"));
list->addItem(obs_module_text("AdvSceneSwitcher.variable.save.save"));
list->addItem(
obs_module_text("AdvSceneSwitcher.variable.save.default"));
}
VariableSettingsDialog::VariableSettingsDialog(QWidget *parent,
const Variable &settings)
: ItemSettingsDialog(settings, switcher->variables,
"AdvSceneSwitcher.variable.select",
"AdvSceneSwitcher.variable.add", parent),
_value(new QLineEdit()),
_defaultValue(new QLineEdit()),
_save(new QComboBox())
{
QWidget::connect(_save, SIGNAL(currentIndexChanged(int)), this,
SLOT(SaveActionChanged(int)));
_value->setText(QString::fromStdString(settings._value));
_defaultValue->setText(QString::fromStdString(settings._defaultValue));
populateSaveActionSelection(_save);
_save->setCurrentIndex(static_cast<int>(settings._saveAction));
QGridLayout *layout = new QGridLayout;
int row = 0;
layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.variable.name")),
row, 0);
QHBoxLayout *nameLayout = new QHBoxLayout;
nameLayout->addWidget(_name);
nameLayout->addWidget(_nameHint);
layout->addLayout(nameLayout, row, 1);
++row;
layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.variable.value")),
row, 0);
layout->addWidget(_value, row, 1);
++row;
layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.variable.save")),
row, 0);
auto saveLayout = new QVBoxLayout;
saveLayout->addWidget(_save);
saveLayout->addWidget(_defaultValue);
saveLayout->addStretch();
layout->addLayout(saveLayout, row, 1);
++row;
layout->addWidget(_buttonbox, row, 0, 1, -1);
layout->setSizeConstraint(QLayout::SetFixedSize);
setLayout(layout);
}
void VariableSettingsDialog::SaveActionChanged(int idx)
{
const Variable::SaveAction action =
static_cast<Variable::SaveAction>(idx);
_defaultValue->setVisible(action == Variable::SaveAction::SET_DEFAULT);
adjustSize();
updateGeometry();
}
bool VariableSettingsDialog::AskForSettings(QWidget *parent, Variable &settings)
{
VariableSettingsDialog dialog(parent, settings);
dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
if (dialog.exec() != DialogCode::Accepted) {
return false;
}
settings._name = dialog._name->text().toStdString();
settings._value = dialog._value->text().toStdString();
settings._defaultValue = dialog._defaultValue->text().toStdString();
settings._saveAction =
static_cast<Variable::SaveAction>(dialog._save->currentIndex());
return true;
}
static bool AskForSettingsWrapper(QWidget *parent, Item &settings)
{
Variable &VariableSettings = dynamic_cast<Variable &>(settings);
return VariableSettingsDialog::AskForSettings(parent, VariableSettings);
}
VariableSelection::VariableSelection(QWidget *parent)
: ItemSelection(switcher->variables, Variable::Create,
AskForSettingsWrapper,
"AdvSceneSwitcher.variable.select",
"AdvSceneSwitcher.variable.add", parent)
{
// Connect to slots
QWidget::connect(
window(),
SIGNAL(VariableRenamed(const QString &, const QString &)), this,
SLOT(RenameItem(const QString &, const QString &)));
QWidget::connect(window(), SIGNAL(VariableAdded(const QString &)), this,
SLOT(AddItem(const QString &)));
QWidget::connect(window(), SIGNAL(VariableRemoved(const QString &)),
this, SLOT(RemoveItem(const QString &)));
// Forward signals
QWidget::connect(
this, SIGNAL(ItemRenamed(const QString &, const QString &)),
window(),
SIGNAL(VariableRenamed(const QString &, const QString &)));
QWidget::connect(this, SIGNAL(ItemAdded(const QString &)), window(),
SIGNAL(VariableAdded(const QString &)));
QWidget::connect(this, SIGNAL(ItemRemoved(const QString &)), window(),
SIGNAL(VariableRemoved(const QString &)));
}
void VariableSelection::SetVariable(const std::string &variable)
{
const QSignalBlocker blocker(_selection);
if (!!GetVariableByName(variable)) {
_selection->setCurrentText(QString::fromStdString(variable));
} else {
_selection->setCurrentIndex(0);
}
}

68
src/utils/variable.hpp Normal file
View File

@@ -0,0 +1,68 @@
#pragma once
#include "item-selection-helpers.hpp"
#include <string>
#include <QStringList>
#include <obs.hpp>
class VariableSelection;
class VariableSettingsDialog;
class Variable : public Item {
public:
Variable();
void Load(obs_data_t *obj);
void Save(obs_data_t *obj) const;
std::string Value() const { return _value; }
bool DoubleValue(double &) const;
void SetValue(const std::string &val) { _value = val; }
void SetValue(double);
static std::shared_ptr<Item> Create()
{
return std::make_shared<Variable>();
}
enum class SaveAction {
DONT_SAVE,
SAVE,
SET_DEFAULT,
};
private:
SaveAction _saveAction = SaveAction::DONT_SAVE;
std::string _value = "";
std::string _defaultValue = "";
friend VariableSelection;
friend VariableSettingsDialog;
};
Variable *GetVariableByName(const std::string &name);
Variable *GetVariableByQString(const QString &name);
std::weak_ptr<Variable> GetWeakVariableByName(const std::string &name);
std::weak_ptr<Variable> GetWeakVariableByQString(const QString &name);
QStringList GetVariablesNameList();
class VariableSettingsDialog : public ItemSettingsDialog {
Q_OBJECT
public:
VariableSettingsDialog(QWidget *parent, const Variable &);
static bool AskForSettings(QWidget *parent, Variable &settings);
private slots:
void SaveActionChanged(int);
private:
QLineEdit *_value;
QLineEdit *_defaultValue;
QComboBox *_save;
};
class VariableSelection : public ItemSelection {
Q_OBJECT
public:
VariableSelection(QWidget *parent = 0);
void SetVariable(const std::string &);
};

View File

@@ -5,23 +5,24 @@
#include <QPushButton> #include <QPushButton>
#include <QLabel> #include <QLabel>
#include <QPainter> #include <QPainter>
#include <QStyle>
#include <QStyleFactory> #include <QStyleFactory>
#include <QSlider>
#include <util/platform.h> #include <util/platform.h>
using namespace std; using namespace std;
#define CLAMP(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))) #define CLAMP(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x)))
#define FADER_PRECISION 4096.0 #define FADER_PRECISION 100
// Size of the audio indicator in pixels
#define INDICATOR_THICKNESS 3
// Padding on top and bottom of vertical meters
#define METER_PADDING 1
QWeakPointer<VolumeMeterTimer> VolumeMeter::updateTimer; QWeakPointer<VolumeMeterTimer> VolumeMeter::updateTimer;
void VolControl::OBSVolumeChanged(void *data, float db) void VolControl::OBSVolumeChanged(void *, float) {}
{
Q_UNUSED(db);
Q_UNUSED(data);
}
void VolControl::OBSVolumeLevel(void *data, void VolControl::OBSVolumeLevel(void *data,
const float magnitude[MAX_AUDIO_CHANNELS], const float magnitude[MAX_AUDIO_CHANNELS],
@@ -33,9 +34,19 @@ void VolControl::OBSVolumeLevel(void *data,
volControl->volMeter->setLevels(magnitude, peak, inputPeak); volControl->volMeter->setLevels(magnitude, peak, inputPeak);
} }
void VolControl::SliderChanged(int vol) void VolControl::VolumeChanged()
{ {
Q_UNUSED(vol); slider->blockSignals(true);
slider->setValue(
(int)(obs_fader_get_deflection(obs_fader) * FADER_PRECISION));
slider->blockSignals(false);
updateText();
}
void VolControl::SliderChanged(int)
{
float prev = obs_source_get_volume(source);
updateText(); updateText();
} }
@@ -61,6 +72,12 @@ void VolControl::SetName(const QString &newName)
{ {
nameLabel->setText(newName); nameLabel->setText(newName);
} }
void VolControl::EmitConfigClicked()
{
emit ConfigClicked();
}
void VolControl::SetMeterDecayRate(qreal q) void VolControl::SetMeterDecayRate(qreal q)
{ {
volMeter->setPeakDecayRate(q); volMeter->setPeakDecayRate(q);
@@ -71,23 +88,46 @@ void VolControl::setPeakMeterType(enum obs_peak_meter_type peakMeterType)
volMeter->setPeakMeterType(peakMeterType); volMeter->setPeakMeterType(peakMeterType);
} }
VolControl::VolControl(OBSSource source_, bool vertical) VolumeSlider::VolumeSlider(obs_fader_t *fader, QWidget *parent)
: QSlider(parent)
{
fad = fader;
}
VolumeSlider::VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation,
QWidget *parent)
: QSlider(orientation, parent)
{
fad = fader;
}
VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical)
: source(std::move(source_)), : source(std::move(source_)),
levelTotal(0.0f), levelTotal(0.0f),
levelCount(0.0f), levelCount(0.0f),
obs_fader(obs_fader_create(OBS_FADER_LOG)), obs_fader(obs_fader_create(OBS_FADER_LOG)),
obs_volmeter(obs_volmeter_create(OBS_FADER_LOG)), obs_volmeter(obs_volmeter_create(OBS_FADER_LOG)),
vertical(vertical) vertical(vertical),
contextMenu(nullptr)
{ {
nameLabel = new QLabel(); nameLabel = new QLabel();
volLabel = new QLabel(); volLabel = new QLabel();
QString sourceName = ""; QString sourceName = obs_source_get_name(source);
if (source) {
obs_source_get_name(source);
}
setObjectName(sourceName); setObjectName(sourceName);
if (showConfig) {
config = new QPushButton(this);
config->setProperty("themeID", "menuIconSmall");
config->setSizePolicy(QSizePolicy::Maximum,
QSizePolicy::Maximum);
config->setMaximumSize(22, 22);
config->setAutoDefault(false);
connect(config, &QAbstractButton::clicked, this,
&VolControl::EmitConfigClicked);
}
QVBoxLayout *mainLayout = new QVBoxLayout; QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->setContentsMargins(4, 4, 4, 4); mainLayout->setContentsMargins(4, 4, 4, 4);
mainLayout->setSpacing(2); mainLayout->setSpacing(2);
@@ -99,7 +139,7 @@ VolControl::VolControl(OBSSource source_, bool vertical)
QHBoxLayout *meterLayout = new QHBoxLayout; QHBoxLayout *meterLayout = new QHBoxLayout;
volMeter = new VolumeMeter(nullptr, obs_volmeter, true); volMeter = new VolumeMeter(nullptr, obs_volmeter, true);
slider = new QSlider(Qt::Vertical); slider = new VolumeSlider(obs_fader, Qt::Vertical);
nameLayout->setAlignment(Qt::AlignCenter); nameLayout->setAlignment(Qt::AlignCenter);
meterLayout->setAlignment(Qt::AlignCenter); meterLayout->setAlignment(Qt::AlignCenter);
@@ -113,7 +153,11 @@ VolControl::VolControl(OBSSource source_, bool vertical)
controlLayout->setContentsMargins(0, 0, 0, 0); controlLayout->setContentsMargins(0, 0, 0, 0);
controlLayout->setSpacing(0); controlLayout->setSpacing(0);
if (showConfig)
controlLayout->addWidget(config);
controlLayout->addItem(new QSpacerItem(3, 0)); controlLayout->addItem(new QSpacerItem(3, 0));
// Add Headphone (audio monitoring) widget here
meterLayout->setContentsMargins(0, 0, 0, 0); meterLayout->setContentsMargins(0, 0, 0, 0);
meterLayout->setSpacing(0); meterLayout->setSpacing(0);
@@ -131,6 +175,12 @@ VolControl::VolControl(OBSSource source_, bool vertical)
volMeter->setFocusProxy(slider); volMeter->setFocusProxy(slider);
// Default size can cause clipping of long names in vertical layout.
QFont font = nameLabel->font();
QFontInfo info(font);
font.setPointSizeF(0.8 * info.pointSizeF());
nameLabel->setFont(font);
setMaximumWidth(110); setMaximumWidth(110);
} else { } else {
QHBoxLayout *volLayout = new QHBoxLayout; QHBoxLayout *volLayout = new QHBoxLayout;
@@ -138,7 +188,7 @@ VolControl::VolControl(OBSSource source_, bool vertical)
QHBoxLayout *botLayout = new QHBoxLayout; QHBoxLayout *botLayout = new QHBoxLayout;
volMeter = new VolumeMeter(nullptr, obs_volmeter, false); volMeter = new VolumeMeter(nullptr, obs_volmeter, false);
slider = new QSlider(Qt::Horizontal); slider = new VolumeSlider(obs_fader, Qt::Horizontal);
textLayout->setContentsMargins(0, 0, 0, 0); textLayout->setContentsMargins(0, 0, 0, 0);
textLayout->addWidget(nameLabel); textLayout->addWidget(nameLabel);
@@ -153,6 +203,9 @@ VolControl::VolControl(OBSSource source_, bool vertical)
botLayout->setSpacing(0); botLayout->setSpacing(0);
botLayout->addLayout(volLayout); botLayout->addLayout(volLayout);
if (showConfig)
botLayout->addWidget(config);
mainLayout->addItem(textLayout); mainLayout->addItem(textLayout);
mainLayout->addWidget(volMeter); mainLayout->addWidget(volMeter);
mainLayout->addItem(botLayout); mainLayout->addItem(botLayout);
@@ -162,15 +215,10 @@ VolControl::VolControl(OBSSource source_, bool vertical)
setLayout(mainLayout); setLayout(mainLayout);
QFont font = nameLabel->font();
font.setPointSize(font.pointSize() - 1);
nameLabel->setText(sourceName); nameLabel->setText(sourceName);
nameLabel->setFont(font);
volLabel->setFont(font);
slider->setMinimum(0); slider->setMinimum(0);
slider->setMaximum(100); slider->setMaximum(int(FADER_PRECISION));
obs_fader_add_callback(obs_fader, OBSVolumeChanged, this); obs_fader_add_callback(obs_fader, OBSVolumeChanged, this);
obs_volmeter_add_callback(obs_volmeter, OBSVolumeLevel, this); obs_volmeter_add_callback(obs_volmeter, OBSVolumeLevel, this);
@@ -180,6 +228,9 @@ VolControl::VolControl(OBSSource source_, bool vertical)
obs_fader_attach_source(obs_fader, source); obs_fader_attach_source(obs_fader, source);
obs_volmeter_attach_source(obs_volmeter, source); obs_volmeter_attach_source(obs_volmeter, source);
/* Call volume changed once to init the slider position and label */
VolumeChanged();
} }
void VolControl::EnableSlider(bool enable) void VolControl::EnableSlider(bool enable)
@@ -187,11 +238,6 @@ void VolControl::EnableSlider(bool enable)
slider->setEnabled(enable); slider->setEnabled(enable);
} }
QSlider *VolControl::GetSlider() const
{
return slider;
}
VolControl::~VolControl() VolControl::~VolControl()
{ {
obs_fader_remove_callback(obs_fader, OBSVolumeChanged, this); obs_fader_remove_callback(obs_fader, OBSVolumeChanged, this);
@@ -199,66 +245,143 @@ VolControl::~VolControl()
obs_fader_destroy(obs_fader); obs_fader_destroy(obs_fader);
obs_volmeter_destroy(obs_volmeter); obs_volmeter_destroy(obs_volmeter);
if (contextMenu)
contextMenu->close();
}
static inline QColor color_from_int(long long val)
{
QColor color(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff,
(val >> 24) & 0xff);
color.setAlpha(255);
return color;
} }
QColor VolumeMeter::getBackgroundNominalColor() const QColor VolumeMeter::getBackgroundNominalColor() const
{ {
return backgroundNominalColor; return p_backgroundNominalColor;
}
QColor VolumeMeter::getBackgroundNominalColorDisabled() const
{
return backgroundNominalColorDisabled;
} }
void VolumeMeter::setBackgroundNominalColor(QColor c) void VolumeMeter::setBackgroundNominalColor(QColor c)
{ {
backgroundNominalColor = std::move(c); p_backgroundNominalColor = std::move(c);
backgroundNominalColor = p_backgroundNominalColor;
}
void VolumeMeter::setBackgroundNominalColorDisabled(QColor c)
{
backgroundNominalColorDisabled = std::move(c);
} }
QColor VolumeMeter::getBackgroundWarningColor() const QColor VolumeMeter::getBackgroundWarningColor() const
{ {
return backgroundWarningColor; return p_backgroundWarningColor;
}
QColor VolumeMeter::getBackgroundWarningColorDisabled() const
{
return backgroundWarningColorDisabled;
} }
void VolumeMeter::setBackgroundWarningColor(QColor c) void VolumeMeter::setBackgroundWarningColor(QColor c)
{ {
backgroundWarningColor = std::move(c); p_backgroundWarningColor = std::move(c);
backgroundWarningColor = p_backgroundWarningColor;
}
void VolumeMeter::setBackgroundWarningColorDisabled(QColor c)
{
backgroundWarningColorDisabled = std::move(c);
} }
QColor VolumeMeter::getBackgroundErrorColor() const QColor VolumeMeter::getBackgroundErrorColor() const
{ {
return backgroundErrorColor; return p_backgroundErrorColor;
}
QColor VolumeMeter::getBackgroundErrorColorDisabled() const
{
return backgroundErrorColorDisabled;
} }
void VolumeMeter::setBackgroundErrorColor(QColor c) void VolumeMeter::setBackgroundErrorColor(QColor c)
{ {
backgroundErrorColor = std::move(c); p_backgroundErrorColor = std::move(c);
backgroundErrorColor = p_backgroundErrorColor;
}
void VolumeMeter::setBackgroundErrorColorDisabled(QColor c)
{
backgroundErrorColorDisabled = std::move(c);
} }
QColor VolumeMeter::getForegroundNominalColor() const QColor VolumeMeter::getForegroundNominalColor() const
{ {
return foregroundNominalColor; return p_foregroundNominalColor;
}
QColor VolumeMeter::getForegroundNominalColorDisabled() const
{
return foregroundNominalColorDisabled;
} }
void VolumeMeter::setForegroundNominalColor(QColor c) void VolumeMeter::setForegroundNominalColor(QColor c)
{ {
foregroundNominalColor = std::move(c); p_foregroundNominalColor = std::move(c);
foregroundNominalColor = p_foregroundNominalColor;
}
void VolumeMeter::setForegroundNominalColorDisabled(QColor c)
{
foregroundNominalColorDisabled = std::move(c);
} }
QColor VolumeMeter::getForegroundWarningColor() const QColor VolumeMeter::getForegroundWarningColor() const
{ {
return foregroundWarningColor; return p_foregroundWarningColor;
}
QColor VolumeMeter::getForegroundWarningColorDisabled() const
{
return foregroundWarningColorDisabled;
} }
void VolumeMeter::setForegroundWarningColor(QColor c) void VolumeMeter::setForegroundWarningColor(QColor c)
{ {
foregroundWarningColor = std::move(c); p_foregroundWarningColor = std::move(c);
foregroundWarningColor = p_foregroundWarningColor;
}
void VolumeMeter::setForegroundWarningColorDisabled(QColor c)
{
foregroundWarningColorDisabled = std::move(c);
} }
QColor VolumeMeter::getForegroundErrorColor() const QColor VolumeMeter::getForegroundErrorColor() const
{ {
return foregroundErrorColor; return p_foregroundErrorColor;
}
QColor VolumeMeter::getForegroundErrorColorDisabled() const
{
return foregroundErrorColorDisabled;
} }
void VolumeMeter::setForegroundErrorColor(QColor c) void VolumeMeter::setForegroundErrorColor(QColor c)
{ {
foregroundErrorColor = std::move(c); p_foregroundErrorColor = std::move(c);
foregroundErrorColor = p_foregroundErrorColor;
}
void VolumeMeter::setForegroundErrorColorDisabled(QColor c)
{
foregroundErrorColorDisabled = std::move(c);
} }
QColor VolumeMeter::getClipColor() const QColor VolumeMeter::getClipColor() const
@@ -301,6 +424,42 @@ void VolumeMeter::setMinorTickColor(QColor c)
minorTickColor = std::move(c); minorTickColor = std::move(c);
} }
int VolumeMeter::getMeterThickness() const
{
return meterThickness;
}
void VolumeMeter::setMeterThickness(int v)
{
meterThickness = v;
recalculateLayout = true;
}
qreal VolumeMeter::getMeterFontScaling() const
{
return meterFontScaling;
}
void VolumeMeter::setMeterFontScaling(qreal v)
{
meterFontScaling = v;
recalculateLayout = true;
}
void VolControl::refreshColors()
{
volMeter->setBackgroundNominalColor(
volMeter->getBackgroundNominalColor());
volMeter->setBackgroundWarningColor(
volMeter->getBackgroundWarningColor());
volMeter->setBackgroundErrorColor(volMeter->getBackgroundErrorColor());
volMeter->setForegroundNominalColor(
volMeter->getForegroundNominalColor());
volMeter->setForegroundWarningColor(
volMeter->getForegroundWarningColor());
volMeter->setForegroundErrorColor(volMeter->getForegroundErrorColor());
}
qreal VolumeMeter::getMinimumLevel() const qreal VolumeMeter::getMinimumLevel() const
{ {
return minimumLevel; return minimumLevel;
@@ -434,10 +593,9 @@ VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter,
bool vertical) bool vertical)
: QWidget(parent), obs_volmeter(obs_volmeter), vertical(vertical) : QWidget(parent), obs_volmeter(obs_volmeter), vertical(vertical)
{ {
// Use a font that can be rendered small. setAttribute(Qt::WA_OpaquePaintEvent, true);
tickFont = QFont("Arial");
tickFont.setPixelSize(7); // Default meter settings, they only show if
// Default meter color settings, they only show if
// there is no stylesheet, do not remove. // there is no stylesheet, do not remove.
backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green
backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow
@@ -445,27 +603,38 @@ VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter,
foregroundNominalColor.setRgb(0x4c, 0xff, 0x4c); // Bright green foregroundNominalColor.setRgb(0x4c, 0xff, 0x4c); // Bright green
foregroundWarningColor.setRgb(0xff, 0xff, 0x4c); // Bright yellow foregroundWarningColor.setRgb(0xff, 0xff, 0x4c); // Bright yellow
foregroundErrorColor.setRgb(0xff, 0x4c, 0x4c); // Bright red foregroundErrorColor.setRgb(0xff, 0x4c, 0x4c); // Bright red
clipColor.setRgb(0xff, 0xff, 0xff); // Bright white
magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black
majorTickColor.setRgb(0xff, 0xff, 0xff); // Black
minorTickColor.setRgb(0xcc, 0xcc, 0xcc); // Black
minimumLevel = -60.0; // -60 dB
warningLevel = -20.0; // -20 dB
errorLevel = -9.0; // -9 dB
clipLevel = -0.5; // -0.5 dB
minimumInputLevel = -50.0; // -50 dB
peakDecayRate = 11.76; // 20 dB / 1.7 sec
magnitudeIntegrationTime = 0.3; // 99% in 300 ms
peakHoldDuration = 20.0; // 20 seconds
inputPeakHoldDuration = 1.0; // 1 second
backgroundNominalColorDisabled.setRgb(90, 90, 90);
backgroundWarningColorDisabled.setRgb(117, 117, 117);
backgroundErrorColorDisabled.setRgb(65, 65, 65);
foregroundNominalColorDisabled.setRgb(163, 163, 163);
foregroundWarningColorDisabled.setRgb(217, 217, 217);
foregroundErrorColorDisabled.setRgb(113, 113, 113);
clipColor.setRgb(0xff, 0xff, 0xff); // Bright white
magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black
majorTickColor.setRgb(0xff, 0xff, 0xff); // Black
minorTickColor.setRgb(0xcc, 0xcc, 0xcc); // Black
minimumLevel = -60.0; // -60 dB
warningLevel = -20.0; // -20 dB
errorLevel = -9.0; // -9 dB
clipLevel = -0.5; // -0.5 dB
minimumInputLevel = -50.0; // -50 dB
peakDecayRate = 11.76; // 20 dB / 1.7 sec
magnitudeIntegrationTime = 0.3; // 99% in 300 ms
peakHoldDuration = 20.0; // 20 seconds
inputPeakHoldDuration = 1.0; // 1 second
meterThickness = 3; // Bar thickness in pixels
meterFontScaling =
0.7; // Font size for numbers is 70% of Widget's font size
channels = (int)audio_output_get_channels(obs_get_audio()); channels = (int)audio_output_get_channels(obs_get_audio());
handleChannelCofigurationChange(); doLayout();
updateTimerRef = updateTimer.toStrongRef(); updateTimerRef = updateTimer.toStrongRef();
if (!updateTimerRef) { if (!updateTimerRef) {
updateTimerRef = QSharedPointer<VolumeMeterTimer>::create(); updateTimerRef = QSharedPointer<VolumeMeterTimer>::create();
updateTimerRef->start(34); updateTimerRef->setTimerType(Qt::PreciseTimer);
updateTimerRef->start(16);
updateTimer = updateTimerRef; updateTimer = updateTimerRef;
} }
@@ -475,7 +644,6 @@ VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter,
VolumeMeter::~VolumeMeter() VolumeMeter::~VolumeMeter()
{ {
updateTimerRef->RemoveVolControl(this); updateTimerRef->RemoveVolControl(this);
delete tickPaintCache;
} }
void VolumeMeter::setLevels(const float magnitude[MAX_AUDIO_CHANNELS], void VolumeMeter::setLevels(const float magnitude[MAX_AUDIO_CHANNELS],
@@ -515,23 +683,58 @@ inline void VolumeMeter::resetLevels()
} }
} }
inline void VolumeMeter::handleChannelCofigurationChange() bool VolumeMeter::needLayoutChange()
{
int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter);
if (!currentNrAudioChannels) {
struct obs_audio_info oai;
obs_get_audio_info(&oai);
currentNrAudioChannels = (oai.speakers == SPEAKERS_MONO) ? 1
: 2;
}
if (displayNrAudioChannels != currentNrAudioChannels) {
displayNrAudioChannels = currentNrAudioChannels;
recalculateLayout = true;
}
return recalculateLayout;
}
// When this is called from the constructor, obs_volmeter_get_nr_channels has not
// yet been called and Q_PROPERTY settings have not yet been read from the
// stylesheet.
inline void VolumeMeter::doLayout()
{ {
QMutexLocker locker(&dataMutex); QMutexLocker locker(&dataMutex);
int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter); recalculateLayout = false;
if (displayNrAudioChannels != currentNrAudioChannels) {
displayNrAudioChannels = currentNrAudioChannels;
// Make room for 3 pixels meter, with one pixel between each. tickFont = font();
// Then 9/13 pixels for ticks and numbers. QFontInfo info(tickFont);
if (vertical) tickFont.setPointSizeF(info.pointSizeF() * meterFontScaling);
setMinimumSize(displayNrAudioChannels * 4 + 14, 130); QFontMetrics metrics(tickFont);
else if (vertical) {
setMinimumSize(130, displayNrAudioChannels * 4 + 8); // Each meter channel is meterThickness pixels wide, plus one pixel
// between channels, but not after the last.
resetLevels(); // Add 4 pixels for ticks, space to hold our longest label in this font,
// and a few pixels before the fader.
QRect scaleBounds = metrics.boundingRect("-88");
setMinimumSize(displayNrAudioChannels * (meterThickness + 1) -
1 + 4 + scaleBounds.width() + 2,
130);
} else {
// Each meter channel is meterThickness pixels high, plus one pixel
// between channels, but not after the last.
// Add 4 pixels for ticks, and space high enough to hold our label in
// this font, presuming that digits don't have descenders.
setMinimumSize(130,
displayNrAudioChannels * (meterThickness + 1) -
1 + 4 + metrics.capHeight());
} }
resetLevels();
} }
inline bool VolumeMeter::detectIdle(uint64_t ts) inline bool VolumeMeter::detectIdle(uint64_t ts)
@@ -649,12 +852,12 @@ void VolumeMeter::paintInputMeter(QPainter &painter, int x, int y, int width,
painter.fillRect(x, y, width, height, color); painter.fillRect(x, y, width, height, color);
} }
void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width, void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width)
int height)
{ {
qreal scale = width / minimumLevel; qreal scale = width / minimumLevel;
painter.setFont(tickFont); painter.setFont(tickFont);
QFontMetrics metrics(tickFont);
painter.setPen(majorTickColor); painter.setPen(majorTickColor);
// Draw major tick lines and numeric indicators. // Draw major tick lines and numeric indicators.
@@ -662,10 +865,18 @@ void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width,
int position = int(x + width - (i * scale) - 1); int position = int(x + width - (i * scale) - 1);
QString str = QString::number(i); QString str = QString::number(i);
if (i == 0 || i == -5) // Center the number on the tick, but don't overflow
painter.drawText(position - 3, height, str); QRect textBounds = metrics.boundingRect(str);
else int pos;
painter.drawText(position - 5, height, str); if (i == 0) {
pos = position - textBounds.width();
} else {
pos = position - (textBounds.width() / 2);
if (pos < 0)
pos = 0;
}
painter.drawText(pos, y + 4 + metrics.capHeight(), str);
painter.drawLine(position, y, position, y + 2); painter.drawLine(position, y, position, y + 2);
} }
@@ -683,26 +894,31 @@ void VolumeMeter::paintVTicks(QPainter &painter, int x, int y, int height)
qreal scale = height / minimumLevel; qreal scale = height / minimumLevel;
painter.setFont(tickFont); painter.setFont(tickFont);
QFontMetrics metrics(tickFont);
painter.setPen(majorTickColor); painter.setPen(majorTickColor);
// Draw major tick lines and numeric indicators. // Draw major tick lines and numeric indicators.
for (int i = 0; i >= minimumLevel; i -= 5) { for (int i = 0; i >= minimumLevel; i -= 5) {
int position = y + int((i * scale) - 1); int position = y + int(i * scale) + METER_PADDING;
QString str = QString::number(i); QString str = QString::number(i);
if (i == 0) // Center the number on the tick, but don't overflow
painter.drawText(x + 5, position + 4, str); if (i == 0) {
else if (i == -60) painter.drawText(x + 6, position + metrics.capHeight(),
painter.drawText(x + 4, position, str); str);
else } else {
painter.drawText(x + 4, position + 2, str); painter.drawText(x + 4,
position + (metrics.capHeight() / 2),
str);
}
painter.drawLine(x, position, x + 2, position); painter.drawLine(x, position, x + 2, position);
} }
// Draw minor tick lines. // Draw minor tick lines.
painter.setPen(minorTickColor); painter.setPen(minorTickColor);
for (int i = 0; i >= minimumLevel; i--) { for (int i = 0; i >= minimumLevel; i--) {
int position = y + int((i * scale) - 1); int position = y + int(i * scale) + METER_PADDING;
if (i % 5 != 0) if (i % 5 != 0)
painter.drawLine(x, position, x + 1, position); painter.drawLine(x, position, x + 1, position);
} }
@@ -777,7 +993,7 @@ void VolumeMeter::paintHMeter(QPainter &painter, int x, int y, int width,
painter.fillRect(peakPosition, y, painter.fillRect(peakPosition, y,
maximumPosition - peakPosition, height, maximumPosition - peakPosition, height,
backgroundErrorColor); backgroundErrorColor);
} else { } else if (int(magnitude) != 0) {
if (!clipping) { if (!clipping) {
QTimer::singleShot(CLIP_FLASH_DURATION_MS, this, QTimer::singleShot(CLIP_FLASH_DURATION_MS, this,
SLOT(ClipEnding())); SLOT(ClipEnding()));
@@ -879,7 +1095,7 @@ void VolumeMeter::paintVMeter(QPainter &painter, int x, int y, int width,
int end = errorLength + warningLength + nominalLength; int end = errorLength + warningLength + nominalLength;
painter.fillRect(x, minimumPosition, width, end, painter.fillRect(x, minimumPosition, width, end,
QBrush(foregroundErrorColor)); foregroundErrorColor);
} }
if (peakHoldPosition - 3 < minimumPosition) if (peakHoldPosition - 3 < minimumPosition)
@@ -903,53 +1119,48 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
{ {
uint64_t ts = os_gettime_ns(); uint64_t ts = os_gettime_ns();
qreal timeSinceLastRedraw = (ts - lastRedrawTime) * 0.000000001; qreal timeSinceLastRedraw = (ts - lastRedrawTime) * 0.000000001;
const QRect rect = event->region().boundingRect();
int width = rect.width();
int height = rect.height();
handleChannelCofigurationChange();
calculateBallistics(ts, timeSinceLastRedraw); calculateBallistics(ts, timeSinceLastRedraw);
bool idle = detectIdle(ts); bool idle = detectIdle(ts);
// Draw the ticks in a off-screen buffer when the widget changes size. QRect widgetRect = rect();
QSize tickPaintCacheSize; int width = widgetRect.width();
int height = widgetRect.height();
QPainter painter(this);
if (vertical) if (vertical)
tickPaintCacheSize = QSize(14, height); height -= METER_PADDING * 2;
else
tickPaintCacheSize = QSize(width, 9);
if (tickPaintCache == nullptr ||
tickPaintCache->size() != tickPaintCacheSize) {
delete tickPaintCache;
tickPaintCache = new QPixmap(tickPaintCacheSize);
QColor clearColor(0, 0, 0, 0); // timerEvent requests update of the bar(s) only, so we can avoid the
tickPaintCache->fill(clearColor); // overhead of repainting the scale and labels.
if (event->region().boundingRect() != getBarRect()) {
if (needLayoutChange())
doLayout();
// Paint window background color (as widget is opaque)
QColor background =
palette().color(QPalette::ColorRole::Window);
painter.fillRect(widgetRect, background);
QPainter tickPainter(tickPaintCache);
if (vertical) { if (vertical) {
tickPainter.translate(0, height); paintVTicks(painter,
tickPainter.scale(1, -1); displayNrAudioChannels *
paintVTicks(tickPainter, 0, 11, (meterThickness + 1) -
tickPaintCacheSize.height() - 11); 1,
0, height - (INDICATOR_THICKNESS + 3));
} else { } else {
paintHTicks(tickPainter, 6, 0, paintHTicks(painter, INDICATOR_THICKNESS + 3,
tickPaintCacheSize.width() - 6, displayNrAudioChannels *
tickPaintCacheSize.height()); (meterThickness + 1) -
1,
width - (INDICATOR_THICKNESS + 3));
} }
tickPainter.end();
} }
// Actual painting of the widget starts here.
QPainter painter(this);
if (vertical) { if (vertical) {
// Invert the Y axis to ease the math // Invert the Y axis to ease the math
painter.translate(0, height); painter.translate(0, height + METER_PADDING);
painter.scale(1, -1); painter.scale(1, -1);
painter.drawPixmap(displayNrAudioChannels * 4 - 1, 7,
*tickPaintCache);
} else {
painter.drawPixmap(0, height - 9, *tickPaintCache);
} }
for (int channelNr = 0; channelNr < displayNrAudioChannels; for (int channelNr = 0; channelNr < displayNrAudioChannels;
@@ -961,12 +1172,17 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
: channelNr; : channelNr;
if (vertical) if (vertical)
paintVMeter(painter, channelNr * 4, 8, 3, height - 10, paintVMeter(painter, channelNr * (meterThickness + 1),
INDICATOR_THICKNESS + 2, meterThickness,
height - (INDICATOR_THICKNESS + 2),
displayMagnitude[channelNrFixed], displayMagnitude[channelNrFixed],
displayPeak[channelNrFixed], displayPeak[channelNrFixed],
displayPeakHold[channelNrFixed]); displayPeakHold[channelNrFixed]);
else else
paintHMeter(painter, 5, channelNr * 4, width - 5, 3, paintHMeter(painter, INDICATOR_THICKNESS + 2,
channelNr * (meterThickness + 1),
width - (INDICATOR_THICKNESS + 2),
meterThickness,
displayMagnitude[channelNrFixed], displayMagnitude[channelNrFixed],
displayPeak[channelNrFixed], displayPeak[channelNrFixed],
displayPeakHold[channelNrFixed]); displayPeakHold[channelNrFixed]);
@@ -978,16 +1194,40 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
// see that the audio stream has been stopped, without // see that the audio stream has been stopped, without
// having too much visual impact. // having too much visual impact.
if (vertical) if (vertical)
paintInputMeter(painter, channelNr * 4, 3, 3, 3, paintInputMeter(painter,
channelNr * (meterThickness + 1), 0,
meterThickness, INDICATOR_THICKNESS,
displayInputPeakHold[channelNrFixed]); displayInputPeakHold[channelNrFixed]);
else else
paintInputMeter(painter, 0, channelNr * 4, 3, 3, paintInputMeter(painter, 0,
channelNr * (meterThickness + 1),
INDICATOR_THICKNESS, meterThickness,
displayInputPeakHold[channelNrFixed]); displayInputPeakHold[channelNrFixed]);
} }
lastRedrawTime = ts; lastRedrawTime = ts;
} }
QRect VolumeMeter::getBarRect() const
{
QRect rec = rect();
if (vertical)
rec.setWidth(displayNrAudioChannels * (meterThickness + 1) - 1);
else
rec.setHeight(displayNrAudioChannels * (meterThickness + 1) -
1);
return rec;
}
void VolumeMeter::changeEvent(QEvent *e)
{
if (e->type() == QEvent::StyleChange)
recalculateLayout = true;
QWidget::changeEvent(e);
}
void VolumeMeterTimer::AddVolControl(VolumeMeter *meter) void VolumeMeterTimer::AddVolControl(VolumeMeter *meter)
{ {
volumeMeters.push_back(meter); volumeMeters.push_back(meter);
@@ -1000,6 +1240,13 @@ void VolumeMeterTimer::RemoveVolControl(VolumeMeter *meter)
void VolumeMeterTimer::timerEvent(QTimerEvent *) void VolumeMeterTimer::timerEvent(QTimerEvent *)
{ {
for (VolumeMeter *meter : volumeMeters) for (VolumeMeter *meter : volumeMeters) {
meter->update(); if (meter->needLayoutChange()) {
// Tell paintEvent to update layout and paint everything
meter->update();
} else {
// Tell paintEvent to paint only the bars
meter->update(meter->getBarRect());
}
}
} }

View File

@@ -7,6 +7,8 @@
#include <QTimer> #include <QTimer>
#include <QMutex> #include <QMutex>
#include <QList> #include <QList>
#include <QSlider>
#include <QMenu>
class QPushButton; class QPushButton;
class VolumeMeterTimer; class VolumeMeterTimer;
@@ -25,6 +27,32 @@ class VolumeMeter : public QWidget {
WRITE setForegroundWarningColor DESIGNABLE true) WRITE setForegroundWarningColor DESIGNABLE true)
Q_PROPERTY(QColor foregroundErrorColor READ getForegroundErrorColor Q_PROPERTY(QColor foregroundErrorColor READ getForegroundErrorColor
WRITE setForegroundErrorColor DESIGNABLE true) WRITE setForegroundErrorColor DESIGNABLE true)
Q_PROPERTY(QColor backgroundNominalColorDisabled READ
getBackgroundNominalColorDisabled WRITE
setBackgroundNominalColorDisabled
DESIGNABLE true)
Q_PROPERTY(QColor backgroundWarningColorDisabled READ
getBackgroundWarningColorDisabled WRITE
setBackgroundWarningColorDisabled
DESIGNABLE true)
Q_PROPERTY(
QColor backgroundErrorColorDisabled READ
getBackgroundErrorColorDisabled WRITE
setBackgroundErrorColorDisabled DESIGNABLE true)
Q_PROPERTY(QColor foregroundNominalColorDisabled READ
getForegroundNominalColorDisabled WRITE
setForegroundNominalColorDisabled
DESIGNABLE true)
Q_PROPERTY(QColor foregroundWarningColorDisabled READ
getForegroundWarningColorDisabled WRITE
setForegroundWarningColorDisabled
DESIGNABLE true)
Q_PROPERTY(
QColor foregroundErrorColorDisabled READ
getForegroundErrorColorDisabled WRITE
setForegroundErrorColorDisabled DESIGNABLE true)
Q_PROPERTY(QColor clipColor READ getClipColor WRITE setClipColor Q_PROPERTY(QColor clipColor READ getClipColor WRITE setClipColor
DESIGNABLE true) DESIGNABLE true)
Q_PROPERTY(QColor magnitudeColor READ getMagnitudeColor WRITE Q_PROPERTY(QColor magnitudeColor READ getMagnitudeColor WRITE
@@ -33,6 +61,10 @@ class VolumeMeter : public QWidget {
setMajorTickColor DESIGNABLE true) setMajorTickColor DESIGNABLE true)
Q_PROPERTY(QColor minorTickColor READ getMinorTickColor WRITE Q_PROPERTY(QColor minorTickColor READ getMinorTickColor WRITE
setMinorTickColor DESIGNABLE true) setMinorTickColor DESIGNABLE true)
Q_PROPERTY(int meterThickness READ getMeterThickness WRITE
setMeterThickness DESIGNABLE true)
Q_PROPERTY(qreal meterFontScaling READ getMeterFontScaling WRITE
setMeterFontScaling DESIGNABLE true)
// Levels are denoted in dBFS. // Levels are denoted in dBFS.
Q_PROPERTY(qreal minimumLevel READ getMinimumLevel WRITE setMinimumLevel Q_PROPERTY(qreal minimumLevel READ getMinimumLevel WRITE setMinimumLevel
@@ -61,6 +93,8 @@ class VolumeMeter : public QWidget {
Q_PROPERTY(qreal inputPeakHoldDuration READ getInputPeakHoldDuration Q_PROPERTY(qreal inputPeakHoldDuration READ getInputPeakHoldDuration
WRITE setInputPeakHoldDuration DESIGNABLE true) WRITE setInputPeakHoldDuration DESIGNABLE true)
friend class VolControl;
private slots: private slots:
void ClipEnding(); void ClipEnding();
@@ -70,7 +104,7 @@ private:
QSharedPointer<VolumeMeterTimer> updateTimerRef; QSharedPointer<VolumeMeterTimer> updateTimerRef;
inline void resetLevels(); inline void resetLevels();
inline void handleChannelCofigurationChange(); inline void doLayout();
inline bool detectIdle(uint64_t ts); inline bool detectIdle(uint64_t ts);
inline void calculateBallistics(uint64_t ts, inline void calculateBallistics(uint64_t ts,
qreal timeSinceLastRedraw = 0.0); qreal timeSinceLastRedraw = 0.0);
@@ -81,20 +115,19 @@ private:
int height, float peakHold); int height, float peakHold);
void paintHMeter(QPainter &painter, int x, int y, int width, int height, void paintHMeter(QPainter &painter, int x, int y, int width, int height,
float magnitude, float peak, float peakHold); float magnitude, float peak, float peakHold);
void paintHTicks(QPainter &painter, int x, int y, int width, void paintHTicks(QPainter &painter, int x, int y, int width);
int height);
void paintVMeter(QPainter &painter, int x, int y, int width, int height, void paintVMeter(QPainter &painter, int x, int y, int width, int height,
float magnitude, float peak, float peakHold); float magnitude, float peak, float peakHold);
void paintVTicks(QPainter &painter, int x, int y, int height); void paintVTicks(QPainter &painter, int x, int y, int height);
QMutex dataMutex; QMutex dataMutex;
bool recalculateLayout = true;
uint64_t currentLastUpdateTime = 0; uint64_t currentLastUpdateTime = 0;
float currentMagnitude[MAX_AUDIO_CHANNELS]; float currentMagnitude[MAX_AUDIO_CHANNELS];
float currentPeak[MAX_AUDIO_CHANNELS]; float currentPeak[MAX_AUDIO_CHANNELS];
float currentInputPeak[MAX_AUDIO_CHANNELS]; float currentInputPeak[MAX_AUDIO_CHANNELS];
QPixmap *tickPaintCache = nullptr;
int displayNrAudioChannels = 0; int displayNrAudioChannels = 0;
float displayMagnitude[MAX_AUDIO_CHANNELS]; float displayMagnitude[MAX_AUDIO_CHANNELS];
float displayPeak[MAX_AUDIO_CHANNELS]; float displayPeak[MAX_AUDIO_CHANNELS];
@@ -110,10 +143,22 @@ private:
QColor foregroundNominalColor; QColor foregroundNominalColor;
QColor foregroundWarningColor; QColor foregroundWarningColor;
QColor foregroundErrorColor; QColor foregroundErrorColor;
QColor backgroundNominalColorDisabled;
QColor backgroundWarningColorDisabled;
QColor backgroundErrorColorDisabled;
QColor foregroundNominalColorDisabled;
QColor foregroundWarningColorDisabled;
QColor foregroundErrorColorDisabled;
QColor clipColor; QColor clipColor;
QColor magnitudeColor; QColor magnitudeColor;
QColor majorTickColor; QColor majorTickColor;
QColor minorTickColor; QColor minorTickColor;
int meterThickness;
qreal meterFontScaling;
qreal minimumLevel; qreal minimumLevel;
qreal warningLevel; qreal warningLevel;
qreal errorLevel; qreal errorLevel;
@@ -124,6 +169,13 @@ private:
qreal peakHoldDuration; qreal peakHoldDuration;
qreal inputPeakHoldDuration; qreal inputPeakHoldDuration;
QColor p_backgroundNominalColor;
QColor p_backgroundWarningColor;
QColor p_backgroundErrorColor;
QColor p_foregroundNominalColor;
QColor p_foregroundWarningColor;
QColor p_foregroundErrorColor;
uint64_t lastRedrawTime = 0; uint64_t lastRedrawTime = 0;
int channels = 0; int channels = 0;
bool clipping = false; bool clipping = false;
@@ -138,6 +190,8 @@ public:
void setLevels(const float magnitude[MAX_AUDIO_CHANNELS], void setLevels(const float magnitude[MAX_AUDIO_CHANNELS],
const float peak[MAX_AUDIO_CHANNELS], const float peak[MAX_AUDIO_CHANNELS],
const float inputPeak[MAX_AUDIO_CHANNELS]); const float inputPeak[MAX_AUDIO_CHANNELS]);
QRect getBarRect() const;
bool needLayoutChange();
QColor getBackgroundNominalColor() const; QColor getBackgroundNominalColor() const;
void setBackgroundNominalColor(QColor c); void setBackgroundNominalColor(QColor c);
@@ -151,6 +205,20 @@ public:
void setForegroundWarningColor(QColor c); void setForegroundWarningColor(QColor c);
QColor getForegroundErrorColor() const; QColor getForegroundErrorColor() const;
void setForegroundErrorColor(QColor c); void setForegroundErrorColor(QColor c);
QColor getBackgroundNominalColorDisabled() const;
void setBackgroundNominalColorDisabled(QColor c);
QColor getBackgroundWarningColorDisabled() const;
void setBackgroundWarningColorDisabled(QColor c);
QColor getBackgroundErrorColorDisabled() const;
void setBackgroundErrorColorDisabled(QColor c);
QColor getForegroundNominalColorDisabled() const;
void setForegroundNominalColorDisabled(QColor c);
QColor getForegroundWarningColorDisabled() const;
void setForegroundWarningColorDisabled(QColor c);
QColor getForegroundErrorColorDisabled() const;
void setForegroundErrorColorDisabled(QColor c);
QColor getClipColor() const; QColor getClipColor() const;
void setClipColor(QColor c); void setClipColor(QColor c);
QColor getMagnitudeColor() const; QColor getMagnitudeColor() const;
@@ -159,6 +227,10 @@ public:
void setMajorTickColor(QColor c); void setMajorTickColor(QColor c);
QColor getMinorTickColor() const; QColor getMinorTickColor() const;
void setMinorTickColor(QColor c); void setMinorTickColor(QColor c);
int getMeterThickness() const;
void setMeterThickness(int v);
qreal getMeterFontScaling() const;
void setMeterFontScaling(qreal v);
qreal getMinimumLevel() const; qreal getMinimumLevel() const;
void setMinimumLevel(qreal v); void setMinimumLevel(qreal v);
qreal getWarningLevel() const; qreal getWarningLevel() const;
@@ -182,6 +254,7 @@ public:
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void changeEvent(QEvent *e) override;
}; };
class VolumeMeterTimer : public QTimer { class VolumeMeterTimer : public QTimer {
@@ -200,7 +273,6 @@ protected:
class QLabel; class QLabel;
class QSlider; class QSlider;
class MuteCheckBox;
class VolControl : public QWidget { class VolControl : public QWidget {
Q_OBJECT Q_OBJECT
@@ -211,11 +283,13 @@ private:
QLabel *volLabel; QLabel *volLabel;
VolumeMeter *volMeter; VolumeMeter *volMeter;
QSlider *slider; QSlider *slider;
QPushButton *config = nullptr;
float levelTotal; float levelTotal;
float levelCount; float levelCount;
obs_fader_t *obs_fader; obs_fader_t *obs_fader;
obs_volmeter_t *obs_volmeter; obs_volmeter_t *obs_volmeter;
bool vertical; bool vertical;
QMenu *contextMenu;
static void OBSVolumeChanged(void *param, float db); static void OBSVolumeChanged(void *param, float db);
static void OBSVolumeLevel(void *data, static void OBSVolumeLevel(void *data,
@@ -223,12 +297,20 @@ private:
const float peak[MAX_AUDIO_CHANNELS], const float peak[MAX_AUDIO_CHANNELS],
const float inputPeak[MAX_AUDIO_CHANNELS]); const float inputPeak[MAX_AUDIO_CHANNELS]);
void EmitConfigClicked();
private slots: private slots:
void VolumeChanged();
void SliderChanged(int vol); void SliderChanged(int vol);
void updateText(); void updateText();
signals:
void ConfigClicked();
public: public:
explicit VolControl(OBSSource source, bool vertical = false); explicit VolControl(OBSSource source, bool showConfig = false,
bool vertical = false);
~VolControl(); ~VolControl();
inline obs_source_t *GetSource() const { return source; } inline obs_source_t *GetSource() const { return source; }
@@ -240,6 +322,19 @@ public:
void setPeakMeterType(enum obs_peak_meter_type peakMeterType); void setPeakMeterType(enum obs_peak_meter_type peakMeterType);
void EnableSlider(bool enable); void EnableSlider(bool enable);
inline void SetContextMenu(QMenu *cm) { contextMenu = cm; }
QSlider *GetSlider() const; void refreshColors();
QSlider *GetSlider() const { return slider; }
};
class VolumeSlider : public QSlider {
Q_OBJECT
public:
obs_fader_t *fad;
VolumeSlider(obs_fader_t *fader, QWidget *parent = nullptr);
VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation,
QWidget *parent = nullptr);
}; };

View File

@@ -13,6 +13,25 @@ using websocketpp::lib::bind;
obs_websocket_vendor vendor; obs_websocket_vendor vendor;
void ClearWebsocketMessages()
{
switcher->websocketMessages.clear();
for (auto &connection : switcher->connections) {
Connection *c = dynamic_cast<Connection *>(connection.get());
if (c) {
c->Events().clear();
}
}
}
void SendWebsocketEvent(const std::string &eventMsg)
{
auto data = obs_data_create();
obs_data_set_string(data, "message", eventMsg.c_str());
obs_websocket_vendor_emit_event(vendor, VendorEvent, data);
obs_data_release(data);
}
void ReceiveWebsocketMessage(obs_data_t *request_data, obs_data_t *, void *) void ReceiveWebsocketMessage(obs_data_t *request_data, obs_data_t *, void *)
{ {
if (!obs_data_has_user_value(request_data, "message")) { if (!obs_data_has_user_value(request_data, "message")) {
@@ -24,7 +43,7 @@ void ReceiveWebsocketMessage(obs_data_t *request_data, obs_data_t *, void *)
auto msg = obs_data_get_string(request_data, "message"); auto msg = obs_data_get_string(request_data, "message");
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
switcher->websocketMessages.emplace_back(msg); switcher->websocketMessages.emplace_back(msg);
vblog(LOG_INFO, "received messsage: %s", msg); vblog(LOG_INFO, "received message: %s", msg);
} }
extern "C" void RegisterWebsocketVendor() extern "C" void RegisterWebsocketVendor()
@@ -232,6 +251,31 @@ void WSConnection::HandleHello(obs_data_t *helloMsg)
Send(response); Send(response);
} }
void WSConnection::HandleEvent(obs_data_t *msg)
{
auto d = obs_data_get_obj(msg, "d");
auto eventData = obs_data_get_obj(d, "eventData");
if (strcmp(obs_data_get_string(eventData, "vendorName"), VendorName) !=
0) {
vblog(LOG_INFO, "ignoring vendor event from \"%s\"",
obs_data_get_string(eventData, "vendorName"));
return;
}
if (strcmp(obs_data_get_string(eventData, "eventType"), VendorEvent) !=
0) {
vblog(LOG_INFO, "ignoring event type\"%s\"",
obs_data_get_string(eventData, "eventType"));
return;
}
auto eventDataNested = obs_data_get_obj(eventData, "eventData");
_messages.emplace_back(obs_data_get_string(eventDataNested, "message"));
vblog(LOG_INFO, "received event msg \"%s\"",
obs_data_get_string(eventDataNested, "message"));
obs_data_release(eventDataNested);
obs_data_release(eventData);
obs_data_release(d);
}
void WSConnection::HandleResponse(obs_data_t *response) void WSConnection::HandleResponse(obs_data_t *response)
{ {
auto data = obs_data_get_obj(response, "d"); auto data = obs_data_get_obj(response, "d");
@@ -278,11 +322,14 @@ void WSConnection::OnMessage(connection_hdl, client::message_ptr message)
case 2: // Identified case 2: // Identified
_status = Status::AUTHENTICATED; _status = Status::AUTHENTICATED;
break; break;
case 5: // Event (Vendor)
HandleEvent(json);
break;
case 7: // RequestResponse case 7: // RequestResponse
HandleResponse(json); HandleResponse(json);
break; break;
default: default:
vblog(LOG_INFO, "ignoring unkown opcode %d", opcode); vblog(LOG_INFO, "ignoring unknown opcode %d", opcode);
break; break;
} }
obs_data_release(json); obs_data_release(json);

View File

@@ -24,6 +24,10 @@ typedef websocketpp::client<websocketpp::config::asio_client> client;
constexpr char VendorName[] = "AdvancedSceneSwitcher"; constexpr char VendorName[] = "AdvancedSceneSwitcher";
constexpr char VendorRequest[] = "AdvancedSceneSwitcherMessage"; constexpr char VendorRequest[] = "AdvancedSceneSwitcherMessage";
constexpr char VendorEvent[] = "AdvancedSceneSwitcherEvent";
void ClearWebsocketMessages();
void SendWebsocketEvent(const std::string &);
class WSConnection : public QObject { class WSConnection : public QObject {
public: public:
@@ -34,6 +38,7 @@ public:
bool _reconnect, int reconnectDelay = 10); bool _reconnect, int reconnectDelay = 10);
void Disconnect(); void Disconnect();
void SendRequest(const std::string &msg); void SendRequest(const std::string &msg);
std::vector<std::string> &Events() { return _messages; }
std::string GetFail() { return _failMsg; } std::string GetFail() { return _failMsg; }
enum class Status { enum class Status {
@@ -51,6 +56,7 @@ private:
void Send(const std::string &); void Send(const std::string &);
void ConnectThread(); void ConnectThread();
void HandleHello(obs_data_t *helloMsg); void HandleHello(obs_data_t *helloMsg);
void HandleEvent(obs_data_t *event);
void HandleResponse(obs_data_t *response); void HandleResponse(obs_data_t *response);
client _client; client _client;
@@ -66,4 +72,6 @@ private:
std::string _failMsg = ""; std::string _failMsg = "";
std::atomic<Status> _status = {Status::DISCONNECTED}; std::atomic<Status> _status = {Status::DISCONNECTED};
std::atomic_bool _disconnect{false}; std::atomic_bool _disconnect{false};
std::vector<std::string> _messages;
}; };