Compare commits

..

57 Commits

Author SHA1 Message Date
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 4698 additions and 1293 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

3
.gitmodules vendored
View File

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

View File

@@ -6,6 +6,8 @@ You have the option to ...
As both methods require you to have a working [OBS Studio development environment](https://obsproject.com/wiki/Building-OBS-Studio) and [CMake](https://cmake.org/download/) it is recommended to build the plugin in tree as it is easier to set up and will enable straightforward debugging.
The plugin can be compiled for OBS 25 and above, although using the latest version of OBS is recommended to support all features.
## Compiling in tree (recommended for development)
Add the "SceneSwitcher" source directory to your obs-studio source directory under obs-studio/UI/frontend-plugins/:
```
@@ -25,6 +27,7 @@ Note that on Linux systems it might be necessary to additionally install the fol
sudo apt-get install \
libxtst-dev \
libxss-dev \
libprocps-dev \
libopencv-dev
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -83,7 +83,8 @@ AdvSceneSwitcher.macroTab.minimize="Minimize"
AdvSceneSwitcher.macroTab.highlightExecutedMacros="Highlight recently executed macros"
AdvSceneSwitcher.macroTab.highlightTrueConditions="Highlight conditions of currently selected macro that evaluated to true recently"
AdvSceneSwitcher.macroTab.highlightPerformedActions="Highlight recently performed actions of currently selected macro"
AdvSceneSwitcher.macroTab.disableHotkeys="Register hotkeys to control pause state of selected macro"
AdvSceneSwitcher.macroTab.newMacroRegisterHotkey="Register hotkeys to control pause state of new macros"
AdvSceneSwitcher.macroTab.currentDisableHotkeys="Register hotkeys to control pause state of selected macro"
; Macro List
AdvSceneSwitcher.macroList.deleted="deleted"
@@ -128,7 +129,10 @@ AdvSceneSwitcher.condition.window="Window"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} exist and ..."
AdvSceneSwitcher.condition.window.entry.line2="... is {{fullscreen}} fullscreen {{maximized}} maximized {{focused}} focused {{windowFocusChanged}} foreground window changed"
AdvSceneSwitcher.condition.file="File"
AdvSceneSwitcher.condition.file.entry.line1="Content of {{fileType}} {{filePath}} matches:"
AdvSceneSwitcher.condition.file.type.match="matches"
AdvSceneSwitcher.condition.file.type.contentChange="content changed"
AdvSceneSwitcher.condition.file.type.dateChange="modification date changed"
AdvSceneSwitcher.condition.file.entry.line1="Content of{{fileType}}{{filePath}}{{conditions}}"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.condition.media="Media"
@@ -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.file="Use existing file"
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot"
AdvSceneSwitcher.condition.video.reduceLatency="Reduce matching latency"
AdvSceneSwitcher.condition.video.reduceLatency.tooltip="Enabling will reduce the matching latency, but will slow down the condition checks of all macros overall."
AdvSceneSwitcher.condition.video.usePatternForChangedCheck="Use pattern matching"
AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip="This will allow you to control how much the image has to change for the condition to be true."
AdvSceneSwitcher.condition.video.patternThreshold="Threshold: "
@@ -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.minNeighbor="Minimum neighbors: {{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU load by performing check only every {{throttleCount}} milliseconds"
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}Perform check only in area {{checkArea}} {{selectArea}}"
AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Perform check only in area"
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
AdvSceneSwitcher.condition.video.minSize="Minimum size:"
AdvSceneSwitcher.condition.video.maxSize="Maximum size:"
AdvSceneSwitcher.condition.video.selectArea="Select area"
@@ -227,7 +234,6 @@ AdvSceneSwitcher.condition.source="Source"
AdvSceneSwitcher.condition.source.type.active="Is active"
AdvSceneSwitcher.condition.source.type.showing="Is showing"
AdvSceneSwitcher.condition.source.type.settings="Settings match"
AdvSceneSwitcher.condition.source.regex="Use regular expressions"
AdvSceneSwitcher.condition.source.getSettings="Get current settings"
AdvSceneSwitcher.condition.source.entry.line1="{{sources}} {{conditions}}"
AdvSceneSwitcher.condition.source.entry.line2="{{settings}}"
@@ -240,7 +246,6 @@ AdvSceneSwitcher.condition.filter="Filter"
AdvSceneSwitcher.condition.filter.type.active="Is enabled"
AdvSceneSwitcher.condition.filter.type.showing="Is disabled"
AdvSceneSwitcher.condition.filter.type.settings="Settings match"
AdvSceneSwitcher.condition.filter.regex="Use regular expressions"
AdvSceneSwitcher.condition.filter.getSettings="Get current settings"
AdvSceneSwitcher.condition.filter.entry.line1="On {{sources}} {{filters}} {{conditions}}"
AdvSceneSwitcher.condition.filter.entry.line2="{{settings}}"
@@ -286,7 +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.sceneTransform="Scene item transform"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
AdvSceneSwitcher.condition.sceneTransform.regex="Use regular expressions"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="On{{scenes}}{{sources}}matches transform"
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}"
@@ -335,12 +339,25 @@ AdvSceneSwitcher.condition.stats.entry="{{stats}} is {{condition}} {{value}}"
AdvSceneSwitcher.condition.profile="Profile"
AdvSceneSwitcher.condition.profile.entry="Current active profile is {{profiles}}"
AdvSceneSwitcher.condition.websocket="Websocket"
AdvSceneSwitcher.condition.websocket.type.request="Request"
AdvSceneSwitcher.condition.websocket.type.event="Event"
AdvSceneSwitcher.condition.websocket.useRegex="Use regular expressions"
AdvSceneSwitcher.condition.websocket.entry="Message was received:"
AdvSceneSwitcher.condition.websocket.entry.request="{{type}} was received:"
AdvSceneSwitcher.condition.websocket.entry.event="{{type}} was received from {{connection}}:"
AdvSceneSwitcher.condition.variable="Variable"
AdvSceneSwitcher.condition.variable.type.compare="equals"
AdvSceneSwitcher.condition.variable.type.empty="is empty"
AdvSceneSwitcher.condition.variable.type.number="is a number"
AdvSceneSwitcher.condition.variable.type.lessThan="is less than"
AdvSceneSwitcher.condition.variable.type.greaterThan="is greater than"
AdvSceneSwitcher.condition.variable.type.valueChanged="value changed"
AdvSceneSwitcher.condition.variable.regex="Use regular expressions"
AdvSceneSwitcher.condition.variable.entry="{{variables}}{{conditions}}{{strValue}}{{numValue}}"
; Macro Actions
AdvSceneSwitcher.action.switchScene="Switch scene"
AdvSceneSwitcher.action.scene.entry="Switch to scene {{scenes}} using {{transitions}} with a duration of {{duration}} seconds"
AdvSceneSwitcher.action.scene.entry="Switch to scene{{scenes}}using{{transitions}}with a duration of{{duration}}seconds"
AdvSceneSwitcher.action.scene.entry.noDuration="Switch to scene{{scenes}}using{{transitions}}"
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Wait until transition to target scene is complete"
AdvSceneSwitcher.action.wait="Wait"
AdvSceneSwitcher.action.wait.type.fixed="fixed"
@@ -481,8 +498,13 @@ AdvSceneSwitcher.action.random.entry="Randomly run any of the following macros (
AdvSceneSwitcher.action.systray="System tray notification"
AdvSceneSwitcher.action.systray.entry="Show notification: {{message}}"
AdvSceneSwitcher.action.screenshot="Screenshot"
AdvSceneSwitcher.action.screenshot.save.default="Default"
AdvSceneSwitcher.action.screenshot.save.custom="Custom"
AdvSceneSwitcher.action.screenshot.type.source="Source"
AdvSceneSwitcher.action.screenshot.type.scene="Scene"
AdvSceneSwitcher.action.screenshot.mainOutput="OBS's main output"
AdvSceneSwitcher.action.screenshot.entry="Screenshot {{sources}}"
AdvSceneSwitcher.action.screenshot.blackscreenNote="Sources or scenes, which are not always rendered, may result in some parts of screenshots to remain blank."
AdvSceneSwitcher.action.screenshot.entry="Screenshot{{targetType}}{{sources}}{{scenes}}and save to{{saveType}}location"
AdvSceneSwitcher.action.profile="Profile"
AdvSceneSwitcher.action.profile.entry="Switch active profile to {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="Scene collection"
@@ -495,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.continueFrom="Continue with selected item"
AdvSceneSwitcher.action.websocket="Websocket"
AdvSceneSwitcher.action.websocket.entry="Send scene switcher message via {{connection}}"
AdvSceneSwitcher.action.websocket.type.request="Request"
AdvSceneSwitcher.action.websocket.type.event="Event"
AdvSceneSwitcher.action.websocket.entry.request="Send scene switcher {{type}} via {{connection}}"
AdvSceneSwitcher.action.websocket.entry.event="Send scene switcher {{type}} to connected clients"
AdvSceneSwitcher.action.http="Http"
AdvSceneSwitcher.action.http.type.get="GET"
AdvSceneSwitcher.action.http.type.post="POST"
AdvSceneSwitcher.action.http.entry.line1="Send {{method}} to {{url}}"
AdvSceneSwitcher.action.http.entry.line2="Timeout: {{timeout}} seconds"
AdvSceneSwitcher.action.variable="Variable"
AdvSceneSwitcher.action.variable.type.set="Set"
AdvSceneSwitcher.action.variable.type.append="Append"
AdvSceneSwitcher.action.variable.type.appendVar="Append variable"
AdvSceneSwitcher.action.variable.type.increment="Increment"
AdvSceneSwitcher.action.variable.type.decrement="Decrement"
AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}"
; Transition Tab
@@ -757,15 +789,27 @@ AdvSceneSwitcher.askForMacro="Select macro {{macroSelection}}"
AdvSceneSwitcher.close="Close"
AdvSceneSwitcher.browse="Browse"
AdvSceneSwitcher.item.select="--select item--"
AdvSceneSwitcher.item.add="Add new item"
AdvSceneSwitcher.item.rename="Rename"
AdvSceneSwitcher.item.remove="Remove"
AdvSceneSwitcher.item.properties="Properties"
AdvSceneSwitcher.item.newName="New name:"
AdvSceneSwitcher.item.emptyName="Empty name not allowed!"
AdvSceneSwitcher.item.nameNotAvailable="Name not available!"
AdvSceneSwitcher.item.nameReserved="Name reserved!"
AdvSceneSwitcher.variable.select="--select variable--"
AdvSceneSwitcher.variable.add="Add new variable"
AdvSceneSwitcher.variable.name="Name:"
AdvSceneSwitcher.variable.value="Current value:"
AdvSceneSwitcher.variable.save="Save / load behavior"
AdvSceneSwitcher.variable.save.dontSave="Don't save variable value"
AdvSceneSwitcher.variable.save.save="Save variable value"
AdvSceneSwitcher.variable.save.default="Set to value"
AdvSceneSwitcher.connection.select="--select connection--"
AdvSceneSwitcher.connection.add="Add new connection"
AdvSceneSwitcher.connection.rename="Rename"
AdvSceneSwitcher.connection.remove="Remove"
AdvSceneSwitcher.connection.properties="Properties"
AdvSceneSwitcher.connection.newName="New name:"
AdvSceneSwitcher.connection.emptyName="Empty name not allowed!"
AdvSceneSwitcher.connection.nameNotAvailable="Name not available!"
AdvSceneSwitcher.connection.nameReserved="Name reserved!"
AdvSceneSwitcher.connection.name="Name:"
AdvSceneSwitcher.connection.address="Address:"
AdvSceneSwitcher.connection.port="Port:"
@@ -779,9 +823,17 @@ AdvSceneSwitcher.connection.status.connecting="Connecting"
AdvSceneSwitcher.connection.status.connected="Connected, but not authenticated"
AdvSceneSwitcher.connection.status.authenticated="Connected and authenticated"
AdvSceneSwitcher.regex.enable="Enable regular expressions"
AdvSceneSwitcher.regex.partialMatch="Allow partial match"
AdvSceneSwitcher.regex.caseInsensitive="Match case insensitive"
AdvSceneSwitcher.regex.dotMatchNewline=". matches newlines"
AdvSceneSwitcher.regex.multiLine="^ and $ match start/end of line"
AdvSceneSwitcher.regex.extendedPattern="Enable Qt's ExtendedPatternSyntax"
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene"
AdvSceneSwitcher.selectPreviewScene="Preview Scene"
AdvSceneSwitcher.selectAnyScene="Any Scene"
AdvSceneSwitcher.currentTransition="Current Transition"
AdvSceneSwitcher.anyTransition="Any Transition"
@@ -790,7 +842,7 @@ AdvSceneSwitcher.selectWindow="--select window--"
AdvSceneSwitcher.selectSource="--select source--"
AdvSceneSwitcher.selectAudioSource="--select audio source--"
AdvSceneSwitcher.selectVideoSource="--select video source--"
AdvSceneSwitcher.OBSVideoOutput="OBS video output"
AdvSceneSwitcher.OBSVideoOutput="OBS's main output"
AdvSceneSwitcher.selectMediaSource="--select media source--"
AdvSceneSwitcher.selectProcess="--select process--"
AdvSceneSwitcher.selectFilter="--select filter--"

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -40,7 +40,7 @@ public:
{
return std::make_shared<MacroConditionVideo>(m);
}
void GetScreenshot();
void GetScreenshot(bool blocking = false);
bool LoadImageFromFile();
bool LoadModelData(std::string &path);
std::string GetModelDataPath() { return _modelDataPath; }
@@ -49,6 +49,13 @@ public:
VideoSelection _video;
VideoCondition _condition = VideoCondition::MATCH;
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
// Enabling this will reduce matching latency, but slow down the
// the condition checks of all macros overall.
//
// If not set the screenshot will be gathered in one interval and
// checked in the next one.
// If set both operations will happen in the same interval.
bool _blockUntilScreenshotDone = false;
bool _useAlphaAsMask = false;
bool _usePatternForChangedCheck = false;
PatternMatchData _patternData;
@@ -107,8 +114,11 @@ public:
private slots:
void VideoSelectionChanged(const VideoSelection &);
void ConditionChanged(int cond);
void ReduceLatencyChanged(int value);
void ImagePathChanged(const QString &text);
void ImageBrowseButtonClicked();
void UsePatternForChangedCheckChanged(int value);
void PatternThresholdChanged(double);
void UseAlphaAsMaskChanged(int value);
@@ -134,8 +144,11 @@ protected:
VideoSelectionWidget *_videoSelection;
QComboBox *_condition;
QCheckBox *_reduceLatency;
QCheckBox *_usePatternForChangedCheck;
FileSelection *_imagePath;
ThresholdSlider *_patternThreshold;
QCheckBox *_useAlphaAsMask;

View File

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

View File

@@ -72,6 +72,7 @@ typedef struct transitionData {
} transitionData;
class Item;
class SwitcherThread;
/********************************************************************************
@@ -138,9 +139,11 @@ struct SwitcherData {
Curlhelper curl;
std::deque<Connection> connections;
std::deque<std::shared_ptr<Item>> connections;
std::vector<std::string> websocketMessages;
std::deque<std::shared_ptr<Item>> variables;
std::deque<WindowSwitch> windowSwitches;
std::vector<std::string> ignoreIdleWindows;
std::string lastTitle;
@@ -291,6 +294,7 @@ struct SwitcherData {
void saveSettings(obs_data_t *obj);
void saveMacros(obs_data_t *obj);
void saveConnections(obs_data_t *obj);
void saveVariables(obs_data_t *obj);
void saveWindowTitleSwitches(obs_data_t *obj);
void saveScreenRegionSwitches(obs_data_t *obj);
void savePauseSwitches(obs_data_t *obj);
@@ -315,6 +319,7 @@ struct SwitcherData {
void loadSettings(obs_data_t *obj);
void loadMacros(obs_data_t *obj);
void loadConnections(obs_data_t *obj);
void loadVariables(obs_data_t *obj);
void loadWindowTitleSwitches(obs_data_t *obj);
void loadScreenRegionSwitches(obs_data_t *obj);
void loadPauseSwitches(obs_data_t *obj);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,11 +3,16 @@
#include <string>
#include <QImage>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
class ScreenshotHelper {
public:
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(const ScreenshotHelper &) = delete;
~ScreenshotHelper();
@@ -16,6 +21,7 @@ public:
void Download();
void Copy();
void MarkDone();
void WriteToFile();
gs_texrender_t *texrender = nullptr;
gs_stagesurf_t *stagesurf = nullptr;
@@ -31,4 +37,10 @@ public:
private:
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));
switch (_type) {
case TransitionSelectionType::TRANSITION:
case Type::TRANSITION:
obs_data_set_string(obj, name,
GetWeakSourceName(_transition).c_str());
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,
const char *typeName)
{
_type = static_cast<TransitionSelectionType>(
obs_data_get_int(obj, typeName));
_type = static_cast<Type>(obs_data_get_int(obj, typeName));
auto target = obs_data_get_string(obj, name);
switch (_type) {
case TransitionSelectionType::TRANSITION:
case Type::TRANSITION:
_transition = GetWeakTransitionByName(target);
break;
default:
@@ -34,9 +33,9 @@ void TransitionSelection::Load(obs_data_t *obj, const char *name,
OBSWeakSource TransitionSelection::GetTransition()
{
switch (_type) {
case TransitionSelectionType::TRANSITION:
case Type::TRANSITION:
return _transition;
case TransitionSelectionType::CURRENT: {
case Type::CURRENT: {
auto source = obs_frontend_get_current_transition();
auto weakSource = obs_source_get_weak_source(source);
obs_weak_source_release(weakSource);
@@ -52,11 +51,11 @@ OBSWeakSource TransitionSelection::GetTransition()
std::string TransitionSelection::ToString()
{
switch (_type) {
case TransitionSelectionType::TRANSITION:
case Type::TRANSITION:
return GetWeakSourceName(_transition);
case TransitionSelectionType::CURRENT:
case Type::CURRENT:
return obs_module_text("AdvSceneSwitcher.currentTransition");
case TransitionSelectionType::ANY:
case Type::ANY:
return obs_module_text("AdvSceneSwitcher.anyTransition");
default:
break;
@@ -85,17 +84,17 @@ void TransitionSelectionWidget::SetTransition(TransitionSelection &t)
int idx;
switch (t.GetType()) {
case TransitionSelectionType::TRANSITION:
case TransitionSelection::Type::TRANSITION:
setCurrentText(QString::fromStdString(t.ToString()));
break;
case TransitionSelectionType::CURRENT:
case TransitionSelection::Type::CURRENT:
idx = findText(QString::fromStdString(
obs_module_text("AdvSceneSwitcher.currentTransition")));
if (idx != -1) {
setCurrentIndex(idx);
}
break;
case TransitionSelectionType::ANY:
case TransitionSelection::Type::ANY:
idx = findText(QString::fromStdString(
obs_module_text("AdvSceneSwitcher.anyTransition")));
if (idx != -1) {
@@ -155,16 +154,16 @@ void TransitionSelectionWidget::SelectionChanged(const QString &name)
TransitionSelection t;
auto transition = GetWeakTransitionByQString(name);
if (transition) {
t._type = TransitionSelectionType::TRANSITION;
t._type = TransitionSelection::Type::TRANSITION;
t._transition = transition;
}
if (!transition) {
if (IsCurrentTransitionSelected(name)) {
t._type = TransitionSelectionType::CURRENT;
t._type = TransitionSelection::Type::CURRENT;
}
if (IsAnyTransitionSelected(name)) {
t._type = TransitionSelectionType::ANY;
t._type = TransitionSelection::Type::ANY;
}
}

View File

@@ -3,12 +3,6 @@
#include <QComboBox>
enum class TransitionSelectionType {
TRANSITION,
CURRENT,
ANY,
};
class TransitionSelection {
public:
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",
const char *typeName = "transitionType");
TransitionSelectionType GetType() { return _type; }
enum class Type {
TRANSITION,
CURRENT,
ANY,
};
Type GetType() { return _type; }
OBSWeakSource GetTransition();
std::string ToString();
private:
OBSWeakSource _transition;
TransitionSelectionType _type = TransitionSelectionType::TRANSITION;
Type _type = Type::TRANSITION;
friend class TransitionSelectionWidget;
};

View File

@@ -1,6 +1,7 @@
#include "utility.hpp"
#include "platform-funcs.hpp"
#include "scene-selection.hpp"
#include "regex-config.hpp"
#include <QTextStream>
#include <QLabel>
@@ -301,41 +302,10 @@ void setSourceSettings(obs_source_t *s, const std::string &settings)
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
bool matchJson(const std::string &json1, const std::string &json2,
bool useRegex)
const RegexConfig &regex)
{
bool ret = false;
auto j1 = formatJsonString(json1).toStdString();
auto j2 = formatJsonString(json2).toStdString();
@@ -346,24 +316,23 @@ bool matchJson(const std::string &json1, const std::string &json2,
j2 = json2;
}
if (useRegex) {
try {
std::regex expr(j2);
ret = std::regex_match(
j1, expr, std::regex_constants::match_default);
} catch (const std::regex_error &) {
if (regex.Enabled()) {
auto expr = regex.GetRegularExpression(j2);
if (!expr.isValid()) {
return false;
}
} else {
ret = j1 == j2;
auto match = expr.match(QString::fromStdString(j1));
return match.hasMatch();
}
return ret;
return j1 == j2;
}
bool compareSourceSettings(const OBSWeakSource &source,
const std::string &settings, bool useRegex)
const std::string &settings,
const RegexConfig &regex)
{
std::string currentSettings = getSourceSettings(source);
return matchJson(currentSettings, settings, useRegex);
return matchJson(currentSettings, settings, regex);
}
std::string getDataFilePath(const std::string &file)
@@ -652,7 +621,8 @@ void populateMediaSelection(QComboBox *sel, bool addSelect)
reinterpret_cast<std::vector<std::string> *>(data);
std::string sourceId = obs_source_get_id(source);
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));
}
return true;
@@ -793,68 +763,6 @@ void populateFilterSelection(QComboBox *list, OBSWeakSource weakSource)
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)
{
std::set<QString> sourceTypeNames;
@@ -1020,3 +928,41 @@ bool doubleEquals(double left, double right, double 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"
class SceneSelection;
class RegexConfig;
bool WeakSourceValid(obs_weak_source_t *ws);
std::string GetWeakSourceName(obs_weak_source_t *weak_source);
@@ -26,12 +27,13 @@ bool compareIgnoringLineEnding(QString &s1, QString &s2);
std::string getSourceSettings(OBSWeakSource ws);
void setSourceSettings(obs_source_t *s, const std::string &settings);
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::string &name);
std::string getDataFilePath(const std::string &file);
bool matchJson(const std::string &json1, const std::string &json2,
bool useRegex);
const RegexConfig &regex);
QString formatJsonString(std::string);
QString formatJsonString(QString);
QString escapeForRegex(QString &s);
@@ -77,10 +79,12 @@ void populateSceneSelection(QComboBox *sel, bool addPrevious = false,
void populateSourcesWithFilterSelection(QComboBox *list);
void populateFilterSelection(QComboBox *list,
OBSWeakSource weakSource = nullptr);
void populateSceneItemSelection(QComboBox *list,
OBSWeakSource sceneWeakSource = nullptr);
void populateSceneItemSelection(QComboBox *list, SceneSelection &s);
void populateSourceGroupSelection(QComboBox *list);
void populateProfileSelection(QComboBox *list);
bool windowPosValid(QPoint pos);
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 <QLabel>
#include <QPainter>
#include <QStyle>
#include <QStyleFactory>
#include <QSlider>
#include <util/platform.h>
using namespace std;
#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;
void VolControl::OBSVolumeChanged(void *data, float db)
{
Q_UNUSED(db);
Q_UNUSED(data);
}
void VolControl::OBSVolumeChanged(void *, float) {}
void VolControl::OBSVolumeLevel(void *data,
const float magnitude[MAX_AUDIO_CHANNELS],
@@ -33,9 +34,19 @@ void VolControl::OBSVolumeLevel(void *data,
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();
}
@@ -61,6 +72,12 @@ void VolControl::SetName(const QString &newName)
{
nameLabel->setText(newName);
}
void VolControl::EmitConfigClicked()
{
emit ConfigClicked();
}
void VolControl::SetMeterDecayRate(qreal q)
{
volMeter->setPeakDecayRate(q);
@@ -71,23 +88,46 @@ void VolControl::setPeakMeterType(enum obs_peak_meter_type 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_)),
levelTotal(0.0f),
levelCount(0.0f),
obs_fader(obs_fader_create(OBS_FADER_LOG)),
obs_volmeter(obs_volmeter_create(OBS_FADER_LOG)),
vertical(vertical)
vertical(vertical),
contextMenu(nullptr)
{
nameLabel = new QLabel();
volLabel = new QLabel();
QString sourceName = "";
if (source) {
obs_source_get_name(source);
}
QString sourceName = obs_source_get_name(source);
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;
mainLayout->setContentsMargins(4, 4, 4, 4);
mainLayout->setSpacing(2);
@@ -99,7 +139,7 @@ VolControl::VolControl(OBSSource source_, bool vertical)
QHBoxLayout *meterLayout = new QHBoxLayout;
volMeter = new VolumeMeter(nullptr, obs_volmeter, true);
slider = new QSlider(Qt::Vertical);
slider = new VolumeSlider(obs_fader, Qt::Vertical);
nameLayout->setAlignment(Qt::AlignCenter);
meterLayout->setAlignment(Qt::AlignCenter);
@@ -113,7 +153,11 @@ VolControl::VolControl(OBSSource source_, bool vertical)
controlLayout->setContentsMargins(0, 0, 0, 0);
controlLayout->setSpacing(0);
if (showConfig)
controlLayout->addWidget(config);
controlLayout->addItem(new QSpacerItem(3, 0));
// Add Headphone (audio monitoring) widget here
meterLayout->setContentsMargins(0, 0, 0, 0);
meterLayout->setSpacing(0);
@@ -131,6 +175,12 @@ VolControl::VolControl(OBSSource source_, bool vertical)
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);
} else {
QHBoxLayout *volLayout = new QHBoxLayout;
@@ -138,7 +188,7 @@ VolControl::VolControl(OBSSource source_, bool vertical)
QHBoxLayout *botLayout = new QHBoxLayout;
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->addWidget(nameLabel);
@@ -153,6 +203,9 @@ VolControl::VolControl(OBSSource source_, bool vertical)
botLayout->setSpacing(0);
botLayout->addLayout(volLayout);
if (showConfig)
botLayout->addWidget(config);
mainLayout->addItem(textLayout);
mainLayout->addWidget(volMeter);
mainLayout->addItem(botLayout);
@@ -162,15 +215,10 @@ VolControl::VolControl(OBSSource source_, bool vertical)
setLayout(mainLayout);
QFont font = nameLabel->font();
font.setPointSize(font.pointSize() - 1);
nameLabel->setText(sourceName);
nameLabel->setFont(font);
volLabel->setFont(font);
slider->setMinimum(0);
slider->setMaximum(100);
slider->setMaximum(int(FADER_PRECISION));
obs_fader_add_callback(obs_fader, OBSVolumeChanged, 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_volmeter_attach_source(obs_volmeter, source);
/* Call volume changed once to init the slider position and label */
VolumeChanged();
}
void VolControl::EnableSlider(bool enable)
@@ -187,11 +238,6 @@ void VolControl::EnableSlider(bool enable)
slider->setEnabled(enable);
}
QSlider *VolControl::GetSlider() const
{
return slider;
}
VolControl::~VolControl()
{
obs_fader_remove_callback(obs_fader, OBSVolumeChanged, this);
@@ -199,66 +245,143 @@ VolControl::~VolControl()
obs_fader_destroy(obs_fader);
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
{
return backgroundNominalColor;
return p_backgroundNominalColor;
}
QColor VolumeMeter::getBackgroundNominalColorDisabled() const
{
return backgroundNominalColorDisabled;
}
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
{
return backgroundWarningColor;
return p_backgroundWarningColor;
}
QColor VolumeMeter::getBackgroundWarningColorDisabled() const
{
return backgroundWarningColorDisabled;
}
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
{
return backgroundErrorColor;
return p_backgroundErrorColor;
}
QColor VolumeMeter::getBackgroundErrorColorDisabled() const
{
return backgroundErrorColorDisabled;
}
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
{
return foregroundNominalColor;
return p_foregroundNominalColor;
}
QColor VolumeMeter::getForegroundNominalColorDisabled() const
{
return foregroundNominalColorDisabled;
}
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
{
return foregroundWarningColor;
return p_foregroundWarningColor;
}
QColor VolumeMeter::getForegroundWarningColorDisabled() const
{
return foregroundWarningColorDisabled;
}
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
{
return foregroundErrorColor;
return p_foregroundErrorColor;
}
QColor VolumeMeter::getForegroundErrorColorDisabled() const
{
return foregroundErrorColorDisabled;
}
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
@@ -301,6 +424,42 @@ void VolumeMeter::setMinorTickColor(QColor 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
{
return minimumLevel;
@@ -434,10 +593,9 @@ VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter,
bool vertical)
: QWidget(parent), obs_volmeter(obs_volmeter), vertical(vertical)
{
// Use a font that can be rendered small.
tickFont = QFont("Arial");
tickFont.setPixelSize(7);
// Default meter color settings, they only show if
setAttribute(Qt::WA_OpaquePaintEvent, true);
// Default meter settings, they only show if
// there is no stylesheet, do not remove.
backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green
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
foregroundWarningColor.setRgb(0xff, 0xff, 0x4c); // Bright yellow
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());
handleChannelCofigurationChange();
doLayout();
updateTimerRef = updateTimer.toStrongRef();
if (!updateTimerRef) {
updateTimerRef = QSharedPointer<VolumeMeterTimer>::create();
updateTimerRef->start(34);
updateTimerRef->setTimerType(Qt::PreciseTimer);
updateTimerRef->start(16);
updateTimer = updateTimerRef;
}
@@ -475,7 +644,6 @@ VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter,
VolumeMeter::~VolumeMeter()
{
updateTimerRef->RemoveVolControl(this);
delete tickPaintCache;
}
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);
int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter);
if (displayNrAudioChannels != currentNrAudioChannels) {
displayNrAudioChannels = currentNrAudioChannels;
recalculateLayout = false;
// Make room for 3 pixels meter, with one pixel between each.
// Then 9/13 pixels for ticks and numbers.
if (vertical)
setMinimumSize(displayNrAudioChannels * 4 + 14, 130);
else
setMinimumSize(130, displayNrAudioChannels * 4 + 8);
resetLevels();
tickFont = font();
QFontInfo info(tickFont);
tickFont.setPointSizeF(info.pointSizeF() * meterFontScaling);
QFontMetrics metrics(tickFont);
if (vertical) {
// Each meter channel is meterThickness pixels wide, plus one pixel
// between channels, but not after the last.
// 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)
@@ -649,12 +852,12 @@ void VolumeMeter::paintInputMeter(QPainter &painter, int x, int y, int width,
painter.fillRect(x, y, width, height, color);
}
void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width,
int height)
void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width)
{
qreal scale = width / minimumLevel;
painter.setFont(tickFont);
QFontMetrics metrics(tickFont);
painter.setPen(majorTickColor);
// 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);
QString str = QString::number(i);
if (i == 0 || i == -5)
painter.drawText(position - 3, height, str);
else
painter.drawText(position - 5, height, str);
// Center the number on the tick, but don't overflow
QRect textBounds = metrics.boundingRect(str);
int pos;
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);
}
@@ -683,26 +894,31 @@ void VolumeMeter::paintVTicks(QPainter &painter, int x, int y, int height)
qreal scale = height / minimumLevel;
painter.setFont(tickFont);
QFontMetrics metrics(tickFont);
painter.setPen(majorTickColor);
// Draw major tick lines and numeric indicators.
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);
if (i == 0)
painter.drawText(x + 5, position + 4, str);
else if (i == -60)
painter.drawText(x + 4, position, str);
else
painter.drawText(x + 4, position + 2, str);
// Center the number on the tick, but don't overflow
if (i == 0) {
painter.drawText(x + 6, position + metrics.capHeight(),
str);
} else {
painter.drawText(x + 4,
position + (metrics.capHeight() / 2),
str);
}
painter.drawLine(x, position, x + 2, position);
}
// Draw minor tick lines.
painter.setPen(minorTickColor);
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)
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,
maximumPosition - peakPosition, height,
backgroundErrorColor);
} else {
} else if (int(magnitude) != 0) {
if (!clipping) {
QTimer::singleShot(CLIP_FLASH_DURATION_MS, this,
SLOT(ClipEnding()));
@@ -879,7 +1095,7 @@ void VolumeMeter::paintVMeter(QPainter &painter, int x, int y, int width,
int end = errorLength + warningLength + nominalLength;
painter.fillRect(x, minimumPosition, width, end,
QBrush(foregroundErrorColor));
foregroundErrorColor);
}
if (peakHoldPosition - 3 < minimumPosition)
@@ -903,53 +1119,48 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
{
uint64_t ts = os_gettime_ns();
qreal timeSinceLastRedraw = (ts - lastRedrawTime) * 0.000000001;
const QRect rect = event->region().boundingRect();
int width = rect.width();
int height = rect.height();
handleChannelCofigurationChange();
calculateBallistics(ts, timeSinceLastRedraw);
bool idle = detectIdle(ts);
// Draw the ticks in a off-screen buffer when the widget changes size.
QSize tickPaintCacheSize;
QRect widgetRect = rect();
int width = widgetRect.width();
int height = widgetRect.height();
QPainter painter(this);
if (vertical)
tickPaintCacheSize = QSize(14, height);
else
tickPaintCacheSize = QSize(width, 9);
if (tickPaintCache == nullptr ||
tickPaintCache->size() != tickPaintCacheSize) {
delete tickPaintCache;
tickPaintCache = new QPixmap(tickPaintCacheSize);
height -= METER_PADDING * 2;
QColor clearColor(0, 0, 0, 0);
tickPaintCache->fill(clearColor);
// timerEvent requests update of the bar(s) only, so we can avoid the
// 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) {
tickPainter.translate(0, height);
tickPainter.scale(1, -1);
paintVTicks(tickPainter, 0, 11,
tickPaintCacheSize.height() - 11);
paintVTicks(painter,
displayNrAudioChannels *
(meterThickness + 1) -
1,
0, height - (INDICATOR_THICKNESS + 3));
} else {
paintHTicks(tickPainter, 6, 0,
tickPaintCacheSize.width() - 6,
tickPaintCacheSize.height());
paintHTicks(painter, INDICATOR_THICKNESS + 3,
displayNrAudioChannels *
(meterThickness + 1) -
1,
width - (INDICATOR_THICKNESS + 3));
}
tickPainter.end();
}
// Actual painting of the widget starts here.
QPainter painter(this);
if (vertical) {
// Invert the Y axis to ease the math
painter.translate(0, height);
painter.translate(0, height + METER_PADDING);
painter.scale(1, -1);
painter.drawPixmap(displayNrAudioChannels * 4 - 1, 7,
*tickPaintCache);
} else {
painter.drawPixmap(0, height - 9, *tickPaintCache);
}
for (int channelNr = 0; channelNr < displayNrAudioChannels;
@@ -961,12 +1172,17 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
: channelNr;
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],
displayPeak[channelNrFixed],
displayPeakHold[channelNrFixed]);
else
paintHMeter(painter, 5, channelNr * 4, width - 5, 3,
paintHMeter(painter, INDICATOR_THICKNESS + 2,
channelNr * (meterThickness + 1),
width - (INDICATOR_THICKNESS + 2),
meterThickness,
displayMagnitude[channelNrFixed],
displayPeak[channelNrFixed],
displayPeakHold[channelNrFixed]);
@@ -978,16 +1194,40 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
// see that the audio stream has been stopped, without
// having too much visual impact.
if (vertical)
paintInputMeter(painter, channelNr * 4, 3, 3, 3,
paintInputMeter(painter,
channelNr * (meterThickness + 1), 0,
meterThickness, INDICATOR_THICKNESS,
displayInputPeakHold[channelNrFixed]);
else
paintInputMeter(painter, 0, channelNr * 4, 3, 3,
paintInputMeter(painter, 0,
channelNr * (meterThickness + 1),
INDICATOR_THICKNESS, meterThickness,
displayInputPeakHold[channelNrFixed]);
}
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)
{
volumeMeters.push_back(meter);
@@ -1000,6 +1240,13 @@ void VolumeMeterTimer::RemoveVolControl(VolumeMeter *meter)
void VolumeMeterTimer::timerEvent(QTimerEvent *)
{
for (VolumeMeter *meter : volumeMeters)
meter->update();
for (VolumeMeter *meter : volumeMeters) {
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 <QMutex>
#include <QList>
#include <QSlider>
#include <QMenu>
class QPushButton;
class VolumeMeterTimer;
@@ -25,6 +27,32 @@ class VolumeMeter : public QWidget {
WRITE setForegroundWarningColor DESIGNABLE true)
Q_PROPERTY(QColor foregroundErrorColor READ getForegroundErrorColor
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
DESIGNABLE true)
Q_PROPERTY(QColor magnitudeColor READ getMagnitudeColor WRITE
@@ -33,6 +61,10 @@ class VolumeMeter : public QWidget {
setMajorTickColor DESIGNABLE true)
Q_PROPERTY(QColor minorTickColor READ getMinorTickColor WRITE
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.
Q_PROPERTY(qreal minimumLevel READ getMinimumLevel WRITE setMinimumLevel
@@ -61,6 +93,8 @@ class VolumeMeter : public QWidget {
Q_PROPERTY(qreal inputPeakHoldDuration READ getInputPeakHoldDuration
WRITE setInputPeakHoldDuration DESIGNABLE true)
friend class VolControl;
private slots:
void ClipEnding();
@@ -70,7 +104,7 @@ private:
QSharedPointer<VolumeMeterTimer> updateTimerRef;
inline void resetLevels();
inline void handleChannelCofigurationChange();
inline void doLayout();
inline bool detectIdle(uint64_t ts);
inline void calculateBallistics(uint64_t ts,
qreal timeSinceLastRedraw = 0.0);
@@ -81,20 +115,19 @@ private:
int height, float peakHold);
void paintHMeter(QPainter &painter, int x, int y, int width, int height,
float magnitude, float peak, float peakHold);
void paintHTicks(QPainter &painter, int x, int y, int width,
int height);
void paintHTicks(QPainter &painter, int x, int y, int width);
void paintVMeter(QPainter &painter, int x, int y, int width, int height,
float magnitude, float peak, float peakHold);
void paintVTicks(QPainter &painter, int x, int y, int height);
QMutex dataMutex;
bool recalculateLayout = true;
uint64_t currentLastUpdateTime = 0;
float currentMagnitude[MAX_AUDIO_CHANNELS];
float currentPeak[MAX_AUDIO_CHANNELS];
float currentInputPeak[MAX_AUDIO_CHANNELS];
QPixmap *tickPaintCache = nullptr;
int displayNrAudioChannels = 0;
float displayMagnitude[MAX_AUDIO_CHANNELS];
float displayPeak[MAX_AUDIO_CHANNELS];
@@ -110,10 +143,22 @@ private:
QColor foregroundNominalColor;
QColor foregroundWarningColor;
QColor foregroundErrorColor;
QColor backgroundNominalColorDisabled;
QColor backgroundWarningColorDisabled;
QColor backgroundErrorColorDisabled;
QColor foregroundNominalColorDisabled;
QColor foregroundWarningColorDisabled;
QColor foregroundErrorColorDisabled;
QColor clipColor;
QColor magnitudeColor;
QColor majorTickColor;
QColor minorTickColor;
int meterThickness;
qreal meterFontScaling;
qreal minimumLevel;
qreal warningLevel;
qreal errorLevel;
@@ -124,6 +169,13 @@ private:
qreal peakHoldDuration;
qreal inputPeakHoldDuration;
QColor p_backgroundNominalColor;
QColor p_backgroundWarningColor;
QColor p_backgroundErrorColor;
QColor p_foregroundNominalColor;
QColor p_foregroundWarningColor;
QColor p_foregroundErrorColor;
uint64_t lastRedrawTime = 0;
int channels = 0;
bool clipping = false;
@@ -138,6 +190,8 @@ public:
void setLevels(const float magnitude[MAX_AUDIO_CHANNELS],
const float peak[MAX_AUDIO_CHANNELS],
const float inputPeak[MAX_AUDIO_CHANNELS]);
QRect getBarRect() const;
bool needLayoutChange();
QColor getBackgroundNominalColor() const;
void setBackgroundNominalColor(QColor c);
@@ -151,6 +205,20 @@ public:
void setForegroundWarningColor(QColor c);
QColor getForegroundErrorColor() const;
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;
void setClipColor(QColor c);
QColor getMagnitudeColor() const;
@@ -159,6 +227,10 @@ public:
void setMajorTickColor(QColor c);
QColor getMinorTickColor() const;
void setMinorTickColor(QColor c);
int getMeterThickness() const;
void setMeterThickness(int v);
qreal getMeterFontScaling() const;
void setMeterFontScaling(qreal v);
qreal getMinimumLevel() const;
void setMinimumLevel(qreal v);
qreal getWarningLevel() const;
@@ -182,6 +254,7 @@ public:
protected:
void paintEvent(QPaintEvent *event) override;
void changeEvent(QEvent *e) override;
};
class VolumeMeterTimer : public QTimer {
@@ -200,7 +273,6 @@ protected:
class QLabel;
class QSlider;
class MuteCheckBox;
class VolControl : public QWidget {
Q_OBJECT
@@ -211,11 +283,13 @@ private:
QLabel *volLabel;
VolumeMeter *volMeter;
QSlider *slider;
QPushButton *config = nullptr;
float levelTotal;
float levelCount;
obs_fader_t *obs_fader;
obs_volmeter_t *obs_volmeter;
bool vertical;
QMenu *contextMenu;
static void OBSVolumeChanged(void *param, float db);
static void OBSVolumeLevel(void *data,
@@ -223,12 +297,20 @@ private:
const float peak[MAX_AUDIO_CHANNELS],
const float inputPeak[MAX_AUDIO_CHANNELS]);
void EmitConfigClicked();
private slots:
void VolumeChanged();
void SliderChanged(int vol);
void updateText();
signals:
void ConfigClicked();
public:
explicit VolControl(OBSSource source, bool vertical = false);
explicit VolControl(OBSSource source, bool showConfig = false,
bool vertical = false);
~VolControl();
inline obs_source_t *GetSource() const { return source; }
@@ -240,6 +322,19 @@ public:
void setPeakMeterType(enum obs_peak_meter_type peakMeterType);
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;
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 *)
{
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");
std::lock_guard<std::mutex> lock(switcher->m);
switcher->websocketMessages.emplace_back(msg);
vblog(LOG_INFO, "received messsage: %s", msg);
vblog(LOG_INFO, "received message: %s", msg);
}
extern "C" void RegisterWebsocketVendor()
@@ -232,6 +251,31 @@ void WSConnection::HandleHello(obs_data_t *helloMsg)
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)
{
auto data = obs_data_get_obj(response, "d");
@@ -278,11 +322,14 @@ void WSConnection::OnMessage(connection_hdl, client::message_ptr message)
case 2: // Identified
_status = Status::AUTHENTICATED;
break;
case 5: // Event (Vendor)
HandleEvent(json);
break;
case 7: // RequestResponse
HandleResponse(json);
break;
default:
vblog(LOG_INFO, "ignoring unkown opcode %d", opcode);
vblog(LOG_INFO, "ignoring unknown opcode %d", opcode);
break;
}
obs_data_release(json);

View File

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