Compare commits

..

47 Commits
1.16 ... 1.16.4

Author SHA1 Message Date
WarmUpTill
5dfed8ac2a CI: Switch to OBS 27.1.3 2021-11-20 05:37:57 -08:00
WarmUpTill
d7102d395e Ensure contents of comboboxes are readable when changing scene/filter 2021-11-20 03:52:54 -08:00
WarmUpTill
82a41694a1 Allow interacting with OBS main window while plugin settings are opened 2021-11-20 03:52:54 -08:00
WarmUpTill
a1cae263c1 Add macro action "random" (#353)
This should be expanded upon by adding the ability to add weights to each macro.
2021-11-20 12:52:12 +01:00
WarmUpTill
cce6e240c2 Add option to fade in/out volume change 2021-11-17 12:30:10 -08:00
WarmUpTill
9d04b4d1a0 Move setLayoutVisible() to utility 2021-11-17 12:30:10 -08:00
WarmUpTill
fd7e48eff9 Add scene-visibility macro condition 2021-11-17 12:29:57 -08:00
WarmUpTill
0350a46004 Allow use of "previous scene" 2021-11-17 12:29:57 -08:00
WarmUpTill
00fec4b573 Fix macro hotkeys not working 2021-11-17 12:29:41 -08:00
WarmUpTill
c40a3af8ca Fix add, delete, reorder not working when changing macro segment type 2021-11-17 12:06:53 -08:00
WarmUpTill
e452d8cc8c Allow selecting source type for scene visibility action
This should allow to simplify some setups which required showing /
hiding all sources of a particular type.
2021-11-12 10:05:54 -08:00
WarmUpTill
fbc9818764 Fix macro count not updating correctly
The underlying issue are the potential deadlocks when triggering
frontent function calls while saving / loading is triggered in parallel
like it can happen when switching scene collections.

Hopefully the new frontend events allow for a cleaner solution.
2021-11-12 10:05:11 -08:00
WarmUpTill
5ef09f661f Show warning if macro is paused
And clean up timer handling for this condition type
2021-11-12 10:05:11 -08:00
WarmUpTill
2155a942f3 Do not check conditions of paused macros 2021-11-12 10:05:11 -08:00
WarmUpTill
5af206da60 Fix typo 2021-11-09 10:24:14 -08:00
WarmUpTill
924f68df64 Fix typo 2021-11-07 11:14:25 +01:00
WarmUpTill
2f3c4a6dae Cleanup 2021-11-06 22:30:26 +01:00
WarmUpTill
f955c07bda Fix warnings 2021-11-01 11:23:43 -07:00
WarmUpTill
6f1cd3cbcc Enable building without opencv 2021-11-01 11:23:43 -07:00
WarmUpTill
95537f39e4 Switch to using process names for process condition and executable tab (#337)
Previously the process condition and executable tab were just reusing
the window title functionality.
2021-10-31 14:05:54 +01:00
WarmUpTill
0490c97ad9 Add option to use pattern matching for "output changed" condition
This will allow you to ignore minor changes in the video output, for
example in noisy video streams.
2021-10-31 06:05:16 -07:00
WarmUpTill
4b17314c66 Add conditions "stream starting" and "stream stopping" 2021-10-31 05:40:28 -07:00
WarmUpTill
7459793fe3 Improve missing data dir error 2021-10-30 11:40:34 -07:00
WarmUpTill
8a39a859be Remove whitespace 2021-10-30 11:40:26 -07:00
WarmUpTill
f8a592f17a CI: Update readme 2021-10-30 11:40:26 -07:00
WarmUpTill
f824ac8f4e Add option to display plugin running state in system tray 2021-10-30 11:40:10 -07:00
WarmUpTill
8e9323173a CI: Run builds on tag creation also 2021-10-28 14:38:25 -07:00
WarmUpTill
9bc59c1082 Add option to not save remaining time 2021-10-25 11:19:53 -07:00
WarmUpTill
8b9f03a349 Add timer action
This action can be used to control the state of the timers on macro
conditions
2021-10-23 13:54:26 -07:00
WarmUpTill
093b0f9ec0 Add pause / continue and save / load remaining time for time condition 2021-10-23 13:54:26 -07:00
WarmUpTill
254eabfa8d Fix exact image match not wokring when using images with alpha channel
The screenshots taken from video sources were using the image format
Format_RGBX8888 instead of Format_RGBA8888.
The alpha channel of screenshots taken from OBS would be blended with a
black background while this step would be not performed for files loaded
to macht match the video sources.
2021-10-23 13:54:15 -07:00
WarmUpTill
b2da8faf37 Improve performance by not creating pattern data each interval 2021-10-23 13:54:15 -07:00
WarmUpTill
3eba20e2d8 Add option to use alpha channel as mask for pattern match 2021-10-23 13:54:15 -07:00
WarmUpTill
08841aa88e Use duration control class / widget for scene triggers 2021-10-23 13:53:56 -07:00
WarmUpTill
2e96c226b5 Improve performance of expand / collapse all 2021-10-23 13:53:56 -07:00
WarmUpTill
c34308f743 Fix scroll position being lost when adding or removing macro segments 2021-10-23 13:53:56 -07:00
WarmUpTill
4af07d9d55 Fix incorrectly set start scene type when extending scquence with groups
This would result in extended sequences being aborted early whenever one
would try to extend after a scene group entry
2021-10-23 13:53:56 -07:00
WarmUpTill
d743260ca8 Add wait for transition to complete option for currrent scene check
Previous behaviour was to always wait for the transition to complete, as
switcher->currentScene is only updated on
OBS_FRONTEND_EVENT_SCENE_CHANGED.
2021-10-23 13:53:56 -07:00
WarmUpTill
8ff50056f0 Fix crash when switching scene collection during macro wait
Also update comment stating why the main mutex needs to be unlocked
during the "action" phase of running macros
2021-10-23 13:53:56 -07:00
WarmUpTill
b786831f3b Remove unnecessary return value check 2021-10-23 13:53:56 -07:00
WarmUpTill
32e2a5f44c Add git tag to version info 2021-10-19 13:02:15 -07:00
WarmUpTill
e3f277c32a Reword date repeat option 2021-10-19 13:01:24 -07:00
WarmUpTill
5a7d76061c Clean up general tab
- Remove unnecessary spacer next to priority list
- Remove horizontal line in General behviour section
2021-10-19 13:01:01 -07:00
WarmUpTill
39a8c34fa6 Add description for the "min neighbors" parameter 2021-10-09 08:08:15 -07:00
WarmUpTill
ead51e2f13 Fix minNeighbors parameter not loading correctly 2021-10-09 08:08:15 -07:00
WarmUpTill
3b465e1fdc Fix crash while loading haar cascade 2021-10-03 12:30:45 -07:00
WarmUpTill
26fd26051b CI: Run on ubuntu-latest 2021-10-03 11:11:53 -07:00
57 changed files with 2122 additions and 351 deletions

View File

@@ -43,7 +43,7 @@ jobs:
# devscripts and libobs-dev are needed but they were already installed # devscripts and libobs-dev are needed but they were already installed
# from check_libobs_revision and install_frontend_header sections. # from check_libobs_revision and install_frontend_header sections.
sudo apt update sudo apt update
sudo apt install cmake debhelper libcurl4-openssl-dev libxss-dev libxtst-dev qtbase5-dev libopencv-dev sudo apt install cmake debhelper libcurl4-openssl-dev libxss-dev libxtst-dev qtbase5-dev libopencv-dev libprocps-dev
- name: build - name: build
run: | run: |
debuild --no-lintian --no-sign debuild --no-lintian --no-sign

View File

@@ -1,15 +1,11 @@
name: build obs plugin name: build obs plugin
on: on: [push, pull_request, workflow_dispatch]
push:
branches: [ master ]
pull_request:
branches: [ master ]
env: env:
PLUGIN_NAME: SceneSwitcher PLUGIN_NAME: SceneSwitcher
LIB_NAME: advanced-scene-switcher LIB_NAME: advanced-scene-switcher
OBS_TAG: 27.1.1 OBS_TAG: 27.1.3
jobs: jobs:
macos64: macos64:
@@ -115,7 +111,7 @@ jobs:
path: ./nightly/*.pkg path: ./nightly/*.pkg
ubuntu64: ubuntu64:
name: 'Linux/Ubuntu 64-bit' name: 'Linux/Ubuntu 64-bit'
runs-on: [ubuntu-18.04] runs-on: [ubuntu-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.4
@@ -181,7 +177,10 @@ jobs:
libqt5svg5-dev \ libqt5svg5-dev \
swig \ swig \
libxss-dev \ libxss-dev \
libopencv-dev libx11-xcb-dev \
libxcb-xfixes0-dev \
libopencv-dev \
libprocps-dev
- name: 'Configure' - name: 'Configure'
shell: bash shell: bash
run: | run: |

View File

@@ -1,4 +1,4 @@
Note that you will have to use OBS version 26.1 or newer! Note that you will have to use OBS version 27 or newer!
--- Windows --- --- Windows ---
Recommended: Run the provided installer. (You might have to click 'More info' and select 'Run anyway' if it is blocked by Windows) Recommended: Run the provided installer. (You might have to click 'More info' and select 'Run anyway' if it is blocked by Windows)
@@ -8,17 +8,17 @@ 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) Remember to install the Visual C++ Redistributable for Visual Studio 2019, if you have not done so already. (See plugin overview page)
--- macOS --- --- macOS ---
Recommended: Run the provided installer. (You might have to right-click or control-click and select 'open' if it is blocked) Run the provided installer. (You might have to right-click or control-click and select 'open' if it is blocked)
Alternatively extract the *so file and data folder and either ...
... right-click the OBS app inside your Applications folder and choose 'Show Package Contents'.
Copy the advanced-scene-switcher.so file to 'Contents/Plugins' and the 'data' folder to 'Contents/Resources'.
... or copy the advanced-scene-switcher.so file to Library/Application Support/obs-studio/plugins/advanced-scene-switcher/bin/ .
And the 'data' folder to 'Library/Application Support/obs-studio/plugins/advanced-scene-switcher/'.
--- Linux --- --- Linux ---
Note that the plugin has dependencies to:
* libXss
* libopencv-imgproc
* libopencv-objdetect
Optional:
* libXtst
* libcurl
Copy the advanced-scene-switcher.so file and into the OBS Studio plugin folder. 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. The location of this folder can vary, so you might have to look around a bit.

View File

@@ -42,6 +42,7 @@ endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
include(GetGitRevisionDescription) include(GetGitRevisionDescription)
get_git_head_revision(GIT_REFSPEC GIT_SHA1) get_git_head_revision(GIT_REFSPEC GIT_SHA1)
git_describe(GIT_TAG)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.cpp.in" configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.cpp.in"
"${CMAKE_CURRENT_BINARY_DIR}/src/version.cpp" @ONLY) "${CMAKE_CURRENT_BINARY_DIR}/src/version.cpp" @ONLY)
@@ -136,9 +137,19 @@ endif()
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
find_package(X11 REQUIRED COMPONENTS Xtst Xss) find_package(X11 REQUIRED COMPONENTS Xtst Xss)
link_libraries(${X11_LIBRARIES}) find_path(PROCPS_INCLUDE_DIR NAMES proc/procps.h)
if(NOT PROCPS_INCLUDE_DIR)
message(
FATAL_ERROR "procps include dir not found - please set PROCPS_INCLUDE_DIR"
)
endif()
find_library(PROCPS_LIBRARY NAMES procps)
if(NOT PROCPS_LIBRARY)
message(FATAL_ERROR "procps lib not found - please set PROCPS_LIBRARY")
endif()
link_libraries(${X11_LIBRARIES} ${procps_LIBRARIES})
include_directories("${X11_INCLUDE_DIR}" "${X11_Xtst_INCLUDE_PATH}" include_directories("${X11_INCLUDE_DIR}" "${X11_Xtst_INCLUDE_PATH}"
"${X11_Xss_INCLUDE_PATH}") "${X11_Xss_INCLUDE_PATH}" "${PROCPS_INCLUDE_DIR}")
endif() endif()
if(WIN32) if(WIN32)
@@ -153,7 +164,7 @@ elseif(APPLE)
else() else()
set(advanced-scene-switcher_PLATFORM_SOURCES set(advanced-scene-switcher_PLATFORM_SOURCES
src/linux/advanced-scene-switcher-nix.cpp) src/linux/advanced-scene-switcher-nix.cpp)
set(advanced-scene-switcher_PLATFORM_LIBS Xss) set(advanced-scene-switcher_PLATFORM_LIBS Xss ${PROCPS_LIBRARY})
endif() endif()
# asio and websocketpp # asio and websocketpp
@@ -164,10 +175,6 @@ if(WIN32)
add_definitions(-D_WEBSOCKETPP_CPP11_STL_) add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
endif() endif()
# opencv
find_package(OpenCV REQUIRED)
include_directories("${OpenCV_INCLUDE_DIRS}")
# Setup QT tools # Setup QT tools
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOUIC ON)
@@ -208,6 +215,7 @@ set(advanced-scene-switcher_HEADERS
src/headers/macro-action-media.hpp src/headers/macro-action-media.hpp
src/headers/macro-action-plugin-state.hpp src/headers/macro-action-plugin-state.hpp
src/headers/macro-action-preview-scene.hpp src/headers/macro-action-preview-scene.hpp
src/headers/macro-action-random.hpp
src/headers/macro-action-recording.hpp src/headers/macro-action-recording.hpp
src/headers/macro-action-replay-buffer.hpp src/headers/macro-action-replay-buffer.hpp
src/headers/macro-action-run.hpp src/headers/macro-action-run.hpp
@@ -218,6 +226,7 @@ set(advanced-scene-switcher_HEADERS
src/headers/macro-action-scene-visibility.hpp src/headers/macro-action-scene-visibility.hpp
src/headers/macro-action-source.hpp src/headers/macro-action-source.hpp
src/headers/macro-action-streaming.hpp src/headers/macro-action-streaming.hpp
src/headers/macro-action-timer.hpp
src/headers/macro-action-transition.hpp src/headers/macro-action-transition.hpp
src/headers/macro-action-virtual-cam.hpp src/headers/macro-action-virtual-cam.hpp
src/headers/macro-action-wait.hpp src/headers/macro-action-wait.hpp
@@ -237,6 +246,7 @@ set(advanced-scene-switcher_HEADERS
src/headers/macro-condition-replay-buffer.hpp src/headers/macro-condition-replay-buffer.hpp
src/headers/macro-condition-scene-order.hpp src/headers/macro-condition-scene-order.hpp
src/headers/macro-condition-scene-transform.hpp src/headers/macro-condition-scene-transform.hpp
src/headers/macro-condition-scene-visibility.hpp
src/headers/macro-condition-scene.hpp src/headers/macro-condition-scene.hpp
src/headers/macro-condition-source.hpp src/headers/macro-condition-source.hpp
src/headers/macro-condition-streaming.hpp src/headers/macro-condition-streaming.hpp
@@ -298,6 +308,7 @@ set(advanced-scene-switcher_SOURCES
src/macro-action-media.cpp src/macro-action-media.cpp
src/macro-action-plugin-state.cpp src/macro-action-plugin-state.cpp
src/macro-action-preview-scene.cpp src/macro-action-preview-scene.cpp
src/macro-action-random.cpp
src/macro-action-recording.cpp src/macro-action-recording.cpp
src/macro-action-replay-buffer.cpp src/macro-action-replay-buffer.cpp
src/macro-action-run.cpp src/macro-action-run.cpp
@@ -308,6 +319,7 @@ set(advanced-scene-switcher_SOURCES
src/macro-action-scene-visibility.cpp src/macro-action-scene-visibility.cpp
src/macro-action-source.cpp src/macro-action-source.cpp
src/macro-action-streaming.cpp src/macro-action-streaming.cpp
src/macro-action-timer.cpp
src/macro-action-transition.cpp src/macro-action-transition.cpp
src/macro-action-virtual-cam.cpp src/macro-action-virtual-cam.cpp
src/macro-action-wait.cpp src/macro-action-wait.cpp
@@ -327,6 +339,7 @@ set(advanced-scene-switcher_SOURCES
src/macro-condition-replay-buffer.cpp src/macro-condition-replay-buffer.cpp
src/macro-condition-scene-order.cpp src/macro-condition-scene-order.cpp
src/macro-condition-scene-transform.cpp src/macro-condition-scene-transform.cpp
src/macro-condition-scene-visibility.cpp
src/macro-condition-scene.cpp src/macro-condition-scene.cpp
src/macro-condition-source.cpp src/macro-condition-source.cpp
src/macro-condition-streaming.cpp src/macro-condition-streaming.cpp
@@ -351,6 +364,22 @@ set(advanced-scene-switcher_SOURCES
src/volume-control.cpp src/volume-control.cpp
src/version.cpp) src/version.cpp)
# opencv
find_package(OpenCV)
if(OpenCV_FOUND)
include_directories("${OpenCV_INCLUDE_DIRS}")
else()
set(OpenCV_LIBRARIES "")
message(
WARNING
"OpenCV not found! Functionality relying on OpenCV will be disabled!\nOpenCV sources are available under: ${CMAKE_CURRENT_SOURCE_DIR}/deps/opencv"
)
list(REMOVE_ITEM advanced-scene-switcher_SOURCES
src/macro-condition-video.cpp)
list(REMOVE_ITEM advanced-scene-switcher_HEADERS
src/headers/macro-condition-video.hpp)
endif()
add_library( add_library(
advanced-scene-switcher MODULE advanced-scene-switcher MODULE
${advanced-scene-switcher_HEADERS} ${advanced-scene-switcher_HEADERS}
@@ -398,11 +427,7 @@ if(BUILD_OUT_OF_TREE)
else() else()
# In tree build # In tree build
target_link_libraries( target_link_libraries(
advanced-scene-switcher advanced-scene-switcher ${advanced-scene-switcher_PLATFORM_LIBS}
${advanced-scene-switcher_PLATFORM_LIBS} ${OpenCV_LIBRARIES} obs-frontend-api Qt5::Widgets libobs)
${OpenCV_LIBRARIES}
obs-frontend-api
Qt5::Widgets
libobs)
install_obs_plugin_with_data(advanced-scene-switcher data) install_obs_plugin_with_data(advanced-scene-switcher data)
endif() endif()

View File

@@ -1,3 +1,5 @@
#include "src/headers/version.h" #include "src/headers/version.h"
#define GIT_SHA1 "@GIT_SHA1@" #define GIT_SHA1 "@GIT_SHA1@"
const char g_GIT_SHA1[] = GIT_SHA1; #define GIT_TAG "@GIT_TAG@"
const char g_GIT_SHA1[] = GIT_SHA1;
const char g_GIT_TAG[] = GIT_TAG;

View File

@@ -45,7 +45,7 @@ AdvSceneSwitcher.generalTab.priority.fileContent="Datei Inhalt"
AdvSceneSwitcher.generalTab.priority.sceneSequence="Szenenfolge" AdvSceneSwitcher.generalTab.priority.sceneSequence="Szenenfolge"
AdvSceneSwitcher.generalTab.priority.idleDetection="Inaktivitätserkennung" AdvSceneSwitcher.generalTab.priority.idleDetection="Inaktivitätserkennung"
AdvSceneSwitcher.generalTab.priority.executable="Prozess" AdvSceneSwitcher.generalTab.priority.executable="Prozess"
AdvSceneSwitcher.generalTab.priority.screenRegion="Bildschrimbereich" AdvSceneSwitcher.generalTab.priority.screenRegion="Bildschirmbereich"
AdvSceneSwitcher.generalTab.priority.windowTitle="Fenstername" AdvSceneSwitcher.generalTab.priority.windowTitle="Fenstername"
AdvSceneSwitcher.generalTab.priority.media="Medien" AdvSceneSwitcher.generalTab.priority.media="Medien"
AdvSceneSwitcher.generalTab.priority.time="Zeit" AdvSceneSwitcher.generalTab.priority.time="Zeit"
@@ -98,7 +98,7 @@ AdvSceneSwitcher.executableTab.entry="Wenn {{processes}} läuft wechsle zu {{sce
AdvSceneSwitcher.executableTab.help="Dieser Tab erlaubt es Szenen zu wechseln, wenn bestimmte Prozesse laufen.\nDas kann nützlich sein, wenn sich der Name eines Fensters ändern kann oder dieser nicht bekannt ist.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen." AdvSceneSwitcher.executableTab.help="Dieser Tab erlaubt es Szenen zu wechseln, wenn bestimmte Prozesse laufen.\nDas kann nützlich sein, wenn sich der Name eines Fensters ändern kann oder dieser nicht bekannt ist.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
; Screen Region Tab ; Screen Region Tab
AdvSceneSwitcher.screenRegionTab.title="Bildschrimbereich" AdvSceneSwitcher.screenRegionTab.title="Bildschirmbereich"
AdvSceneSwitcher.screenRegionTab.currentPosition="Mauszeiger ist bei:" AdvSceneSwitcher.screenRegionTab.currentPosition="Mauszeiger ist bei:"
AdvSceneSwitcher.screenRegionTab.showGuideFrames="Hilfsrahmen einblenden" AdvSceneSwitcher.screenRegionTab.showGuideFrames="Hilfsrahmen einblenden"
AdvSceneSwitcher.screenRegionTab.hideGuideFrames="Hilfsrahmen ausblenden" AdvSceneSwitcher.screenRegionTab.hideGuideFrames="Hilfsrahmen ausblenden"

View File

@@ -28,6 +28,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.cooldown="After a match do not switc
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="During this time potential matches will be ignored!" AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="During this time potential matches will be ignored!"
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Enable verbose logging" AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Enable verbose logging"
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Save window position and size" AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Save window position and size"
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Show system tray notifications"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Disable UI hints" AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Disable UI hints"
AdvSceneSwitcher.generalTab.priority="Priority" AdvSceneSwitcher.generalTab.priority="Priority"
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)" AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
@@ -95,9 +96,10 @@ AdvSceneSwitcher.condition.cursor.entry.line2="Cursor is currently at {{xPos}} x
AdvSceneSwitcher.condition.scene="Scene" AdvSceneSwitcher.condition.scene="Scene"
AdvSceneSwitcher.condition.scene.type.current="Current scene is" AdvSceneSwitcher.condition.scene.type.current="Current scene is"
AdvSceneSwitcher.condition.scene.type.previous="Previous scene is" AdvSceneSwitcher.condition.scene.type.previous="Previous scene is"
AdvSceneSwitcher.condition.scene.type.changed="Scene chagned" AdvSceneSwitcher.condition.scene.type.changed="Scene changed"
AdvSceneSwitcher.condition.scene.type.notChanged="Scene has not changed" AdvSceneSwitcher.condition.scene.type.notChanged="Scene has not changed"
AdvSceneSwitcher.condition.scene.entry="{{sceneType}} {{scenes}}" AdvSceneSwitcher.condition.scene.waitForTransition="Wait for transition to complete"
AdvSceneSwitcher.condition.scene.entry="{{sceneType}} {{scenes}} {{waitForTransition}}"
AdvSceneSwitcher.condition.window="Window" AdvSceneSwitcher.condition.window="Window"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} exist and ..." AdvSceneSwitcher.condition.window.entry.line1="{{windows}} exist and ..."
AdvSceneSwitcher.condition.window.entry.line2="... is {{fullscreen}} fullscreen {{maximized}} maximized {{focused}} focused {{windowFocusChanged}} foreground window changed" AdvSceneSwitcher.condition.window.entry.line2="... is {{fullscreen}} fullscreen {{maximized}} maximized {{focused}} focused {{windowFocusChanged}} foreground window changed"
@@ -118,10 +120,14 @@ AdvSceneSwitcher.condition.video.condition.object="contains object"
AdvSceneSwitcher.condition.video.askFileAction="Do you want to use an existing file or create a screenshot of the currently selected source?" AdvSceneSwitcher.condition.video.askFileAction="Do you want to use an existing file or create a screenshot of the currently selected source?"
AdvSceneSwitcher.condition.video.askFileAction.file="Use existing file" AdvSceneSwitcher.condition.video.askFileAction.file="Use existing file"
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot" AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot"
AdvSceneSwitcher.condition.video.usePatternForChangedCheck="Use pattern matching"
AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip="This will allow you to control how much the image has to change for the condition to be true."
AdvSceneSwitcher.condition.video.patternThreshold="Threshold: " AdvSceneSwitcher.condition.video.patternThreshold="Threshold: "
AdvSceneSwitcher.condition.video.patternThresholdDescription="A higher threshold value means that the pattern needs to match the video source more closely." AdvSceneSwitcher.condition.video.patternThresholdDescription="A higher threshold value means that the pattern needs to match the video source more closely."
AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask="Use alpha channel as mask for pattern."
AdvSceneSwitcher.condition.video.objectScaleThreshold="Scale factor: " AdvSceneSwitcher.condition.video.objectScaleThreshold="Scale factor: "
AdvSceneSwitcher.condition.video.objectScaleThresholdDescription="A lower scale factor will lead to more matches but higher CPU load." AdvSceneSwitcher.condition.video.objectScaleThresholdDescription="A lower scale factor will lead to more matches but higher CPU load."
AdvSceneSwitcher.condition.video.minNeighborDescription="A higher minimum neighbors value will result in fewer but higher quality matches."
AdvSceneSwitcher.condition.video.showMatch="Show match" AdvSceneSwitcher.condition.video.showMatch="Show match"
AdvSceneSwitcher.condition.video.screenshotFail="Failed to get screenshot of source!" AdvSceneSwitcher.condition.video.screenshotFail="Failed to get screenshot of source!"
AdvSceneSwitcher.condition.video.patternMatchFail="Pattern was not found!" AdvSceneSwitcher.condition.video.patternMatchFail="Pattern was not found!"
@@ -136,6 +142,8 @@ AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU lo
AdvSceneSwitcher.condition.stream="Streaming" AdvSceneSwitcher.condition.stream="Streaming"
AdvSceneSwitcher.condition.stream.state.start="Stream running" AdvSceneSwitcher.condition.stream.state.start="Stream running"
AdvSceneSwitcher.condition.stream.state.stop="Stream stopped" AdvSceneSwitcher.condition.stream.state.stop="Stream stopped"
AdvSceneSwitcher.condition.stream.state.starting="Stream starting"
AdvSceneSwitcher.condition.stream.state.stopping="Stream stopping"
AdvSceneSwitcher.condition.stream.entry="{{streamState}}" AdvSceneSwitcher.condition.stream.entry="{{streamState}}"
AdvSceneSwitcher.condition.record="Recording" AdvSceneSwitcher.condition.record="Recording"
AdvSceneSwitcher.condition.record.state.start="Recording running" AdvSceneSwitcher.condition.record.state.start="Recording running"
@@ -150,9 +158,11 @@ AdvSceneSwitcher.condition.pluginState="Plugin state"
AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="Automated scene change was triggered in this interval" AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="Automated scene change was triggered in this interval"
AdvSceneSwitcher.condition.pluginState.entry="{{condition}}" AdvSceneSwitcher.condition.pluginState.entry="{{condition}}"
AdvSceneSwitcher.condition.timer="Timer" AdvSceneSwitcher.condition.timer="Timer"
AdvSceneSwitcher.condition.timer.pause="Pause"
AdvSceneSwitcher.condition.timer.continue="Continue"
AdvSceneSwitcher.condition.timer.entry.line1="{{duration}} have passed" AdvSceneSwitcher.condition.timer.entry.line1="{{duration}} have passed"
AdvSceneSwitcher.condition.timer.entry.line2="Time remaining: {{remaining}} seconds" AdvSceneSwitcher.condition.timer.entry.line2="Time remaining: {{remaining}} seconds"
AdvSceneSwitcher.condition.timer.entry.line3="{{reset}} {{autoReset}} Automatically reset timer after duration was reached" AdvSceneSwitcher.condition.timer.entry.line3="{{pauseContinue}} {{reset}} {{saveRemaining}} Save time remaining {{autoReset}} Automatically reset timer after duration was reached"
AdvSceneSwitcher.condition.timer.reset="Reset" AdvSceneSwitcher.condition.timer.reset="Reset"
AdvSceneSwitcher.condition.macro="Macro" AdvSceneSwitcher.condition.macro="Macro"
AdvSceneSwitcher.condition.macro.type.count="Count" AdvSceneSwitcher.condition.macro.type.count="Count"
@@ -162,6 +172,7 @@ AdvSceneSwitcher.condition.macro.count.type.below="Less than"
AdvSceneSwitcher.condition.macro.count.type.above="More than" AdvSceneSwitcher.condition.macro.count.type.above="More than"
AdvSceneSwitcher.condition.macro.count.type.equal="Exactly" AdvSceneSwitcher.condition.macro.count.type.equal="Exactly"
AdvSceneSwitcher.condition.macro.count.reset="Reset" AdvSceneSwitcher.condition.macro.count.reset="Reset"
AdvSceneSwitcher.condition.macro.pausedWarning="Selected macro is currently paused!"
AdvSceneSwitcher.condition.macro.state.entry="Conditions of {{macros}} are true" AdvSceneSwitcher.condition.macro.state.entry="Conditions of {{macros}} are true"
AdvSceneSwitcher.condition.macro.count.entry.line1="{{macros}} was executed {{conditions}} {{count}} times" AdvSceneSwitcher.condition.macro.count.entry.line1="{{macros}} was executed {{conditions}} {{count}} times"
AdvSceneSwitcher.condition.macro.count.entry.line2="Current count: {{currentCount}} {{resetCount}}" AdvSceneSwitcher.condition.macro.count.entry.line2="Current count: {{currentCount}} {{resetCount}}"
@@ -208,7 +219,7 @@ AdvSceneSwitcher.condition.date.state.after="After"
AdvSceneSwitcher.condition.date.state.before="Before" AdvSceneSwitcher.condition.date.state.before="Before"
AdvSceneSwitcher.condition.date.state.between="Between" AdvSceneSwitcher.condition.date.state.between="Between"
AdvSceneSwitcher.condition.date.entry.line1="{{condition}} {{dateTime}} {{dateTime2}} {{ignoreDate}} Ignore date component {{ignoreTime}} Ignore time component" AdvSceneSwitcher.condition.date.entry.line1="{{condition}} {{dateTime}} {{dateTime2}} {{ignoreDate}} Ignore date component {{ignoreTime}} Ignore time component"
AdvSceneSwitcher.condition.date.entry.line2="{{repeat}} Reschedule {{duration}} later when matching" AdvSceneSwitcher.condition.date.entry.line2="{{repeat}} Repeat every {{duration}} on date match"
AdvSceneSwitcher.condition.sceneTransform="Scene item transform" AdvSceneSwitcher.condition.sceneTransform="Scene item transform"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform" AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
AdvSceneSwitcher.condition.sceneTransform.regex="Use regular expressions" AdvSceneSwitcher.condition.sceneTransform.regex="Use regular expressions"
@@ -222,6 +233,10 @@ AdvSceneSwitcher.condition.transition.type.started="Transition active"
AdvSceneSwitcher.condition.transition.type.ended="Transition ended" AdvSceneSwitcher.condition.transition.type.ended="Transition ended"
AdvSceneSwitcher.condition.transition.durationSuffix="seconds" AdvSceneSwitcher.condition.transition.durationSuffix="seconds"
AdvSceneSwitcher.condition.transition.entry="{{conditions}}{{transitions}}{{duration}}{{durationSuffix}}" AdvSceneSwitcher.condition.transition.entry="{{conditions}}{{transitions}}{{duration}}{{durationSuffix}}"
AdvSceneSwitcher.condition.sceneVisibility="Scene item visibility"
AdvSceneSwitcher.condition.sceneVisibility.type.shown="Shown"
AdvSceneSwitcher.condition.sceneVisibility.type.hidden="Hidden"
AdvSceneSwitcher.condition.sceneVisibility.entry="On {{scenes}} {{sources}} is {{conditions}} "
; Macro Actions ; Macro Actions
AdvSceneSwitcher.action.switchScene="Switch scene" AdvSceneSwitcher.action.switchScene="Switch scene"
@@ -236,6 +251,7 @@ AdvSceneSwitcher.action.audio.type.mute="Mute"
AdvSceneSwitcher.action.audio.type.unmute="Unmute" AdvSceneSwitcher.action.audio.type.unmute="Unmute"
AdvSceneSwitcher.action.audio.type.sourceVolume="Set source volume" AdvSceneSwitcher.action.audio.type.sourceVolume="Set source volume"
AdvSceneSwitcher.action.audio.type.masterVolume="Set master volume" AdvSceneSwitcher.action.audio.type.masterVolume="Set master volume"
AdvSceneSwitcher.action.audio.fade="{{fade}}Fade over {{duration}} seconds"
AdvSceneSwitcher.action.audio.entry="{{actions}} {{audioSources}} {{volume}}" AdvSceneSwitcher.action.audio.entry="{{actions}} {{audioSources}} {{volume}}"
AdvSceneSwitcher.action.recording="Recording" AdvSceneSwitcher.action.recording="Recording"
AdvSceneSwitcher.action.recording.type.stop="Stop recording" AdvSceneSwitcher.action.recording.type.stop="Stop recording"
@@ -261,7 +277,9 @@ AdvSceneSwitcher.action.run.entry="Run {{filePath}}"
AdvSceneSwitcher.action.sceneVisibility="Scene item visibility" AdvSceneSwitcher.action.sceneVisibility="Scene item visibility"
AdvSceneSwitcher.action.sceneVisibility.type.show="Show" AdvSceneSwitcher.action.sceneVisibility.type.show="Show"
AdvSceneSwitcher.action.sceneVisibility.type.hide="Hide" AdvSceneSwitcher.action.sceneVisibility.type.hide="Hide"
AdvSceneSwitcher.action.sceneVisibility.entry="On {{scenes}} {{actions}} {{sources}}" AdvSceneSwitcher.action.sceneVisibility.type.source="Source"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Any"
AdvSceneSwitcher.action.sceneVisibility.entry="On {{scenes}} {{actions}} {{sourceTypes}} {{sources}}"
AdvSceneSwitcher.action.filter="Filter" AdvSceneSwitcher.action.filter="Filter"
AdvSceneSwitcher.action.filter.type.enable="Enable" AdvSceneSwitcher.action.filter.type.enable="Enable"
AdvSceneSwitcher.action.filter.type.disable="Disable" AdvSceneSwitcher.action.filter.type.disable="Disable"
@@ -329,6 +347,16 @@ AdvSceneSwitcher.action.SceneSwap.entry="Swap preview and program scene in studi
AdvSceneSwitcher.action.transition="Transition" AdvSceneSwitcher.action.transition="Transition"
AdvSceneSwitcher.action.transition.entry.line1="{{setType}}Set transition type to {{transitions}}" AdvSceneSwitcher.action.transition.entry.line1="{{setType}}Set transition type to {{transitions}}"
AdvSceneSwitcher.action.transition.entry.line2="{{setDuration}}Set transition duration to {{duration}}seconds" AdvSceneSwitcher.action.transition.entry.line2="{{setDuration}}Set transition duration to {{duration}}seconds"
AdvSceneSwitcher.action.timer="Timer"
AdvSceneSwitcher.action.timer.type.pause="Pause"
AdvSceneSwitcher.action.timer.type.continue="Continue"
AdvSceneSwitcher.action.timer.type.reset="Reset"
AdvSceneSwitcher.action.timer.type.setTimeRemaining="Set time remaining of"
AdvSceneSwitcher.action.timer.entry="{{timerAction}} timers on {{macros}} {{duration}}"
AdvSceneSwitcher.action.random="Random"
AdvSceneSwitcher.action.random.arguments="Macros:"
AdvSceneSwitcher.action.random.addArgument="Add Macro"
AdvSceneSwitcher.action.random.entry="Run {{macroSelection}}"
; Transition Tab ; Transition Tab
AdvSceneSwitcher.transitionTab.title="Transition" AdvSceneSwitcher.transitionTab.title="Transition"
@@ -603,6 +631,8 @@ AdvSceneSwitcher.selectWindowTip="Use \"OBS\" to specify OBS window\nUse \"Task
AdvSceneSwitcher.status.active="Active" AdvSceneSwitcher.status.active="Active"
AdvSceneSwitcher.status.inactive="Inactive" AdvSceneSwitcher.status.inactive="Inactive"
AdvSceneSwitcher.running="Plugin running"
AdvSceneSwitcher.stopped="Plugin stopped"
AdvSceneSwitcher.unit.milliseconds="milliseconds" AdvSceneSwitcher.unit.milliseconds="milliseconds"
AdvSceneSwitcher.unit.secends="seconds" AdvSceneSwitcher.unit.secends="seconds"

View File

@@ -258,13 +258,6 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_52"> <layout class="QHBoxLayout" name="horizontalLayout_52">
<item> <item>
@@ -313,6 +306,30 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="showTrayNotifications">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_48"> <layout class="QHBoxLayout" name="horizontalLayout_48">
<item> <item>
@@ -364,19 +381,6 @@
<item> <item>
<widget class="QListWidget" name="priorityList"/> <widget class="QListWidget" name="priorityList"/>
</item> </item>
<item>
<spacer name="horizontalSpacer_30">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>

View File

@@ -2,6 +2,7 @@
#include <QAction> #include <QAction>
#include <QFileDialog> #include <QFileDialog>
#include <regex> #include <regex>
#include <filesystem>
#include <obs-module.h> #include <obs-module.h>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
@@ -19,6 +20,7 @@ SwitcherData *switcher = nullptr;
AdvSceneSwitcher::AdvSceneSwitcher(QWidget *parent) AdvSceneSwitcher::AdvSceneSwitcher(QWidget *parent)
: QDialog(parent), ui(new Ui_AdvSceneSwitcher) : QDialog(parent), ui(new Ui_AdvSceneSwitcher)
{ {
switcher->settingsWindowOpened = true;
ui->setupUi(this); ui->setupUi(this);
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
@@ -27,6 +29,13 @@ AdvSceneSwitcher::AdvSceneSwitcher(QWidget *parent)
loadUI(); loadUI();
} }
AdvSceneSwitcher::~AdvSceneSwitcher()
{
if (switcher) {
switcher->settingsWindowOpened = false;
}
}
bool translationAvailable() bool translationAvailable()
{ {
return !!strcmp(obs_module_text("AdvSceneSwitcher.pluginName"), return !!strcmp(obs_module_text("AdvSceneSwitcher.pluginName"),
@@ -36,9 +45,16 @@ bool translationAvailable()
void AdvSceneSwitcher::loadUI() void AdvSceneSwitcher::loadUI()
{ {
if (!translationAvailable()) { if (!translationAvailable()) {
(void)DisplayMessage( QString msg = "Failed to find plug-in's 'data' directory.\n"
"Failed to find plug-in's 'data' directory.\n" "Please check installation instructions!\n\n"
"Please check installation instructions!"); "Data most likely expected at:\n\n";
#ifdef _WIN32
msg += QString::fromStdString(
(std::filesystem::current_path().string()));
msg += "/";
#endif
msg += obs_get_module_data_path(obs_current_module());
(void)DisplayMessage(msg);
} }
#if __APPLE__ #if __APPLE__
@@ -104,33 +120,23 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
{ {
if (saving) { if (saving) {
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
switcher->Prune(); switcher->Prune();
obs_data_t *obj = obs_data_create(); obs_data_t *obj = obs_data_create();
switcher->saveSettings(obj); switcher->saveSettings(obj);
obs_data_set_obj(save_data, "advanced-scene-switcher", obj); obs_data_set_obj(save_data, "advanced-scene-switcher", obj);
obs_data_release(obj); obs_data_release(obj);
} else { } else {
switcher->m.lock(); switcher->m.lock();
obs_data_t *obj = obs_data_t *obj =
obs_data_get_obj(save_data, "advanced-scene-switcher"); obs_data_get_obj(save_data, "advanced-scene-switcher");
if (!obj) { if (!obj) {
obj = obs_data_create(); obj = obs_data_create();
} }
if (switcher->versionChanged(obj, g_GIT_SHA1)) { if (switcher->versionChanged(obj, g_GIT_SHA1)) {
AskBackup(obj); AskBackup(obj);
} }
switcher->loadSettings(obj); switcher->loadSettings(obj);
obs_data_release(obj); obs_data_release(obj);
switcher->m.unlock(); switcher->m.unlock();
// Stop the scene switcher at least once to // Stop the scene switcher at least once to
@@ -166,9 +172,7 @@ void SwitcherData::Thread()
bool setPrevSceneAfterLinger = false; bool setPrevSceneAfterLinger = false;
bool macroMatch = false; bool macroMatch = false;
macroSceneSwitched = false; macroSceneSwitched = false;
endTime = std::chrono::high_resolution_clock::now(); endTime = std::chrono::high_resolution_clock::now();
auto runTime = auto runTime =
std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::duration_cast<std::chrono::milliseconds>(
endTime - startTime); endTime - startTime);
@@ -232,12 +236,17 @@ void SwitcherData::Thread()
} }
} }
// After this point we will call frontend functions // After this point we will call frontend functions like
// obs_frontend_set_current_scene() and // obs_frontend_set_current_scene() and
// obs_frontend_set_current_transition() // obs_frontend_set_current_transition()
// //
// During this time SaveSceneSwitcher() could be called // During this time SaveSceneSwitcher() could be called
// leading to a deadlock, so we have to unlock() // leading to a deadlock with the frontend function being stuck
// in QMetaObject::invokeMethod() holding the mutex and
// OBS being stuck in SaveSceneSwitcher().
//
// So we have to unlock() risking race conditions as these are
// less frequent than the above described deadlock
lock.unlock(); lock.unlock();
if (match) { if (match) {
@@ -410,12 +419,18 @@ void SwitcherData::Start()
if (networkConfig.ClientEnabled) { if (networkConfig.ClientEnabled) {
client.connect(networkConfig.GetClientUri()); client.connect(networkConfig.GetClientUri());
} }
if (showSystemTrayNotifications) {
DisplayTrayMessage(
obs_module_text("AdvSceneSwitcher.pluginName"),
obs_module_text("AdvSceneSwitcher.running"));
}
} }
void ResetMacroCounters() void ResetMacroCounters()
{ {
for (auto &m : switcher->macros) { for (auto &m : switcher->macros) {
m.ResetCount(); m->ResetCount();
} }
} }
@@ -436,6 +451,19 @@ void SwitcherData::Stop()
server.stop(); server.stop();
client.disconnect(); client.disconnect();
for (auto &t : audioHelperThreads) {
if (t.joinable()) {
t.join();
}
}
audioHelperThreads.clear();
if (showSystemTrayNotifications) {
DisplayTrayMessage(
obs_module_text("AdvSceneSwitcher.pluginName"),
obs_module_text("AdvSceneSwitcher.stopped"));
}
} }
void SwitcherData::setWaitScene() void SwitcherData::setWaitScene()
@@ -553,6 +581,18 @@ void setTranstionEnd()
std::chrono::high_resolution_clock::now(); std::chrono::high_resolution_clock::now();
} }
void setStreamStarting()
{
switcher->lastStreamStartingTime =
std::chrono::high_resolution_clock::now();
}
void setStreamStopping()
{
switcher->lastStreamStoppingTime =
std::chrono::high_resolution_clock::now();
}
// Note to future self: // Note to future self:
// be careful using switcher->m here as there is potential for deadlocks when using // be careful using switcher->m here as there is potential for deadlocks when using
// frontend functions such as obs_frontend_set_current_scene() // frontend functions such as obs_frontend_set_current_scene()
@@ -592,13 +632,22 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
case OBS_FRONTEND_EVENT_TRANSITION_STOPPED: case OBS_FRONTEND_EVENT_TRANSITION_STOPPED:
setTranstionEnd(); setTranstionEnd();
break; break;
case OBS_FRONTEND_EVENT_STREAMING_STARTING:
setStreamStarting();
break;
case OBS_FRONTEND_EVENT_STREAMING_STOPPING:
setStreamStopping();
break;
default: default:
break; break;
} }
} }
AdvSceneSwitcher *ssWindow;
extern "C" void InitSceneSwitcher() extern "C" void InitSceneSwitcher()
{ {
blog(LOG_INFO, "version: %s", g_GIT_TAG);
blog(LOG_INFO, "version: %s", g_GIT_SHA1); blog(LOG_INFO, "version: %s", g_GIT_SHA1);
QAction *action = (QAction *)obs_frontend_add_tools_menu_qaction( QAction *action = (QAction *)obs_frontend_add_tools_menu_qaction(
@@ -613,11 +662,16 @@ extern "C" void InitSceneSwitcher()
PlatformInit(); PlatformInit();
auto cb = []() { auto cb = []() {
QMainWindow *window = if (switcher->settingsWindowOpened) {
(QMainWindow *)obs_frontend_get_main_window(); ssWindow->show();
ssWindow->raise();
AdvSceneSwitcher ss(window); ssWindow->activateWindow();
ss.exec(); } else {
ssWindow = new AdvSceneSwitcher(
(QMainWindow *)obs_frontend_get_main_window());
ssWindow->setAttribute(Qt::WA_DeleteOnClose);
ssWindow->show();
}
}; };
obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr); obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr);

View File

@@ -46,6 +46,13 @@ double Duration::TimeRemaining()
return (seconds * 1000 - runTime.count()) / 1000.; return (seconds * 1000 - runTime.count()) / 1000.;
} }
void Duration::SetTimeRemaining(double remaining)
{
long long msPassed = (seconds - remaining) * 1000;
_startTime = std::chrono::high_resolution_clock::now() -
std::chrono::milliseconds(msPassed);
}
void Duration::Reset() void Duration::Reset()
{ {
_startTime = {}; _startTime = {};

View File

@@ -5,7 +5,7 @@
#include <QFileDialog> #include <QFileDialog>
FileSelection::FileSelection(FileSelection::Type type, QWidget *parent) FileSelection::FileSelection(FileSelection::Type type, QWidget *parent)
: _type(type), QWidget(parent) : QWidget(parent), _type(type)
{ {
_filePath = new QLineEdit(); _filePath = new QLineEdit();
_browseButton = _browseButton =

View File

@@ -186,6 +186,9 @@ void AdvSceneSwitcher::on_toggleStartButton_clicked()
void AdvSceneSwitcher::closeEvent(QCloseEvent *) void AdvSceneSwitcher::closeEvent(QCloseEvent *)
{ {
if (!switcher) {
return;
}
switcher->windowPos = this->pos(); switcher->windowPos = this->pos();
switcher->windowSize = this->size(); switcher->windowSize = this->size();
@@ -210,6 +213,15 @@ void AdvSceneSwitcher::on_saveWindowGeo_stateChanged(int state)
switcher->saveWindowGeo = state; switcher->saveWindowGeo = state;
} }
void AdvSceneSwitcher::on_showTrayNotifications_stateChanged(int state)
{
if (loading) {
return;
}
switcher->showSystemTrayNotifications = state;
}
void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state) void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
{ {
if (loading) { if (loading) {
@@ -503,6 +515,8 @@ void SwitcherData::saveGeneralSettings(obs_data_t *obj)
static_cast<int>(autoStartEvent)); static_cast<int>(autoStartEvent));
obs_data_set_bool(obj, "verbose", verbose); obs_data_set_bool(obj, "verbose", verbose);
obs_data_set_bool(obj, "showSystemTrayNotifications",
showSystemTrayNotifications);
obs_data_set_bool(obj, "disableHints", disableHints); obs_data_set_bool(obj, "disableHints", disableHints);
obs_data_set_int(obj, "priority0", functionNamesByPriority[0]); obs_data_set_int(obj, "priority0", functionNamesByPriority[0]);
@@ -574,6 +588,8 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
obs_data_get_int(obj, "autoStartEvent")); obs_data_get_int(obj, "autoStartEvent"));
verbose = obs_data_get_bool(obj, "verbose"); verbose = obs_data_get_bool(obj, "verbose");
showSystemTrayNotifications =
obs_data_get_bool(obj, "showSystemTrayNotifications");
disableHints = obs_data_get_bool(obj, "disableHints"); disableHints = obs_data_get_bool(obj, "disableHints");
obs_data_set_default_int(obj, "priority0", default_priority_0); obs_data_set_default_int(obj, "priority0", default_priority_0);
@@ -800,6 +816,8 @@ void AdvSceneSwitcher::setupGeneralTab()
ui->verboseLogging->setChecked(switcher->verbose); ui->verboseLogging->setChecked(switcher->verbose);
ui->saveWindowGeo->setChecked(switcher->saveWindowGeo); ui->saveWindowGeo->setChecked(switcher->saveWindowGeo);
ui->showTrayNotifications->setChecked(
switcher->showSystemTrayNotifications);
ui->uiHintsDisable->setChecked(switcher->disableHints); ui->uiHintsDisable->setChecked(switcher->disableHints);
for (int p : switcher->functionNamesByPriority) { for (int p : switcher->functionNamesByPriority) {
@@ -887,4 +905,6 @@ void AdvSceneSwitcher::setupGeneralTab()
QTimer *statusTimer = new QTimer(this); QTimer *statusTimer = new QTimer(this);
connect(statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); connect(statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
statusTimer->start(1000); statusTimer->start(1000);
setWindowTitle(windowTitle() + " - " + g_GIT_TAG);
} }

View File

@@ -29,6 +29,7 @@ public:
bool currentStatusActive = false; bool currentStatusActive = false;
AdvSceneSwitcher(QWidget *parent); AdvSceneSwitcher(QWidget *parent);
~AdvSceneSwitcher();
void reject() override; void reject() override;
void closeEvent(QCloseEvent *event) override; void closeEvent(QCloseEvent *event) override;
@@ -52,6 +53,8 @@ public:
void SetEditMacro(Macro &m); void SetEditMacro(Macro &m);
void HighlightAction(int idx); void HighlightAction(int idx);
void HighlightCondition(int idx); void HighlightCondition(int idx);
void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0);
void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0);
void ConnectControlSignals(MacroActionEdit *); void ConnectControlSignals(MacroActionEdit *);
void ConnectControlSignals(MacroConditionEdit *); void ConnectControlSignals(MacroConditionEdit *);
void SwapActions(Macro *m, int pos1, int pos2); void SwapActions(Macro *m, int pos1, int pos2);
@@ -162,6 +165,7 @@ public slots:
void on_verboseLogging_stateChanged(int state); void on_verboseLogging_stateChanged(int state);
void on_saveWindowGeo_stateChanged(int state); void on_saveWindowGeo_stateChanged(int state);
void on_showTrayNotifications_stateChanged(int state);
void on_uiHintsDisable_stateChanged(int state); void on_uiHintsDisable_stateChanged(int state);
void on_exportSettings_clicked(); void on_exportSettings_clicked();

View File

@@ -22,6 +22,7 @@ public:
bool DurationReached(); bool DurationReached();
double TimeRemaining(); double TimeRemaining();
void SetTimeRemaining(double);
void Reset(); void Reset();
std::string ToString(); std::string ToString();

View File

@@ -1,7 +1,10 @@
#pragma once #pragma once
#include <QSpinBox>
#include <QHBoxLayout>
#include "macro-action-edit.hpp" #include "macro-action-edit.hpp"
#include "duration-control.hpp"
#include <QSpinBox>
#include <QCheckBox>
#include <QHBoxLayout>
enum class AudioAction { enum class AudioAction {
MUTE, MUTE,
@@ -26,6 +29,8 @@ public:
OBSWeakSource _audioSource; OBSWeakSource _audioSource;
AudioAction _action = AudioAction::MUTE; AudioAction _action = AudioAction::MUTE;
int _volume = 0; int _volume = 0;
bool _fade = false;
Duration _duration;
private: private:
static bool _registered; static bool _registered;
@@ -53,6 +58,8 @@ private slots:
void SourceChanged(const QString &text); void SourceChanged(const QString &text);
void ActionChanged(int value); void ActionChanged(int value);
void VolumeChanged(int value); void VolumeChanged(int value);
void FadeChanged(int value);
void DurationChanged(double seconds);
signals: signals:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
@@ -60,9 +67,11 @@ protected:
QComboBox *_audioSources; QComboBox *_audioSources;
QComboBox *_actions; QComboBox *_actions;
QSpinBox *_volumePercent; QSpinBox *_volumePercent;
QCheckBox *_fade;
DurationSelection *_duration;
QHBoxLayout *_fadeLayout;
std::shared_ptr<MacroActionAudio> _entryData; std::shared_ptr<MacroActionAudio> _entryData;
private: private:
QHBoxLayout *_mainLayout;
bool _loading = true; bool _loading = true;
}; };

View File

@@ -0,0 +1,63 @@
#pragma once
#include "macro-action-edit.hpp"
#include "macro-selection.hpp"
#include <QPushButton>
#include <QListWidget>
#include <unordered_map>
class MacroActionRandom : public MultiMacroRefAction {
public:
bool PerformAction();
void LogAction();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
std::string GetId() { return id; };
static std::shared_ptr<MacroAction> Create()
{
return std::make_shared<MacroActionRandom>();
}
// TODO: add weights to each macro ...
// std::unordered_map<MacroRef, int> _weights;
private:
MacroRef lastRandomMacro;
static bool _registered;
static const std::string id;
};
class MacroActionRandomEdit : public QWidget {
Q_OBJECT
public:
MacroActionRandomEdit(
QWidget *parent,
std::shared_ptr<MacroActionRandom> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionRandomEdit(
parent,
std::dynamic_pointer_cast<MacroActionRandom>(action));
}
private slots:
void MacroRemove(const QString &name);
void MacroRename(const QString &oldName, const QString &newName);
void AddMacro();
void RemoveMacro();
int FindEntry(const std::string &macro);
void MacroSelectionChanged(int idx);
protected:
std::shared_ptr<MacroActionRandom> _entryData;
private:
MacroSelection *_macroSelection;
QListWidget *_macroList;
QPushButton *_add;
QPushButton *_remove;
bool _loading = true;
};

View File

@@ -8,6 +8,11 @@ enum class SceneVisibilityAction {
HIDE, HIDE,
}; };
enum class SceneItemSourceType {
SOURCE,
SOURCE_GROUP,
};
class MacroActionSceneVisibility : public MacroAction { class MacroActionSceneVisibility : public MacroAction {
public: public:
bool PerformAction(); bool PerformAction();
@@ -22,7 +27,9 @@ public:
} }
SceneSelection _scene; SceneSelection _scene;
SceneItemSourceType _sourceType = SceneItemSourceType::SOURCE;
OBSWeakSource _source; OBSWeakSource _source;
std::string _sourceGroup = "";
SceneVisibilityAction _action = SceneVisibilityAction::SHOW; SceneVisibilityAction _action = SceneVisibilityAction::SHOW;
private: private:
@@ -49,6 +56,7 @@ public:
private slots: private slots:
void SceneChanged(const SceneSelection &); void SceneChanged(const SceneSelection &);
void SourceTypeChanged(int value);
void SourceChanged(const QString &text); void SourceChanged(const QString &text);
void ActionChanged(int value); void ActionChanged(int value);
signals: signals:
@@ -56,6 +64,7 @@ signals:
protected: protected:
SceneSelectionWidget *_scenes; SceneSelectionWidget *_scenes;
QComboBox *_sourceTypes;
QComboBox *_sources; QComboBox *_sources;
QComboBox *_actions; QComboBox *_actions;
std::shared_ptr<MacroActionSceneVisibility> _entryData; std::shared_ptr<MacroActionSceneVisibility> _entryData;

View File

@@ -0,0 +1,72 @@
#pragma once
#include "macro-action-edit.hpp"
#include "duration-control.hpp"
#include "macro-selection.hpp"
#include <QDoubleSpinBox>
#include <QHBoxLayout>
enum class TimerAction {
PAUSE,
CONTINUE,
RESET,
SET_TIME_REMAINING,
};
class MacroActionTimer : public MacroRefAction {
public:
bool PerformAction();
void LogAction();
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()
{
return std::make_shared<MacroActionTimer>();
}
Duration _duration;
TimerAction _actionType = TimerAction::PAUSE;
private:
static bool _registered;
static const std::string id;
};
class MacroActionTimerEdit : public QWidget {
Q_OBJECT
public:
MacroActionTimerEdit(
QWidget *parent,
std::shared_ptr<MacroActionTimer> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionTimerEdit(
parent,
std::dynamic_pointer_cast<MacroActionTimer>(action));
}
private slots:
void MacroChanged(const QString &text);
void MacroRemove(const QString &name);
void DurationChanged(double value);
void DurationUnitChanged(DurationUnit unit);
void ActionTypeChanged(int value);
signals:
void HeaderInfoChanged(const QString &);
protected:
void SetWidgetVisibility();
MacroSelection *_macros;
DurationSelection *_duration;
QComboBox *_timerAction;
std::shared_ptr<MacroActionTimer> _entryData;
private:
QHBoxLayout *_mainLayout;
bool _loading = true;
};

View File

@@ -67,6 +67,7 @@ private slots:
void ConditionChanged(int cond); void ConditionChanged(int cond);
void ResetClicked(); void ResetClicked();
void UpdateCount(); void UpdateCount();
void UpdatePaused();
signals: signals:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
@@ -76,16 +77,17 @@ protected:
QComboBox *_counterConditions; QComboBox *_counterConditions;
QSpinBox *_count; QSpinBox *_count;
QLabel *_currentCount; QLabel *_currentCount;
QLabel *_pausedWarning;
QPushButton *_resetCount; QPushButton *_resetCount;
QHBoxLayout *_settingsLine1; QHBoxLayout *_settingsLine1;
QHBoxLayout *_settingsLine2; QHBoxLayout *_settingsLine2;
std::unique_ptr<QTimer> _timer; QTimer _countTimer;
QTimer _pausedTimer;
std::shared_ptr<MacroConditionMacro> _entryData; std::shared_ptr<MacroConditionMacro> _entryData;
private: private:
void ClearLayouts(); void ClearLayouts();
void SetupStateWidgets(); void SetupStateWidgets();
void SetupCountWidgets(); void SetupCountWidgets();
void ResetTimer();
bool _loading = true; bool _loading = true;
}; };

View File

@@ -0,0 +1,66 @@
#pragma once
#include "macro.hpp"
#include "scene-selection.hpp"
#include <QComboBox>
enum class SceneVisibilityCondition {
SHOWN,
HIDDEN,
};
class MacroConditionSceneVisibility : public MacroCondition {
public:
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()
{
return std::make_shared<MacroConditionSceneVisibility>();
}
SceneSelection _scene;
OBSWeakSource _source;
SceneVisibilityCondition _condition = SceneVisibilityCondition::SHOWN;
private:
static bool _registered;
static const std::string id;
};
class MacroConditionSceneVisibilityEdit : public QWidget {
Q_OBJECT
public:
MacroConditionSceneVisibilityEdit(
QWidget *parent,
std::shared_ptr<MacroConditionSceneVisibility> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionSceneVisibilityEdit(
parent,
std::dynamic_pointer_cast<MacroConditionSceneVisibility>(
cond));
}
private slots:
void SceneChanged(const SceneSelection &);
void SourceChanged(const QString &text);
void ConditionChanged(int cond);
signals:
void HeaderInfoChanged(const QString &);
protected:
SceneSelectionWidget *_scenes;
QComboBox *_sources;
QComboBox *_conditions;
std::shared_ptr<MacroConditionSceneVisibility> _entryData;
private:
bool _loading = true;
};

View File

@@ -4,6 +4,7 @@
#include <QWidget> #include <QWidget>
#include <QComboBox> #include <QComboBox>
#include <QCheckBox>
enum class SceneType { enum class SceneType {
CURRENT, CURRENT,
@@ -24,6 +25,7 @@ public:
return std::make_shared<MacroConditionScene>(); return std::make_shared<MacroConditionScene>();
} }
bool _waitForTransition = false;
SceneSelection _scene; SceneSelection _scene;
SceneType _type; SceneType _type;
@@ -52,12 +54,14 @@ public:
private slots: private slots:
void SceneChanged(const SceneSelection &); void SceneChanged(const SceneSelection &);
void TypeChanged(int value); void TypeChanged(int value);
void WaitForTransitionChanged(int state);
signals: signals:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
protected: protected:
SceneSelectionWidget *_scenes; SceneSelectionWidget *_scenes;
QComboBox *_sceneType; QComboBox *_sceneType;
QCheckBox *_waitForTransition;
std::shared_ptr<MacroConditionScene> _entryData; std::shared_ptr<MacroConditionScene> _entryData;
private: private:

View File

@@ -6,6 +6,8 @@
enum class StreamState { enum class StreamState {
STOP, STOP,
START, START,
STARTING,
STOPPING,
}; };
class MacroConditionStream : public MacroCondition { class MacroConditionStream : public MacroCondition {
@@ -22,6 +24,9 @@ public:
StreamState _streamState; StreamState _streamState;
private: private:
std::chrono::high_resolution_clock::time_point _lastStreamStartingTime{};
std::chrono::high_resolution_clock::time_point _lastStreamStoppingTime{};
static bool _registered; static bool _registered;
static const std::string id; static const std::string id;
}; };

View File

@@ -16,8 +16,14 @@ public:
{ {
return std::make_shared<MacroConditionTimer>(); return std::make_shared<MacroConditionTimer>();
} }
void Pause();
void Continue();
void Reset();
Duration _duration; Duration _duration;
bool _paused = false;
bool _saveRemaining = false;
double _remaining = 0.;
bool _oneshot = false; bool _oneshot = false;
private: private:
@@ -44,14 +50,20 @@ public:
private slots: private slots:
void DurationChanged(double seconds); void DurationChanged(double seconds);
void DurationUnitChanged(DurationUnit unit); void DurationUnitChanged(DurationUnit unit);
void SaveRemainingChanged(int state);
void AutoResetChanged(int state); void AutoResetChanged(int state);
void PauseContinueClicked();
void ResetClicked(); void ResetClicked();
void UpdateTimeRemaining(); void UpdateTimeRemaining();
protected: protected:
void SetPauseContinueButtonLabel();
DurationSelection *_duration; DurationSelection *_duration;
QCheckBox *_autoReset; QCheckBox *_autoReset;
QCheckBox *_saveRemaining;
QPushButton *_reset; QPushButton *_reset;
QPushButton *_pauseConinue;
QLabel *_remaining; QLabel *_remaining;
std::shared_ptr<MacroConditionTimer> _entryData; std::shared_ptr<MacroConditionTimer> _entryData;

View File

@@ -20,6 +20,12 @@ enum class VideoCondition {
OBJECT, OBJECT,
}; };
struct PatternMatchData {
cv::Mat4b rgbaPattern;
cv::Mat3b rgbPattern;
cv::Mat1b mask;
};
constexpr int minMinNeighbors = 3; constexpr int minMinNeighbors = 3;
constexpr int maxMinNeighbors = 6; constexpr int maxMinNeighbors = 6;
@@ -44,6 +50,9 @@ public:
OBSWeakSource _videoSource; OBSWeakSource _videoSource;
VideoCondition _condition = VideoCondition::MATCH; VideoCondition _condition = VideoCondition::MATCH;
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath"); std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
bool _useAlphaAsMask = false;
bool _usePatternForChangedCheck = false;
PatternMatchData _patternData;
double _patternThreshold = 0.8; double _patternThreshold = 0.8;
cv::CascadeClassifier _objectCascade; cv::CascadeClassifier _objectCascade;
double _scaleFactor = 1.1; double _scaleFactor = 1.1;
@@ -57,6 +66,7 @@ public:
int _throttleCount = 3; int _throttleCount = 3;
private: private:
bool OutputChanged();
bool ScreenshotContainsPattern(); bool ScreenshotContainsPattern();
bool ScreenshotContainsObject(); bool ScreenshotContainsObject();
bool Compare(); bool Compare();
@@ -119,7 +129,9 @@ private slots:
void ConditionChanged(int cond); void ConditionChanged(int cond);
void ImagePathChanged(const QString &text); void ImagePathChanged(const QString &text);
void ImageBrowseButtonClicked(); void ImageBrowseButtonClicked();
void UsePatternForChangedCheckChanged(int value);
void PatternThresholdChanged(double); void PatternThresholdChanged(double);
void UseAlphaAsMaskChanged(int value);
void ModelPathChanged(const QString &text); void ModelPathChanged(const QString &text);
void ObjectScaleThresholdChanged(double); void ObjectScaleThresholdChanged(double);
@@ -139,14 +151,17 @@ protected:
QComboBox *_videoSelection; QComboBox *_videoSelection;
QComboBox *_condition; QComboBox *_condition;
QCheckBox *_usePatternForChangedCheck;
FileSelection *_imagePath; FileSelection *_imagePath;
ThresholdSlider *_patternThreshold; ThresholdSlider *_patternThreshold;
QCheckBox *_useAlphaAsMask;
FileSelection *_modelDataPath; FileSelection *_modelDataPath;
QHBoxLayout *_modelPathLayout; QHBoxLayout *_modelPathLayout;
ThresholdSlider *_objectScaleThreshold; ThresholdSlider *_objectScaleThreshold;
QHBoxLayout *_neighborsControlLayout; QHBoxLayout *_neighborsControlLayout;
QSpinBox *_minNeighbors; QSpinBox *_minNeighbors;
QLabel *_minNeighborsDescription;
QHBoxLayout *_minSizeControlLayout; QHBoxLayout *_minSizeControlLayout;
QSpinBox *_minSizeX; QSpinBox *_minSizeX;
QSpinBox *_minSizeY; QSpinBox *_minSizeY;

View File

@@ -30,6 +30,7 @@ public:
// Use this function to avoid accidental edits when scrolling through // Use this function to avoid accidental edits when scrolling through
// list of actions and conditions // list of actions and conditions
void SetFocusPolicyOfWidgets(); void SetFocusPolicyOfWidgets();
void SetCollapsed(bool collapsed);
protected slots: protected slots:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);

View File

@@ -77,7 +77,7 @@ public:
bool Matched() { return _matched; } bool Matched() { return _matched; }
std::string Name() { return _name; } std::string Name() { return _name; }
void SetName(const std::string &name); void SetName(const std::string &name);
void SetPaused(bool pause = true) { _paused = pause; } void SetPaused(bool pause = true);
bool Paused() { return _paused; } bool Paused() { return _paused; }
int GetCount() { return _count; }; int GetCount() { return _count; };
void ResetCount() { _count = 0; }; void ResetCount() { _count = 0; };
@@ -102,6 +102,7 @@ private:
void SetupHotkeys(); void SetupHotkeys();
void ClearHotkeys(); void ClearHotkeys();
void SetHotkeysDesc(); void SetHotkeysDesc();
void ResetTimers();
std::string _name = ""; std::string _name = "";
std::deque<std::shared_ptr<MacroCondition>> _conditions; std::deque<std::shared_ptr<MacroCondition>> _conditions;
@@ -144,3 +145,9 @@ public:
void ResolveMacroRef(); void ResolveMacroRef();
MacroRef _macro; MacroRef _macro;
}; };
class MultiMacroRefAction : public MacroAction {
public:
void ResolveMacroRef();
std::vector<MacroRef> _macros;
};

View File

@@ -8,6 +8,7 @@
#pragma once #pragma once
#include "switch-generic.hpp" #include "switch-generic.hpp"
#include "duration-control.hpp"
enum class sceneTriggerType { enum class sceneTriggerType {
NONE = 0, NONE = 0,
@@ -43,7 +44,7 @@ struct SceneTrigger : SceneSwitcherEntry {
static bool pause; static bool pause;
sceneTriggerType triggerType = sceneTriggerType::NONE; sceneTriggerType triggerType = sceneTriggerType::NONE;
sceneTriggerAction triggerAction = sceneTriggerAction::NONE; sceneTriggerAction triggerAction = sceneTriggerAction::NONE;
double duration = 0; Duration duration;
OBSWeakSource audioSource = nullptr; OBSWeakSource audioSource = nullptr;
const char *getType() { return "trigger"; } const char *getType() { return "trigger"; }
@@ -70,13 +71,14 @@ public:
private slots: private slots:
void TriggerTypeChanged(int index); void TriggerTypeChanged(int index);
void TriggerActionChanged(int index); void TriggerActionChanged(int index);
void DurationChanged(double dur); void DurationChanged(double seconds);
void DurationUnitChanged(DurationUnit unit);
void AudioSourceChanged(const QString &text); void AudioSourceChanged(const QString &text);
private: private:
QComboBox *triggers; QComboBox *triggers;
QComboBox *actions; QComboBox *actions;
QDoubleSpinBox *duration; DurationSelection *duration;
QComboBox *audioSources; QComboBox *audioSources;
SceneTrigger *switchData; SceneTrigger *switchData;

View File

@@ -16,6 +16,7 @@ public:
void SetContent(QWidget *w, bool collapsed = true); void SetContent(QWidget *w, bool collapsed = true);
void AddHeaderWidget(QWidget *); void AddHeaderWidget(QWidget *);
void SetCollapsed(bool);
protected: protected:
bool eventFilter(QObject *obj, QEvent *event) override; bool eventFilter(QObject *obj, QEvent *event) override;

View File

@@ -6,6 +6,7 @@
#include <QDateTime> #include <QDateTime>
#include <QThread> #include <QThread>
#include <curl/curl.h> #include <curl/curl.h>
#include <unordered_map>
#include "scene-group.hpp" #include "scene-group.hpp"
#include "scene-trigger.hpp" #include "scene-trigger.hpp"
@@ -63,6 +64,8 @@ class SwitcherThread;
struct SwitcherData { struct SwitcherData {
SwitcherThread *th = nullptr; SwitcherThread *th = nullptr;
bool settingsWindowOpened = false;
std::condition_variable cv; std::condition_variable cv;
std::mutex m; std::mutex m;
bool transitionActive = false; bool transitionActive = false;
@@ -70,6 +73,7 @@ struct SwitcherData {
bool stop = false; bool stop = false;
bool verbose = false; bool verbose = false;
bool disableHints = false; bool disableHints = false;
bool showSystemTrayNotifications = false;
bool showFrame = false; bool showFrame = false;
bool transitionOverrideOverride = false; bool transitionOverrideOverride = false;
bool adjustActiveTransitionType = true; bool adjustActiveTransitionType = true;
@@ -81,6 +85,8 @@ struct SwitcherData {
OBSWeakSource previousScene = nullptr; OBSWeakSource previousScene = nullptr;
std::chrono::high_resolution_clock::time_point lastSceneChangeTime{}; std::chrono::high_resolution_clock::time_point lastSceneChangeTime{};
std::chrono::high_resolution_clock::time_point lastTransitionEndTime{}; std::chrono::high_resolution_clock::time_point lastTransitionEndTime{};
std::chrono::high_resolution_clock::time_point lastStreamStartingTime{};
std::chrono::high_resolution_clock::time_point lastStreamStoppingTime{};
OBSWeakSource lastRandomScene; OBSWeakSource lastRandomScene;
SceneGroup *lastRandomSceneGroup; SceneGroup *lastRandomSceneGroup;
OBSWeakSource nonMatchingScene; OBSWeakSource nonMatchingScene;
@@ -89,10 +95,14 @@ struct SwitcherData {
StartupBehavior startupBehavior = PERSIST; StartupBehavior startupBehavior = PERSIST;
AutoStartEvent autoStartEvent = AutoStartEvent::NEVER; AutoStartEvent autoStartEvent = AutoStartEvent::NEVER;
std::vector<std::thread> audioHelperThreads;
std::atomic_bool masterAudioFadeActive = {false};
std::unordered_map<std::string, std::atomic_bool> activeAudioFades;
Duration cooldown; Duration cooldown;
std::chrono::high_resolution_clock::time_point lastMatchTime; std::chrono::high_resolution_clock::time_point lastMatchTime;
std::deque<Macro> macros; std::deque<std::shared_ptr<Macro>> macros;
std::condition_variable macroWaitCv; std::condition_variable macroWaitCv;
std::atomic_bool abortMacroWait = {false}; std::atomic_bool abortMacroWait = {false};
bool macroSceneSwitched = false; bool macroSceneSwitched = false;

View File

@@ -44,7 +44,8 @@ void placeWidgets(std::string text, QBoxLayout *layout,
std::unordered_map<std::string, QWidget *> placeholders, std::unordered_map<std::string, QWidget *> placeholders,
bool addStretch = true); bool addStretch = true);
void deleteLayoutItem(QLayoutItem *item); void deleteLayoutItem(QLayoutItem *item);
void clearLayout(QLayout *layout); void clearLayout(QLayout *layout, int afterIdx = 0);
void setLayoutVisible(QLayout *layout, bool visible);
QMetaObject::Connection PulseWidget(QWidget *widget, QColor startColor, QMetaObject::Connection PulseWidget(QWidget *widget, QColor startColor,
QColor = QColor(0, 0, 0, 0), QColor = QColor(0, 0, 0, 0),
QString specifier = "QLabel ", QString specifier = "QLabel ",
@@ -55,6 +56,7 @@ void listAddClicked(QListWidget *list, QWidget *newWidget,
bool listMoveUp(QListWidget *list); bool listMoveUp(QListWidget *list);
bool listMoveDown(QListWidget *list); bool listMoveDown(QListWidget *list);
bool DisplayMessage(const QString &msg, bool question = false); bool DisplayMessage(const QString &msg, bool question = false);
void DisplayTrayMessage(const QString &title, const QString &msg);
void addSelectionEntry(QComboBox *sel, const char *description, void addSelectionEntry(QComboBox *sel, const char *description,
bool selectable = false, const char *tooltip = ""); bool selectable = false, const char *tooltip = "");
void populateTransitionSelection(QComboBox *sel, bool addCurrent = true, void populateTransitionSelection(QComboBox *sel, bool addCurrent = true,
@@ -77,3 +79,4 @@ void populateFilterSelection(QComboBox *list,
void populateSceneItemSelection(QComboBox *list, void populateSceneItemSelection(QComboBox *list,
OBSWeakSource sceneWeakSource = nullptr); OBSWeakSource sceneWeakSource = nullptr);
void populateSceneItemSelection(QComboBox *list, SceneSelection &s); void populateSceneItemSelection(QComboBox *list, SceneSelection &s);
void populateSourceTypeSelection(QComboBox *list);

View File

@@ -1 +1,2 @@
extern const char g_GIT_SHA1[]; extern const char g_GIT_SHA1[];
extern const char g_GIT_TAG[];

View File

@@ -24,6 +24,9 @@
#include <QStringList> #include <QStringList>
#include <QRegularExpression> #include <QRegularExpression>
#include <QLibrary> #include <QLibrary>
#include <proc/readproc.h>
#include <fstream>
#include <sstream>
static Display *xdisplay = 0; static Display *xdisplay = 0;
@@ -363,17 +366,78 @@ bool isFullscreen(const std::string &title)
void GetProcessList(QStringList &processes) void GetProcessList(QStringList &processes)
{ {
processes.clear(); processes.clear();
for (size_t i = 0; i < getTopLevelWindows().size(); ++i) { PROCTAB *proc = openproc(PROC_FILLSTAT);
std::string s = GetWindowTitle(i); proc_t proc_info;
if (s != "") memset(&proc_info, 0, sizeof(proc_info));
processes << QString::fromStdString(s); while (readproc(proc, &proc_info) != NULL) {
QString procName(proc_info.cmd);
if (!procName.isEmpty() && !processes.contains(proc_info.cmd)) {
processes << QString(proc_info.cmd);
}
} }
closeproc(proc);
}
int getForegroundProcessPid()
{
if (!ewmhIsSupported()) {
return -1;
}
auto dpy = disp();
Atom active = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", true);
Atom actualType;
int format;
unsigned long num, bytes;
Window *window = 0;
int pid = -1;
Window rootWin = RootWindow(dpy, 0);
int xstatus = XGetWindowProperty(dpy, rootWin, active, 0L, ~0L, false,
AnyPropertyType, &actualType, &format,
&num, &bytes, (uint8_t **)&window);
if (xstatus == 0 && window == nullptr) {
return -1;
}
Atom atom, actual_type;
int actual_format;
unsigned long nitems;
unsigned long bytes_after;
unsigned char *prop;
atom = XInternAtom(dpy, "_NET_WM_PID", True);
auto status = XGetWindowProperty(dpy, *window, atom, 0, 1024, False,
AnyPropertyType, &actual_type,
&actual_format, &nitems, &bytes_after,
&prop);
if (status != 0) {
return -2;
}
if (!prop) {
return -3;
}
pid = prop[1] * 256;
pid += prop[0];
return pid;
}
std::string getProcNameFromPid(int pid)
{
std::string path = "/proc/" + std::to_string(pid) + "/comm";
std::ifstream t(path);
std::stringstream buffer;
buffer << t.rdbuf();
return buffer.str();
} }
bool isInFocus(const QString &executable) bool isInFocus(const QString &executable)
{ {
std::string current; auto pid = getForegroundProcessPid();
GetCurrentWindowTitle(current); std::string current = getProcNameFromPid(pid);
// True if executable switch equals current window // True if executable switch equals current window
bool equals = (executable.toStdString() == current); bool equals = (executable.toStdString() == current);

View File

@@ -18,6 +18,94 @@ const static std::map<AudioAction, std::string> actionTypes = {
"AdvSceneSwitcher.action.audio.type.masterVolume"}, "AdvSceneSwitcher.action.audio.type.masterVolume"},
}; };
constexpr auto fadeInterval = std::chrono::milliseconds(100);
constexpr float minFade = 0.000001f;
void fadeSourceVolume(Duration duration, float vol, OBSWeakSource audioSource)
{
auto s = obs_weak_source_get_source(audioSource);
if (!s) {
return;
}
float curVol = obs_source_get_volume(s);
obs_source_release(s);
bool volIncrease = curVol <= vol;
int nrSteps = duration.seconds * 1000 / fadeInterval.count();
float volDiff = (volIncrease) ? vol - curVol : curVol - vol;
float volStep = volDiff / nrSteps;
if (volStep < minFade) {
switcher->activeAudioFades[GetWeakSourceName(audioSource)] =
false;
return;
}
for (int step = 0; step < nrSteps && !switcher->stop; ++step) {
auto s = obs_weak_source_get_source(audioSource);
if (!s) {
return;
}
curVol = (volIncrease) ? curVol + volStep : curVol - volStep;
obs_source_set_volume(s, curVol);
std::this_thread::sleep_for(fadeInterval);
obs_source_release(s);
}
switcher->activeAudioFades[GetWeakSourceName(audioSource)] = false;
}
void fadeMasterVolume(Duration duration, float vol)
{
float curVol = obs_get_master_volume();
bool volIncrease = curVol <= vol;
int nrSteps = duration.seconds * 1000 / fadeInterval.count();
float volDiff = (volIncrease) ? vol - curVol : curVol - vol;
float volStep = volDiff / nrSteps;
if (volStep < minFade) {
switcher->masterAudioFadeActive = false;
return;
}
for (int step = 0; step < nrSteps && !switcher->stop; ++step) {
curVol = (volIncrease) ? curVol + volStep : curVol - volStep;
obs_set_master_volume(curVol);
std::this_thread::sleep_for(fadeInterval);
}
switcher->masterAudioFadeActive = false;
}
void startSourceFade(Duration &duration, float vol, OBSWeakSource audioSource)
{
if (!audioSource) {
return;
}
auto it =
switcher->activeAudioFades.find(GetWeakSourceName(audioSource));
if (it != switcher->activeAudioFades.end() && it->second == true) {
blog(LOG_WARNING,
"Audio fade for volume of %s already active! New fade request will be ignored!",
GetWeakSourceName(audioSource).c_str());
return;
}
switcher->activeAudioFades[GetWeakSourceName(audioSource)] = true;
switcher->audioHelperThreads.emplace_back(fadeSourceVolume, duration,
vol, audioSource);
}
void startMasterFade(Duration &duration, float vol)
{
if (switcher->masterAudioFadeActive) {
blog(LOG_WARNING,
"Audio fade for master volume already active! New fade request will be ignored!");
return;
}
switcher->masterAudioFadeActive = true;
switcher->audioHelperThreads.emplace_back(fadeMasterVolume, duration,
vol);
}
bool MacroActionAudio::PerformAction() bool MacroActionAudio::PerformAction()
{ {
auto s = obs_weak_source_get_source(_audioSource); auto s = obs_weak_source_get_source(_audioSource);
@@ -29,10 +117,19 @@ bool MacroActionAudio::PerformAction()
obs_source_set_muted(s, false); obs_source_set_muted(s, false);
break; break;
case AudioAction::SOURCE_VOLUME: case AudioAction::SOURCE_VOLUME:
obs_source_set_volume(s, (float)_volume / 100.0f); if (_fade) {
startSourceFade(_duration, (float)_volume / 100.0f,
_audioSource);
} else {
obs_source_set_volume(s, (float)_volume / 100.0f);
}
break; break;
case AudioAction::MASTER_VOLUME: case AudioAction::MASTER_VOLUME:
obs_set_master_volume((float)_volume / 100.0f); if (_fade) {
startMasterFade(_duration, (float)_volume / 100.0f);
} else {
obs_set_master_volume((float)_volume / 100.0f);
}
break; break;
default: default:
break; break;
@@ -46,9 +143,10 @@ void MacroActionAudio::LogAction()
auto it = actionTypes.find(_action); auto it = actionTypes.find(_action);
if (it != actionTypes.end()) { if (it != actionTypes.end()) {
vblog(LOG_INFO, vblog(LOG_INFO,
"performed action \"%s\" for source \"%s\" with volume %d", "performed action \"%s\" for source \"%s\" with volume %d with fade %d %f",
it->second.c_str(), it->second.c_str(),
GetWeakSourceName(_audioSource).c_str(), _volume); GetWeakSourceName(_audioSource).c_str(), _volume, _fade,
_duration.seconds);
} else { } else {
blog(LOG_WARNING, "ignored unknown audio action %d", blog(LOG_WARNING, "ignored unknown audio action %d",
static_cast<int>(_action)); static_cast<int>(_action));
@@ -58,20 +156,24 @@ void MacroActionAudio::LogAction()
bool MacroActionAudio::Save(obs_data_t *obj) bool MacroActionAudio::Save(obs_data_t *obj)
{ {
MacroAction::Save(obj); MacroAction::Save(obj);
_duration.Save(obj);
obs_data_set_string(obj, "audioSource", obs_data_set_string(obj, "audioSource",
GetWeakSourceName(_audioSource).c_str()); GetWeakSourceName(_audioSource).c_str());
obs_data_set_int(obj, "action", static_cast<int>(_action)); obs_data_set_int(obj, "action", static_cast<int>(_action));
obs_data_set_int(obj, "volume", _volume); obs_data_set_int(obj, "volume", _volume);
obs_data_set_bool(obj, "fade", _fade);
return true; return true;
} }
bool MacroActionAudio::Load(obs_data_t *obj) bool MacroActionAudio::Load(obs_data_t *obj)
{ {
MacroAction::Load(obj); MacroAction::Load(obj);
_duration.Load(obj);
const char *audioSourceName = obs_data_get_string(obj, "audioSource"); const char *audioSourceName = obs_data_get_string(obj, "audioSource");
_audioSource = GetWeakSourceByName(audioSourceName); _audioSource = GetWeakSourceByName(audioSourceName);
_action = static_cast<AudioAction>(obs_data_get_int(obj, "action")); _action = static_cast<AudioAction>(obs_data_get_int(obj, "action"));
_volume = obs_data_get_int(obj, "volume"); _volume = obs_data_get_int(obj, "volume");
_fade = obs_data_get_bool(obj, "fade");
return true; return true;
} }
@@ -100,6 +202,8 @@ MacroActionAudioEdit::MacroActionAudioEdit(
_volumePercent->setMinimum(0); _volumePercent->setMinimum(0);
_volumePercent->setMaximum(2000); _volumePercent->setMaximum(2000);
_volumePercent->setSuffix("%"); _volumePercent->setSuffix("%");
_fade = new QCheckBox();
_duration = new DurationSelection(parent, false);
populateActionSelection(_actions); populateActionSelection(_actions);
populateAudioSelection(_audioSources); populateAudioSelection(_audioSources);
@@ -111,15 +215,25 @@ MacroActionAudioEdit::MacroActionAudioEdit(
SLOT(SourceChanged(const QString &))); SLOT(SourceChanged(const QString &)));
QWidget::connect(_volumePercent, SIGNAL(valueChanged(int)), this, QWidget::connect(_volumePercent, SIGNAL(valueChanged(int)), this,
SLOT(VolumeChanged(int))); SLOT(VolumeChanged(int)));
QWidget::connect(_fade, SIGNAL(stateChanged(int)), this,
SLOT(FadeChanged(int)));
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = { std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{audioSources}}", _audioSources}, {"{{audioSources}}", _audioSources}, {"{{actions}}", _actions},
{"{{actions}}", _actions}, {"{{volume}}", _volumePercent}, {"{{fade}}", _fade},
{"{{volume}}", _volumePercent}, {"{{duration}}", _duration},
}; };
QHBoxLayout *entryLayout = new QHBoxLayout;
placeWidgets(obs_module_text("AdvSceneSwitcher.action.audio.entry"), placeWidgets(obs_module_text("AdvSceneSwitcher.action.audio.entry"),
mainLayout, widgetPlaceholders); entryLayout, widgetPlaceholders);
_fadeLayout = new QHBoxLayout;
placeWidgets(obs_module_text("AdvSceneSwitcher.action.audio.fade"),
_fadeLayout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLayout);
mainLayout->addLayout(_fadeLayout);
setLayout(mainLayout); setLayout(mainLayout);
_entryData = entryData; _entryData = entryData;
@@ -140,19 +254,11 @@ bool hasSourceControl(AudioAction action)
void MacroActionAudioEdit::SetWidgetVisibility() void MacroActionAudioEdit::SetWidgetVisibility()
{ {
if (hasVolumeControl(_entryData->_action)) { _volumePercent->setVisible(hasVolumeControl(_entryData->_action));
_volumePercent->show(); _audioSources->setVisible(hasSourceControl(_entryData->_action));
} else { setLayoutVisible(_fadeLayout, hasVolumeControl(_entryData->_action));
_volumePercent->hide(); adjustSize();
}
if (hasSourceControl(_entryData->_action)) {
_audioSources->show();
} else {
_audioSources->hide();
}
} }
void MacroActionAudioEdit::UpdateEntryData() void MacroActionAudioEdit::UpdateEntryData()
{ {
if (!_entryData) { if (!_entryData) {
@@ -163,7 +269,8 @@ void MacroActionAudioEdit::UpdateEntryData()
GetWeakSourceName(_entryData->_audioSource).c_str()); GetWeakSourceName(_entryData->_audioSource).c_str());
_actions->setCurrentIndex(static_cast<int>(_entryData->_action)); _actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_volumePercent->setValue(_entryData->_volume); _volumePercent->setValue(_entryData->_volume);
_fade->setChecked(_entryData->_fade);
_duration->SetDuration(_entryData->_duration);
SetWidgetVisibility(); SetWidgetVisibility();
} }
@@ -199,3 +306,23 @@ void MacroActionAudioEdit::VolumeChanged(int value)
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_volume = value; _entryData->_volume = value;
} }
void MacroActionAudioEdit::FadeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_fade = value;
}
void MacroActionAudioEdit::DurationChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.seconds = seconds;
}

View File

@@ -92,12 +92,14 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
return; return;
} }
auto idx = _entryData->get()->GetIndex();
std::string id = MacroActionFactory::GetIdByName(text); std::string id = MacroActionFactory::GetIdByName(text);
HeaderInfoChanged(""); HeaderInfoChanged("");
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
_entryData->reset(); _entryData->reset();
*_entryData = MacroActionFactory::Create(id); *_entryData = MacroActionFactory::Create(id);
(*_entryData)->SetIndex(idx);
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData); auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)), QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &))); this, SLOT(HeaderInfoChanged(const QString &)));
@@ -152,12 +154,8 @@ void AdvSceneSwitcher::AddMacroAction(int idx)
} }
macro->UpdateActionIndices(); macro->UpdateActionIndices();
// All entry pointers in existing edit widgets after the new entry will clearLayout(ui->macroEditActionLayout, idx);
// be invalidated by adding the new entry so we have to recreate them PopulateMacroActions(*macro, idx);
// and because I am lazy I am recreating every widget.
//
// If performance should become a concern this has to be revisited.
SetEditMacro(*macro);
HighlightAction(idx); HighlightAction(idx);
} }
@@ -189,12 +187,8 @@ void AdvSceneSwitcher::RemoveMacroAction(int idx)
switcher->macroWaitCv.notify_one(); switcher->macroWaitCv.notify_one();
macro->UpdateActionIndices(); macro->UpdateActionIndices();
// All entry pointers in existing edit widgets after the new entry will clearLayout(ui->macroEditActionLayout, idx);
// be invalidated by adding the new entry so we have to recreate them PopulateMacroActions(*macro, idx);
// and because I am lazy I am recreating every widget.
//
// If performance should become a concern this has to be revisited.
SetEditMacro(*macro);
} }
void AdvSceneSwitcher::on_actionRemove_clicked() void AdvSceneSwitcher::on_actionRemove_clicked()

View File

@@ -94,6 +94,7 @@ MacroActionFilterEdit::MacroActionFilterEdit(
{ {
_sources = new QComboBox(); _sources = new QComboBox();
_filters = new QComboBox(); _filters = new QComboBox();
_filters->setSizeAdjustPolicy(QComboBox::AdjustToContents);
_actions = new QComboBox(); _actions = new QComboBox();
_getSettings = new QPushButton( _getSettings = new QPushButton(
obs_module_text("AdvSceneSwitcher.action.filter.getSettings")); obs_module_text("AdvSceneSwitcher.action.filter.getSettings"));
@@ -165,6 +166,7 @@ void MacroActionFilterEdit::SourceChanged(const QString &text)
} }
_filters->clear(); _filters->clear();
populateFilterSelection(_filters, _entryData->_source); populateFilterSelection(_filters, _entryData->_source);
_filters->adjustSize();
} }
void MacroActionFilterEdit::FilterChanged(const QString &text) void MacroActionFilterEdit::FilterChanged(const QString &text)

View File

@@ -141,7 +141,7 @@ obs_key_combination keysToOBSKeycombo(const std::vector<HotkeyType> &keys)
return combo; return combo;
} }
for (int i = 0; i < keys.size() - 1; i++) { for (uint32_t i = 0; i < keys.size() - 1; i++) {
switch (keys[i]) { switch (keys[i]) {
case HotkeyType::Key_Shift_L: case HotkeyType::Key_Shift_L:
combo.modifiers |= INTERACT_SHIFT_KEY; combo.modifiers |= INTERACT_SHIFT_KEY;

263
src/macro-action-random.cpp Normal file
View File

@@ -0,0 +1,263 @@
#include "headers/macro-action-random.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/name-dialog.hpp"
#include "headers/utility.hpp"
#include <cstdlib>
const std::string MacroActionRandom::id = "random";
bool MacroActionRandom::_registered = MacroActionFactory::Register(
MacroActionRandom::id,
{MacroActionRandom::Create, MacroActionRandomEdit::Create,
"AdvSceneSwitcher.action.random"});
std::vector<MacroRef> getPossibleMacros(std::vector<MacroRef> &macros,
MacroRef &lastRandomMacro)
{
std::vector<MacroRef> res;
if (macros.size() == 1) {
if (macros[0]->Paused()) {
return res;
} else {
return macros;
}
}
for (auto &m : macros) {
if (m.get() && !m->Paused() &&
!(lastRandomMacro.get() == m.get())) {
res.push_back(m);
}
}
return res;
}
bool MacroActionRandom::PerformAction()
{
if (_macros.size() == 0) {
return true;
}
auto macros = getPossibleMacros(_macros, lastRandomMacro);
if (macros.size() == 0) {
return true;
}
if (macros.size() == 1) {
lastRandomMacro = macros[0];
return macros[0]->PerformAction();
}
size_t idx = std::rand() % (macros.size());
lastRandomMacro = macros[idx];
return macros[idx]->PerformAction();
}
void MacroActionRandom::LogAction()
{
vblog(LOG_INFO, "running random macro");
}
bool MacroActionRandom::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
obs_data_array_t *args = obs_data_array_create();
for (auto &m : _macros) {
obs_data_t *array_obj = obs_data_create();
m.Save(array_obj);
obs_data_array_push_back(args, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "macros", args);
obs_data_array_release(args);
return true;
}
bool MacroActionRandom::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
obs_data_array_t *args = obs_data_get_array(obj, "macros");
size_t count = obs_data_array_count(args);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(args, i);
MacroRef ref;
ref.Load(array_obj);
_macros.push_back(ref);
obs_data_release(array_obj);
}
obs_data_array_release(args);
return true;
}
MacroActionRandomEdit::MacroActionRandomEdit(
QWidget *parent, std::shared_ptr<MacroActionRandom> entryData)
: QWidget(parent)
{
_macroSelection = new MacroSelection(window());
_macroList = new QListWidget();
_macroList->setSortingEnabled(true);
_add = new QPushButton();
_add->setMaximumSize(QSize(22, 22));
_add->setProperty("themeID",
QVariant(QString::fromUtf8("addIconSmall")));
_add->setFlat(true);
_remove = new QPushButton();
_remove->setMaximumSize(QSize(22, 22));
_remove->setProperty("themeID",
QVariant(QString::fromUtf8("removeIconSmall")));
_remove->setFlat(true);
QWidget::connect(_add, SIGNAL(clicked()), this, SLOT(AddMacro()));
QWidget::connect(_remove, SIGNAL(clicked()), this, SLOT(RemoveMacro()));
QWidget::connect(_macroList, SIGNAL(currentRowChanged(int)), this,
SLOT(MacroSelectionChanged(int)));
QWidget::connect(window(),
SIGNAL(MacroRenamed(const QString &, const QString &)),
this,
SLOT(MacroRename(const QString &, const QString &)));
auto *entryLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{macroSelection}}", _macroSelection},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.random.entry"),
entryLayout, widgetPlaceholders);
auto *argButtonLayout = new QHBoxLayout;
argButtonLayout->addWidget(_add);
argButtonLayout->addWidget(_remove);
argButtonLayout->addStretch();
auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLayout);
mainLayout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.action.random.arguments")));
mainLayout->addWidget(_macroList);
mainLayout->addLayout(argButtonLayout);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionRandomEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
for (auto &m : _entryData->_macros) {
if (!m.get()) {
continue;
}
auto name = QString::fromStdString(m->Name());
QListWidgetItem *item = new QListWidgetItem(name, _macroList);
item->setData(Qt::UserRole, name);
}
}
void MacroActionRandomEdit::MacroRemove(const QString &name)
{
if (_entryData) {
auto it = _entryData->_macros.begin();
while (it != _entryData->_macros.end()) {
if (it->get()->Name() == name.toStdString()) {
it = _entryData->_macros.erase(it);
} else {
++it;
}
}
}
}
void MacroActionRandomEdit::MacroRename(const QString &oldName,
const QString &newName)
{
auto count = _macroList->count();
for (int idx = 0; idx < count; ++idx) {
QListWidgetItem *item = _macroList->item(idx);
QString itemString = item->data(Qt::UserRole).toString();
if (oldName == itemString) {
item->setData(Qt::UserRole, newName);
item->setText(newName);
break;
}
}
}
void MacroActionRandomEdit::AddMacro()
{
if (_loading || !_entryData) {
return;
}
auto macroName = _macroSelection->currentText();
MacroRef macro(macroName.toStdString());
if (!macro.get()) {
return;
}
if (FindEntry(macro->Name()) != -1) {
return;
}
QVariant v = QVariant::fromValue(macroName);
QListWidgetItem *item = new QListWidgetItem(macroName, _macroList);
item->setData(Qt::UserRole, macroName);
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_macros.push_back(macro);
}
void MacroActionRandomEdit::RemoveMacro()
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
auto item = _macroList->currentItem();
if (!item) {
return;
}
std::string name = item->data(Qt::UserRole).toString().toStdString();
for (auto it = _entryData->_macros.begin();
it != _entryData->_macros.end(); ++it) {
auto m = *it;
if (m.get() && m->Name() == name) {
_entryData->_macros.erase(it);
break;
}
}
delete item;
}
int MacroActionRandomEdit::FindEntry(const std::string &macro)
{
int count = _macroList->count();
int idx = -1;
for (int i = 0; i < count; i++) {
QListWidgetItem *item = _macroList->item(i);
QString itemString = item->data(Qt::UserRole).toString();
if (QString::fromStdString(macro) == itemString) {
idx = i;
break;
}
}
return idx;
}
void MacroActionRandomEdit::MacroSelectionChanged(int idx)
{
if (_loading || !_entryData || idx == -1) {
return;
}
QListWidgetItem *item = _macroList->item(idx);
QString name = item->text();
_macroSelection->SetCurrentMacro(GetMacroByName(name.toUtf8().data()));
}

View File

@@ -17,14 +17,22 @@ const static std::map<SceneVisibilityAction, std::string> actionTypes = {
"AdvSceneSwitcher.action.sceneVisibility.type.hide"}, "AdvSceneSwitcher.action.sceneVisibility.type.hide"},
}; };
struct VisInfo { const static std::map<SceneItemSourceType, std::string> sourceItemSourceTypes = {
{SceneItemSourceType::SOURCE,
"AdvSceneSwitcher.action.sceneVisibility.type.source"},
{SceneItemSourceType::SOURCE_GROUP,
"AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup"},
};
struct VisibilityData {
std::string name; std::string name;
bool visible; bool visible;
}; };
static bool visibilityEnum(obs_scene_t *, obs_sceneitem_t *item, void *ptr) static bool visibilitySourceEnum(obs_scene_t *, obs_sceneitem_t *item,
void *ptr)
{ {
VisInfo *vInfo = reinterpret_cast<VisInfo *>(ptr); VisibilityData *vInfo = reinterpret_cast<VisibilityData *>(ptr);
auto sourceName = obs_source_get_name(obs_sceneitem_get_source(item)); auto sourceName = obs_source_get_name(obs_sceneitem_get_source(item));
if (vInfo->name == sourceName) { if (vInfo->name == sourceName) {
obs_sceneitem_set_visible(item, vInfo->visible); obs_sceneitem_set_visible(item, vInfo->visible);
@@ -32,7 +40,26 @@ static bool visibilityEnum(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
if (obs_sceneitem_is_group(item)) { if (obs_sceneitem_is_group(item)) {
obs_scene_t *scene = obs_sceneitem_group_get_scene(item); obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
obs_scene_enum_items(scene, visibilityEnum, ptr); obs_scene_enum_items(scene, visibilitySourceEnum, ptr);
}
return true;
}
static bool visibilitySourceTypeEnum(obs_scene_t *, obs_sceneitem_t *item,
void *ptr)
{
VisibilityData *vInfo = reinterpret_cast<VisibilityData *>(ptr);
auto sourceTypeName = obs_source_get_display_name(
obs_source_get_id(obs_sceneitem_get_source(item)));
if (vInfo->name == sourceTypeName) {
obs_sceneitem_set_visible(item, vInfo->visible);
}
if (obs_sceneitem_is_group(item)) {
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
obs_scene_enum_items(scene, visibilitySourceTypeEnum, ptr);
} }
return true; return true;
@@ -43,18 +70,21 @@ bool MacroActionSceneVisibility::PerformAction()
auto s = obs_weak_source_get_source(_scene.GetScene()); auto s = obs_weak_source_get_source(_scene.GetScene());
auto scene = obs_scene_from_source(s); auto scene = obs_scene_from_source(s);
auto sourceName = GetWeakSourceName(_source); auto sourceName = GetWeakSourceName(_source);
VisInfo vInfo = {sourceName, _action == SceneVisibilityAction::SHOW}; VisibilityData vInfo = {"", _action == SceneVisibilityAction::SHOW};
switch (_action) { switch (_sourceType) {
case SceneVisibilityAction::SHOW: case SceneItemSourceType::SOURCE:
obs_scene_enum_items(scene, visibilityEnum, &vInfo); vInfo.name = sourceName;
obs_scene_enum_items(scene, visibilitySourceEnum, &vInfo);
break; break;
case SceneVisibilityAction::HIDE: case SceneItemSourceType::SOURCE_GROUP:
obs_scene_enum_items(scene, visibilityEnum, &vInfo); vInfo.name = _sourceGroup;
obs_scene_enum_items(scene, visibilitySourceTypeEnum, &vInfo);
break; break;
default: default:
break; break;
} }
obs_source_release(s); obs_source_release(s);
return true; return true;
} }
@@ -63,10 +93,18 @@ void MacroActionSceneVisibility::LogAction()
{ {
auto it = actionTypes.find(_action); auto it = actionTypes.find(_action);
if (it != actionTypes.end()) { if (it != actionTypes.end()) {
vblog(LOG_INFO, if (_sourceType == SceneItemSourceType::SOURCE) {
"performed visibility action \"%s\" for source \"%s\" on scene \"%s\"", vblog(LOG_INFO,
it->second.c_str(), GetWeakSourceName(_source).c_str(), "performed visibility action \"%s\" for source \"%s\" on scene \"%s\"",
_scene.ToString().c_str()); it->second.c_str(),
GetWeakSourceName(_source).c_str(),
_scene.ToString().c_str());
} else {
vblog(LOG_INFO,
"performed visibility action \"%s\" for any source type \"%s\" on scene \"%s\"",
it->second.c_str(), _sourceGroup.c_str(),
_scene.ToString().c_str());
}
} else { } else {
blog(LOG_WARNING, "ignored unknown SceneVisibility action %d", blog(LOG_WARNING, "ignored unknown SceneVisibility action %d",
static_cast<int>(_action)); static_cast<int>(_action));
@@ -77,8 +115,14 @@ bool MacroActionSceneVisibility::Save(obs_data_t *obj)
{ {
MacroAction::Save(obj); MacroAction::Save(obj);
_scene.Save(obj); _scene.Save(obj);
obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str()); if (_sourceType == SceneItemSourceType::SOURCE) {
obs_data_set_string(obj, "source",
GetWeakSourceName(_source).c_str());
} else {
obs_data_set_string(obj, "source", _sourceGroup.c_str());
}
obs_data_set_int(obj, "action", static_cast<int>(_action)); obs_data_set_int(obj, "action", static_cast<int>(_action));
obs_data_set_int(obj, "sourceType", static_cast<int>(_sourceType));
return true; return true;
} }
@@ -86,8 +130,11 @@ bool MacroActionSceneVisibility::Load(obs_data_t *obj)
{ {
MacroAction::Load(obj); MacroAction::Load(obj);
_scene.Load(obj); _scene.Load(obj);
_sourceType = static_cast<SceneItemSourceType>(
obs_data_get_int(obj, "sourceType"));
const char *sourceName = obs_data_get_string(obj, "source"); const char *sourceName = obs_data_get_string(obj, "source");
_source = GetWeakSourceByName(sourceName); _source = GetWeakSourceByName(sourceName);
_sourceGroup = sourceName;
_action = static_cast<SceneVisibilityAction>( _action = static_cast<SceneVisibilityAction>(
obs_data_get_int(obj, "action")); obs_data_get_int(obj, "action"));
return true; return true;
@@ -95,9 +142,16 @@ bool MacroActionSceneVisibility::Load(obs_data_t *obj)
std::string MacroActionSceneVisibility::GetShortDesc() std::string MacroActionSceneVisibility::GetShortDesc()
{ {
if (_source) { if (_sourceType == SceneItemSourceType::SOURCE && _source) {
return _scene.ToString() + " - " + GetWeakSourceName(_source); return _scene.ToString() + " - " + GetWeakSourceName(_source);
} }
if (_sourceType == SceneItemSourceType::SOURCE_GROUP &&
!_sourceGroup.empty()) {
return _scene.ToString() + " - " +
obs_module_text(
"AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup") +
" " + _sourceGroup;
}
return ""; return "";
} }
@@ -108,31 +162,44 @@ static inline void populateActionSelection(QComboBox *list)
} }
} }
static inline void populateSourceItemTypeSelection(QComboBox *list)
{
for (auto entry : sourceItemSourceTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionSceneVisibilityEdit::MacroActionSceneVisibilityEdit( MacroActionSceneVisibilityEdit::MacroActionSceneVisibilityEdit(
QWidget *parent, std::shared_ptr<MacroActionSceneVisibility> entryData) QWidget *parent, std::shared_ptr<MacroActionSceneVisibility> entryData)
: QWidget(parent) : QWidget(parent)
{ {
_scenes = new SceneSelectionWidget(window(), false, false, true); _scenes = new SceneSelectionWidget(window(), false, true, true);
_sourceTypes = new QComboBox();
_sources = new QComboBox(); _sources = new QComboBox();
_sources->setSizeAdjustPolicy(QComboBox::AdjustToContents);
_actions = new QComboBox(); _actions = new QComboBox();
populateSourceItemTypeSelection(_sourceTypes);
populateActionSelection(_actions); populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int))); SLOT(ActionChanged(int)));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)), QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &))); this, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_sourceTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(SourceTypeChanged(int)));
QWidget::connect(_sources, SIGNAL(currentTextChanged(const QString &)), QWidget::connect(_sources, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(SourceChanged(const QString &))); this, SLOT(SourceChanged(const QString &)));
QHBoxLayout *mainLayout = new QHBoxLayout; QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = { std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes}, {"{{scenes}}", _scenes},
{"{{sourceTypes}}", _sourceTypes},
{"{{sources}}", _sources}, {"{{sources}}", _sources},
{"{{actions}}", _actions}, {"{{actions}}", _actions},
}; };
placeWidgets(obs_module_text( placeWidgets(obs_module_text(
"AdvSceneSwitcher.action.SceneVisibility.entry"), "AdvSceneSwitcher.action.sceneVisibility.entry"),
mainLayout, widgetPlaceholders); mainLayout, widgetPlaceholders);
setLayout(mainLayout); setLayout(mainLayout);
@@ -148,10 +215,17 @@ void MacroActionSceneVisibilityEdit::UpdateEntryData()
} }
_actions->setCurrentIndex(static_cast<int>(_entryData->_action)); _actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_sourceTypes->setCurrentIndex(
static_cast<int>(_entryData->_sourceType));
_scenes->SetScene(_entryData->_scene); _scenes->SetScene(_entryData->_scene);
populateSceneItemSelection(_sources, _entryData->_scene); if (_entryData->_sourceType == SceneItemSourceType::SOURCE) {
_sources->setCurrentText( populateSceneItemSelection(_sources, _entryData->_scene);
GetWeakSourceName(_entryData->_source).c_str()); _sources->setCurrentText(
GetWeakSourceName(_entryData->_source).c_str());
} else {
populateSourceTypeSelection(_sources);
_sources->setCurrentText(_entryData->_sourceGroup.c_str());
}
} }
void MacroActionSceneVisibilityEdit::SceneChanged(const SceneSelection &s) void MacroActionSceneVisibilityEdit::SceneChanged(const SceneSelection &s)
@@ -165,6 +239,26 @@ void MacroActionSceneVisibilityEdit::SceneChanged(const SceneSelection &s)
} }
_sources->clear(); _sources->clear();
populateSceneItemSelection(_sources, _entryData->_scene); populateSceneItemSelection(_sources, _entryData->_scene);
_sources->adjustSize();
}
void MacroActionSceneVisibilityEdit::SourceTypeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_sourceType =
static_cast<SceneItemSourceType>(value);
}
_sources->clear();
if (_entryData->_sourceType == SceneItemSourceType::SOURCE) {
populateSceneItemSelection(_sources, _entryData->_scene);
} else {
populateSourceTypeSelection(_sources);
}
} }
void MacroActionSceneVisibilityEdit::SourceChanged(const QString &text) void MacroActionSceneVisibilityEdit::SourceChanged(const QString &text)
@@ -174,7 +268,15 @@ void MacroActionSceneVisibilityEdit::SourceChanged(const QString &text)
} }
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_source = GetWeakSourceByQString(text); if (_entryData->_sourceType == SceneItemSourceType::SOURCE) {
_entryData->_source = GetWeakSourceByQString(text);
} else {
if (text == obs_module_text("AdvSceneSwitcher.selectItem")) {
_entryData->_sourceGroup = "";
} else {
_entryData->_sourceGroup = text.toStdString();
}
}
emit HeaderInfoChanged( emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc())); QString::fromStdString(_entryData->GetShortDesc()));
} }

228
src/macro-action-timer.cpp Normal file
View File

@@ -0,0 +1,228 @@
#include "headers/macro-action-timer.hpp"
#include "headers/macro-condition-timer.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
#include <random>
const std::string MacroActionTimer::id = "timer";
bool MacroActionTimer::_registered = MacroActionFactory::Register(
MacroActionTimer::id,
{MacroActionTimer::Create, MacroActionTimerEdit::Create,
"AdvSceneSwitcher.action.Timer"});
static std::map<TimerAction, std::string> timerActions = {
{TimerAction::PAUSE, "AdvSceneSwitcher.action.timer.type.pause"},
{TimerAction::CONTINUE, "AdvSceneSwitcher.action.timer.type.continue"},
{TimerAction::RESET, "AdvSceneSwitcher.action.timer.type.reset"},
{TimerAction::SET_TIME_REMAINING,
"AdvSceneSwitcher.action.timer.type.setTimeRemaining"},
};
bool MacroActionTimer::PerformAction()
{
if (!_macro.get()) {
return true;
}
for (auto c : _macro->Conditions()) {
if (c->GetId() != "timer") {
continue;
}
auto timerCondition =
dynamic_cast<MacroConditionTimer *>(c.get());
if (!timerCondition) {
continue;
}
switch (_actionType) {
case TimerAction::PAUSE:
timerCondition->Pause();
break;
case TimerAction::CONTINUE:
timerCondition->Continue();
break;
case TimerAction::RESET:
timerCondition->Reset();
break;
case TimerAction::SET_TIME_REMAINING:
timerCondition->_duration.SetTimeRemaining(
_duration.seconds);
break;
default:
break;
}
}
return true;
}
void MacroActionTimer::LogAction()
{
if (!_macro.get()) {
return;
}
switch (_actionType) {
case TimerAction::PAUSE:
vblog(LOG_INFO, "paused timers on \"%s\"",
_macro->Name().c_str());
break;
case TimerAction::CONTINUE:
vblog(LOG_INFO, "continued timers on \"%s\"",
_macro->Name().c_str());
break;
case TimerAction::RESET:
vblog(LOG_INFO, "reset timers on \"%s\"",
_macro->Name().c_str());
break;
case TimerAction::SET_TIME_REMAINING:
vblog(LOG_INFO,
"set time remaining of timers on \"%s\" to \"%s\"",
_macro->Name().c_str(), _duration.ToString().c_str());
break;
default:
break;
}
}
bool MacroActionTimer::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
_macro.Save(obj);
_duration.Save(obj);
obs_data_set_int(obj, "actionType", static_cast<int>(_actionType));
return true;
}
bool MacroActionTimer::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_macro.Load(obj);
_duration.Load(obj);
_actionType =
static_cast<TimerAction>(obs_data_get_int(obj, "actionType"));
return true;
}
std::string MacroActionTimer::GetShortDesc()
{
if (_macro.get()) {
return _macro->Name();
}
return "";
}
static inline void populateTypeSelection(QComboBox *list)
{
for (auto entry : timerActions) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionTimerEdit::MacroActionTimerEdit(
QWidget *parent, std::shared_ptr<MacroActionTimer> entryData)
: QWidget(parent)
{
_macros = new MacroSelection(parent);
_duration = new DurationSelection();
_timerAction = new QComboBox();
populateTypeSelection(_timerAction);
QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(MacroChanged(const QString &)));
QWidget::connect(parent, SIGNAL(MacroRemoved(const QString &)), this,
SLOT(MacroRemove(const QString &)));
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(DurationUnitChanged(DurationUnit)));
QWidget::connect(_timerAction, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionTypeChanged(int)));
_mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{macros}}", _macros},
{"{{duration}}", _duration},
{"{{timerAction}}", _timerAction},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.timer.entry"),
_mainLayout, widgetPlaceholders);
setLayout(_mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionTimerEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_macros->SetCurrentMacro(_entryData->_macro.get());
_duration->SetDuration(_entryData->_duration);
_timerAction->setCurrentIndex(
static_cast<int>(_entryData->_actionType));
SetWidgetVisibility();
}
void MacroActionTimerEdit::ActionTypeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_actionType = static_cast<TimerAction>(value);
SetWidgetVisibility();
}
void MacroActionTimerEdit::SetWidgetVisibility()
{
if (!_entryData) {
return;
}
_duration->setVisible(_entryData->_actionType ==
TimerAction::SET_TIME_REMAINING);
adjustSize();
}
void MacroActionTimerEdit::DurationChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.seconds = seconds;
}
void MacroActionTimerEdit::DurationUnitChanged(DurationUnit unit)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.displayUnit = unit;
}
void MacroActionTimerEdit::MacroChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_macro.UpdateRef(text);
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionTimerEdit::MacroRemove(const QString &)
{
if (_entryData) {
_entryData->_macro.UpdateRef();
}
}

View File

@@ -188,6 +188,7 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
return; return;
} }
auto idx = _entryData->get()->GetIndex();
std::string id = MacroConditionFactory::GetIdByName(text); std::string id = MacroConditionFactory::GetIdByName(text);
auto temp = DurationConstraint(); auto temp = DurationConstraint();
@@ -198,6 +199,7 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
auto logic = (*_entryData)->GetLogicType(); auto logic = (*_entryData)->GetLogicType();
_entryData->reset(); _entryData->reset();
*_entryData = MacroConditionFactory::Create(id); *_entryData = MacroConditionFactory::Create(id);
(*_entryData)->SetIndex(idx);
(*_entryData)->SetLogicType(logic); (*_entryData)->SetLogicType(logic);
auto widget = auto widget =
MacroConditionFactory::CreateWidget(id, this, *_entryData); MacroConditionFactory::CreateWidget(id, this, *_entryData);
@@ -276,12 +278,8 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
(*cond)->SetLogicType(logic); (*cond)->SetLogicType(logic);
macro->UpdateConditionIndices(); macro->UpdateConditionIndices();
// All entry pointers in existing edit widgets after the new entry will clearLayout(ui->macroEditConditionLayout, idx);
// be invalidated by adding the new entry so we have to recreate them PopulateMacroConditions(*macro, idx);
// and because I am lazy I am recreating every widget.
//
// If performance should become a concern this has to be revisited.
SetEditMacro(*macro);
HighlightCondition(idx); HighlightCondition(idx);
} }
@@ -311,21 +309,13 @@ void AdvSceneSwitcher::RemoveMacroCondition(int idx)
macro->Conditions().erase(macro->Conditions().begin() + idx); macro->Conditions().erase(macro->Conditions().begin() + idx);
macro->UpdateConditionIndices(); macro->UpdateConditionIndices();
if (macro->Conditions().size() == 0) {
ui->macroEditConditionHelp->setVisible(true);
}
if (idx == 0 && macro->Conditions().size() > 0) { if (idx == 0 && macro->Conditions().size() > 0) {
auto newRoot = macro->Conditions().at(0); auto newRoot = macro->Conditions().at(0);
newRoot->SetLogicType(LogicType::ROOT_NONE); newRoot->SetLogicType(LogicType::ROOT_NONE);
} }
// All entry pointers in existing edit widgets after the new entry will clearLayout(ui->macroEditConditionLayout, idx);
// be invalidated by adding the new entry so we have to recreate them PopulateMacroConditions(*macro, idx);
// and because I am lazy I am recreating every widget.
//
// If performance should become a concern this has to be revisited.
SetEditMacro(*macro);
} }
void AdvSceneSwitcher::on_conditionRemove_clicked() void AdvSceneSwitcher::on_conditionRemove_clicked()

View File

@@ -96,6 +96,7 @@ MacroConditionFilterEdit::MacroConditionFilterEdit(
{ {
_sources = new QComboBox(); _sources = new QComboBox();
_filters = new QComboBox(); _filters = new QComboBox();
_filters->setSizeAdjustPolicy(QComboBox::AdjustToContents);
_conditions = new QComboBox(); _conditions = new QComboBox();
_getSettings = new QPushButton(obs_module_text( _getSettings = new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.filter.getSettings")); "AdvSceneSwitcher.condition.filter.getSettings"));
@@ -159,6 +160,7 @@ void MacroConditionFilterEdit::SourceChanged(const QString &text)
} }
_filters->clear(); _filters->clear();
populateFilterSelection(_filters, _entryData->_source); populateFilterSelection(_filters, _entryData->_source);
_filters->adjustSize();
} }
void MacroConditionFilterEdit::FilterChanged(const QString &text) void MacroConditionFilterEdit::FilterChanged(const QString &text)

View File

@@ -125,6 +125,8 @@ MacroConditionMacroEdit::MacroConditionMacroEdit(
_currentCount = new QLabel(parent); _currentCount = new QLabel(parent);
_resetCount = new QPushButton(obs_module_text( _resetCount = new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.macro.count.reset")); "AdvSceneSwitcher.condition.macro.count.reset"));
_pausedWarning = new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.macro.pausedWarning"));
_count->setMaximum(10000000); _count->setMaximum(10000000);
populateTypeSelection(_types); populateTypeSelection(_types);
@@ -156,9 +158,18 @@ MacroConditionMacroEdit::MacroConditionMacroEdit(
mainLayout->addLayout(typesLayout); mainLayout->addLayout(typesLayout);
mainLayout->addLayout(_settingsLine1); mainLayout->addLayout(_settingsLine1);
mainLayout->addLayout(_settingsLine2); mainLayout->addLayout(_settingsLine2);
mainLayout->addWidget(_pausedWarning);
setLayout(mainLayout); setLayout(mainLayout);
_entryData = entryData; _entryData = entryData;
connect(&_countTimer, SIGNAL(timeout()), this, SLOT(UpdateCount()));
_countTimer.start(1000);
_pausedWarning->setVisible(false);
connect(&_pausedTimer, SIGNAL(timeout()), this, SLOT(UpdatePaused()));
_pausedTimer.start(1000);
UpdateEntryData(); UpdateEntryData();
_loading = false; _loading = false;
} }
@@ -237,7 +248,6 @@ void MacroConditionMacroEdit::UpdateEntryData()
_counterConditions->setCurrentIndex( _counterConditions->setCurrentIndex(
static_cast<int>(_entryData->_counterCondition)); static_cast<int>(_entryData->_counterCondition));
_count->setValue(_entryData->_count); _count->setValue(_entryData->_count);
ResetTimer();
} }
void MacroConditionMacroEdit::MacroChanged(const QString &text) void MacroConditionMacroEdit::MacroChanged(const QString &text)
@@ -248,7 +258,6 @@ void MacroConditionMacroEdit::MacroChanged(const QString &text)
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_macro.UpdateRef(text); _entryData->_macro.UpdateRef(text);
ResetTimer();
emit HeaderInfoChanged( emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc())); QString::fromStdString(_entryData->GetShortDesc()));
} }
@@ -303,7 +312,6 @@ void MacroConditionMacroEdit::ResetClicked()
} }
_entryData->_macro->ResetCount(); _entryData->_macro->ResetCount();
ResetTimer();
} }
void MacroConditionMacroEdit::UpdateCount() void MacroConditionMacroEdit::UpdateCount()
@@ -316,9 +324,9 @@ void MacroConditionMacroEdit::UpdateCount()
} }
} }
void MacroConditionMacroEdit::ResetTimer() void MacroConditionMacroEdit::UpdatePaused()
{ {
_timer.reset(new QTimer(this)); _pausedWarning->setVisible(_entryData && _entryData->_macro.get() &&
connect(_timer.get(), SIGNAL(timeout()), this, SLOT(UpdateCount())); _entryData->_macro->Paused());
_timer->start(1000); adjustSize();
} }

View File

@@ -0,0 +1,184 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-scene-visibility.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include <regex>
const std::string MacroConditionSceneVisibility::id = "scene_visibility";
bool MacroConditionSceneVisibility::_registered =
MacroConditionFactory::Register(
MacroConditionSceneVisibility::id,
{MacroConditionSceneVisibility::Create,
MacroConditionSceneVisibilityEdit::Create,
"AdvSceneSwitcher.condition.sceneVisibility"});
static std::map<SceneVisibilityCondition, std::string>
SceneVisibilityConditionTypes = {
{SceneVisibilityCondition::SHOWN,
"AdvSceneSwitcher.condition.sceneVisibility.type.shown"},
{SceneVisibilityCondition::HIDDEN,
"AdvSceneSwitcher.condition.sceneVisibility.type.hidden"},
};
struct VisibilityData {
std::string name;
bool visible;
bool result;
};
static bool visibilityEnum(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
{
VisibilityData *vInfo = reinterpret_cast<VisibilityData *>(ptr);
auto sourceName = obs_source_get_name(obs_sceneitem_get_source(item));
if (vInfo->name == sourceName &&
obs_sceneitem_visible(item) == vInfo->visible) {
vInfo->result = true;
return false;
}
if (obs_sceneitem_is_group(item)) {
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
obs_scene_enum_items(scene, visibilityEnum, ptr);
}
return true;
}
bool MacroConditionSceneVisibility::CheckCondition()
{
if (!_source) {
return false;
}
auto s = obs_weak_source_get_source(_scene.GetScene());
auto scene = obs_scene_from_source(s);
auto sourceName = GetWeakSourceName(_source);
VisibilityData data = {sourceName,
_condition == SceneVisibilityCondition::SHOWN,
false};
obs_scene_enum_items(scene, visibilityEnum, &data);
obs_source_release(s);
return data.result;
}
bool MacroConditionSceneVisibility::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
_scene.Save(obj);
obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str());
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
return true;
}
bool MacroConditionSceneVisibility::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_scene.Load(obj);
const char *sourceName = obs_data_get_string(obj, "source");
_source = GetWeakSourceByName(sourceName);
_condition = static_cast<SceneVisibilityCondition>(
obs_data_get_int(obj, "condition"));
return true;
}
std::string MacroConditionSceneVisibility::GetShortDesc()
{
if (_source) {
return _scene.ToString() + " - " + GetWeakSourceName(_source);
}
return "";
}
static inline void populateConditionSelection(QComboBox *list)
{
for (auto entry : SceneVisibilityConditionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroConditionSceneVisibilityEdit::MacroConditionSceneVisibilityEdit(
QWidget *parent,
std::shared_ptr<MacroConditionSceneVisibility> entryData)
: QWidget(parent)
{
_scenes = new SceneSelectionWidget(window(), false, true, true);
_sources = new QComboBox();
_sources->setSizeAdjustPolicy(QComboBox::AdjustToContents);
_conditions = new QComboBox();
populateConditionSelection(_conditions);
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(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{sources}}", _sources},
{"{{scenes}}", _scenes},
{"{{conditions}}", _conditions},
};
QHBoxLayout *mainLayout = new QHBoxLayout;
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.sceneVisibility.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionSceneVisibilityEdit::SourceChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_source = GetWeakSourceByQString(text);
}
void MacroConditionSceneVisibilityEdit::SceneChanged(const SceneSelection &s)
{
if (_loading || !_entryData) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_scene = s;
}
_sources->clear();
populateSceneItemSelection(_sources, _entryData->_scene);
_sources->adjustSize();
}
void MacroConditionSceneVisibilityEdit::ConditionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_condition = static_cast<SceneVisibilityCondition>(index);
}
void MacroConditionSceneVisibilityEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
_scenes->SetScene(_entryData->_scene);
populateSceneItemSelection(_sources, _entryData->_scene);
_sources->setCurrentText(
GetWeakSourceName(_entryData->_source).c_str());
}

View File

@@ -28,7 +28,18 @@ bool MacroConditionScene::CheckCondition()
switch (_type) { switch (_type) {
case SceneType::CURRENT: case SceneType::CURRENT:
return switcher->currentScene == _scene.GetScene(false); if (_waitForTransition) {
return switcher->currentScene == _scene.GetScene(false);
} else {
bool match = false;
auto current = obs_frontend_get_current_scene();
auto weak = obs_source_get_weak_source(current);
match = weak == _scene.GetScene(false);
obs_weak_source_release(weak);
obs_source_release(current);
return match;
}
case SceneType::PREVIOUS: case SceneType::PREVIOUS:
return switcher->previousScene == _scene.GetScene(false); return switcher->previousScene == _scene.GetScene(false);
case SceneType::CHANGED: case SceneType::CHANGED:
@@ -47,6 +58,7 @@ bool MacroConditionScene::Save(obs_data_t *obj)
MacroCondition::Save(obj); MacroCondition::Save(obj);
_scene.Save(obj); _scene.Save(obj);
obs_data_set_int(obj, "type", static_cast<int>(_type)); obs_data_set_int(obj, "type", static_cast<int>(_type));
obs_data_set_bool(obj, "waitForTransition", _waitForTransition);
return true; return true;
} }
@@ -55,6 +67,12 @@ bool MacroConditionScene::Load(obs_data_t *obj)
MacroCondition::Load(obj); MacroCondition::Load(obj);
_scene.Load(obj); _scene.Load(obj);
_type = static_cast<SceneType>(obs_data_get_int(obj, "type")); _type = static_cast<SceneType>(obs_data_get_int(obj, "type"));
if (!obs_data_has_user_value(obj, "waitForTransition")) {
_waitForTransition = true;
} else {
_waitForTransition =
obs_data_get_bool(obj, "waitForTransition");
}
return true; return true;
} }
@@ -76,11 +94,15 @@ MacroConditionSceneEdit::MacroConditionSceneEdit(
{ {
_scenes = new SceneSelectionWidget(window(), false, false, false); _scenes = new SceneSelectionWidget(window(), false, false, false);
_sceneType = new QComboBox(); _sceneType = new QComboBox();
_waitForTransition = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.scene.waitForTransition"));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)), QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &))); this, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_sceneType, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_sceneType, SIGNAL(currentIndexChanged(int)), this,
SLOT(TypeChanged(int))); SLOT(TypeChanged(int)));
QWidget::connect(_waitForTransition, SIGNAL(stateChanged(int)), this,
SLOT(WaitForTransitionChanged(int)));
populateTypeSelection(_sceneType); populateTypeSelection(_sceneType);
@@ -88,6 +110,7 @@ MacroConditionSceneEdit::MacroConditionSceneEdit(
std::unordered_map<std::string, QWidget *> widgetPlaceholders = { std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes}, {"{{scenes}}", _scenes},
{"{{sceneType}}", _sceneType}, {"{{sceneType}}", _sceneType},
{"{{waitForTransition}}", _waitForTransition},
}; };
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.scene.entry"), placeWidgets(obs_module_text("AdvSceneSwitcher.condition.scene.entry"),
mainLayout, widgetPlaceholders); mainLayout, widgetPlaceholders);
@@ -121,10 +144,21 @@ void MacroConditionSceneEdit::TypeChanged(int value)
SetWidgetVisibility(); SetWidgetVisibility();
} }
void MacroConditionSceneEdit::WaitForTransitionChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_waitForTransition = state;
}
void MacroConditionSceneEdit::SetWidgetVisibility() void MacroConditionSceneEdit::SetWidgetVisibility()
{ {
_scenes->setVisible(_entryData->_type == SceneType::CURRENT || _scenes->setVisible(_entryData->_type == SceneType::CURRENT ||
_entryData->_type == SceneType::PREVIOUS); _entryData->_type == SceneType::PREVIOUS);
_waitForTransition->setVisible(_entryData->_type == SceneType::CURRENT);
} }
void MacroConditionSceneEdit::UpdateEntryData() void MacroConditionSceneEdit::UpdateEntryData()
@@ -135,5 +169,6 @@ void MacroConditionSceneEdit::UpdateEntryData()
_scenes->SetScene(_entryData->_scene); _scenes->SetScene(_entryData->_scene);
_sceneType->setCurrentIndex(static_cast<int>(_entryData->_type)); _sceneType->setCurrentIndex(static_cast<int>(_entryData->_type));
_waitForTransition->setChecked(_entryData->_waitForTransition);
SetWidgetVisibility(); SetWidgetVisibility();
} }

View File

@@ -13,11 +13,21 @@ bool MacroConditionStream::_registered = MacroConditionFactory::Register(
static std::map<StreamState, std::string> streamStates = { static std::map<StreamState, std::string> streamStates = {
{StreamState::STOP, "AdvSceneSwitcher.condition.stream.state.stop"}, {StreamState::STOP, "AdvSceneSwitcher.condition.stream.state.stop"},
{StreamState::START, "AdvSceneSwitcher.condition.stream.state.start"}, {StreamState::START, "AdvSceneSwitcher.condition.stream.state.start"},
{StreamState::STARTING,
"AdvSceneSwitcher.condition.stream.state.starting"},
{StreamState::STOPPING,
"AdvSceneSwitcher.condition.stream.state.stopping"},
}; };
bool MacroConditionStream::CheckCondition() bool MacroConditionStream::CheckCondition()
{ {
bool stateMatch = false; bool stateMatch = false;
bool streamStarting = switcher->lastStreamStartingTime !=
_lastStreamStartingTime;
bool streamStopping = switcher->lastStreamStoppingTime !=
_lastStreamStoppingTime;
switch (_streamState) { switch (_streamState) {
case StreamState::STOP: case StreamState::STOP:
stateMatch = !obs_frontend_streaming_active(); stateMatch = !obs_frontend_streaming_active();
@@ -25,9 +35,22 @@ bool MacroConditionStream::CheckCondition()
case StreamState::START: case StreamState::START:
stateMatch = obs_frontend_streaming_active(); stateMatch = obs_frontend_streaming_active();
break; break;
case StreamState::STARTING:
stateMatch = streamStarting;
break;
case StreamState::STOPPING:
stateMatch = streamStopping;
break;
default: default:
break; break;
} }
if (streamStarting) {
_lastStreamStartingTime = switcher->lastStreamStartingTime;
}
if (streamStopping) {
_lastStreamStoppingTime = switcher->lastStreamStoppingTime;
}
return stateMatch; return stateMatch;
} }

View File

@@ -12,6 +12,9 @@ bool MacroConditionTimer::_registered = MacroConditionFactory::Register(
bool MacroConditionTimer::CheckCondition() bool MacroConditionTimer::CheckCondition()
{ {
if (_paused) {
return _remaining == 0.;
}
if (_duration.DurationReached()) { if (_duration.DurationReached()) {
if (!_oneshot) { if (!_oneshot) {
_duration.Reset(); _duration.Reset();
@@ -25,6 +28,16 @@ bool MacroConditionTimer::Save(obs_data_t *obj)
{ {
MacroCondition::Save(obj); MacroCondition::Save(obj);
_duration.Save(obj); _duration.Save(obj);
if (!_paused) {
_remaining = _duration.TimeRemaining();
}
if (_saveRemaining) {
obs_data_set_double(obj, "remaining", _remaining);
} else {
obs_data_set_double(obj, "remaining", _duration.seconds);
}
obs_data_set_bool(obj, "saveRemaining", _saveRemaining);
obs_data_set_bool(obj, "paused", _paused);
obs_data_set_bool(obj, "oneshot", _oneshot); obs_data_set_bool(obj, "oneshot", _oneshot);
return true; return true;
} }
@@ -33,20 +46,49 @@ bool MacroConditionTimer::Load(obs_data_t *obj)
{ {
MacroCondition::Load(obj); MacroCondition::Load(obj);
_duration.Load(obj); _duration.Load(obj);
_remaining = obs_data_get_double(obj, "remaining");
_paused = obs_data_get_bool(obj, "paused");
_saveRemaining = obs_data_get_bool(obj, "saveRemaining");
if (!obs_data_has_user_value(obj, "oneshot")) { if (!obs_data_has_user_value(obj, "oneshot")) {
_oneshot = false; _oneshot = false;
} else { } else {
_oneshot = obs_data_get_bool(obj, "oneshot"); _oneshot = obs_data_get_bool(obj, "oneshot");
} }
_duration.SetTimeRemaining(_remaining);
return true; return true;
} }
void MacroConditionTimer::Pause()
{
if (!_paused) {
_paused = true;
_remaining = _duration.TimeRemaining();
}
}
void MacroConditionTimer::Continue()
{
if (_paused) {
_paused = false;
_duration.SetTimeRemaining(_remaining);
}
}
void MacroConditionTimer::Reset()
{
_remaining = _duration.seconds;
_duration.Reset();
}
MacroConditionTimerEdit::MacroConditionTimerEdit( MacroConditionTimerEdit::MacroConditionTimerEdit(
QWidget *parent, std::shared_ptr<MacroConditionTimer> entryData) QWidget *parent, std::shared_ptr<MacroConditionTimer> entryData)
: QWidget(parent) : QWidget(parent)
{ {
_duration = new DurationSelection(); _duration = new DurationSelection();
_autoReset = new QCheckBox(); _autoReset = new QCheckBox();
_saveRemaining = new QCheckBox();
_pauseConinue = new QPushButton(
obs_module_text("AdvSceneSwitcher.condition.timer.pause"));
_reset = new QPushButton( _reset = new QPushButton(
obs_module_text("AdvSceneSwitcher.condition.timer.reset")); obs_module_text("AdvSceneSwitcher.condition.timer.reset"));
_remaining = new QLabel(); _remaining = new QLabel();
@@ -55,16 +97,22 @@ MacroConditionTimerEdit::MacroConditionTimerEdit(
SLOT(DurationChanged(double))); SLOT(DurationChanged(double)));
QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this, QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(DurationUnitChanged(DurationUnit))); SLOT(DurationUnitChanged(DurationUnit)));
QWidget::connect(_pauseConinue, SIGNAL(clicked()), this,
SLOT(PauseContinueClicked()));
QWidget::connect(_reset, SIGNAL(clicked()), this, SLOT(ResetClicked())); QWidget::connect(_reset, SIGNAL(clicked()), this, SLOT(ResetClicked()));
QWidget::connect(_autoReset, SIGNAL(stateChanged(int)), this, QWidget::connect(_autoReset, SIGNAL(stateChanged(int)), this,
SLOT(AutoResetChanged(int))); SLOT(AutoResetChanged(int)));
QWidget::connect(_saveRemaining, SIGNAL(stateChanged(int)), this,
SLOT(SaveRemainingChanged(int)));
auto line1Layout = new QHBoxLayout; auto line1Layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = { std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{duration}}", _duration}, {"{{duration}}", _duration},
{"{{autoReset}}", _autoReset}, {"{{autoReset}}", _autoReset},
{"{{remaining}}", _remaining}, {"{{remaining}}", _remaining},
{"{{pauseContinue}}", _pauseConinue},
{"{{reset}}", _reset}, {"{{reset}}", _reset},
{"{{saveRemaining}}", _saveRemaining},
}; };
placeWidgets( placeWidgets(
obs_module_text("AdvSceneSwitcher.condition.timer.entry.line1"), obs_module_text("AdvSceneSwitcher.condition.timer.entry.line1"),
@@ -112,6 +160,16 @@ void MacroConditionTimerEdit::DurationUnitChanged(DurationUnit unit)
_entryData->_duration.displayUnit = unit; _entryData->_duration.displayUnit = unit;
} }
void MacroConditionTimerEdit::SaveRemainingChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_saveRemaining = state;
}
void MacroConditionTimerEdit::AutoResetChanged(int state) void MacroConditionTimerEdit::AutoResetChanged(int state)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
@@ -122,6 +180,23 @@ void MacroConditionTimerEdit::AutoResetChanged(int state)
_entryData->_oneshot = !state; _entryData->_oneshot = !state;
} }
void MacroConditionTimerEdit::PauseContinueClicked()
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
if (_entryData->_paused) {
timer.start(1000);
_entryData->Continue();
} else {
_entryData->Pause();
timer.stop();
}
SetPauseContinueButtonLabel();
}
void MacroConditionTimerEdit::ResetClicked() void MacroConditionTimerEdit::ResetClicked()
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
@@ -129,7 +204,7 @@ void MacroConditionTimerEdit::ResetClicked()
} }
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.Reset(); _entryData->Reset();
} }
void MacroConditionTimerEdit::UpdateTimeRemaining() void MacroConditionTimerEdit::UpdateTimeRemaining()
@@ -138,8 +213,28 @@ void MacroConditionTimerEdit::UpdateTimeRemaining()
_remaining->setText("-"); _remaining->setText("-");
return; return;
} }
_remaining->setText(
QString::number(_entryData->_duration.TimeRemaining())); if (_entryData->_paused) {
_remaining->setText(QString::number(_entryData->_remaining));
} else {
_remaining->setText(
QString::number(_entryData->_duration.TimeRemaining()));
}
}
void MacroConditionTimerEdit::SetPauseContinueButtonLabel()
{
if (!_entryData) {
return;
}
if (_entryData->_paused) {
_pauseConinue->setText(obs_module_text(
"AdvSceneSwitcher.condition.timer.continue"));
} else {
_pauseConinue->setText(obs_module_text(
"AdvSceneSwitcher.condition.timer.pause"));
}
} }
void MacroConditionTimerEdit::UpdateEntryData() void MacroConditionTimerEdit::UpdateEntryData()
@@ -150,4 +245,6 @@ void MacroConditionTimerEdit::UpdateEntryData()
_duration->SetDuration(_entryData->_duration); _duration->SetDuration(_entryData->_duration);
_autoReset->setChecked(!_entryData->_oneshot); _autoReset->setChecked(!_entryData->_oneshot);
_saveRemaining->setChecked(_entryData->_saveRemaining);
SetPauseContinueButtonLabel();
} }

View File

@@ -35,7 +35,12 @@ static std::map<VideoCondition, std::string> conditionTypes = {
cv::CascadeClassifier initObjectCascade(std::string &path) cv::CascadeClassifier initObjectCascade(std::string &path)
{ {
cv::CascadeClassifier cascade; cv::CascadeClassifier cascade;
cascade.load(path); try {
cascade.load(path);
} catch (...) {
blog(LOG_WARNING, "failed to load model data \"%s\"",
path.c_str());
}
return cascade; return cascade;
} }
@@ -94,7 +99,10 @@ bool MacroConditionVideo::Save(obs_data_t *obj)
GetWeakSourceName(_videoSource).c_str()); GetWeakSourceName(_videoSource).c_str());
obs_data_set_int(obj, "condition", static_cast<int>(_condition)); obs_data_set_int(obj, "condition", static_cast<int>(_condition));
obs_data_set_string(obj, "filePath", _file.c_str()); obs_data_set_string(obj, "filePath", _file.c_str());
obs_data_set_bool(obj, "usePatternForChangedCheck",
_usePatternForChangedCheck);
obs_data_set_double(obj, "threshold", _patternThreshold); obs_data_set_double(obj, "threshold", _patternThreshold);
obs_data_set_bool(obj, "useAlphaAsMask", _useAlphaAsMask);
obs_data_set_string(obj, "modelDataPath", _modelDataPath.c_str()); obs_data_set_string(obj, "modelDataPath", _modelDataPath.c_str());
obs_data_set_double(obj, "scaleFactor", _scaleFactor); obs_data_set_double(obj, "scaleFactor", _scaleFactor);
obs_data_set_int(obj, "minNeighbors", _minNeighbors); obs_data_set_int(obj, "minNeighbors", _minNeighbors);
@@ -126,14 +134,17 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
_condition = _condition =
static_cast<VideoCondition>(obs_data_get_int(obj, "condition")); static_cast<VideoCondition>(obs_data_get_int(obj, "condition"));
_file = obs_data_get_string(obj, "filePath"); _file = obs_data_get_string(obj, "filePath");
_usePatternForChangedCheck =
obs_data_get_bool(obj, "usePatternForChangedCheck");
_patternThreshold = obs_data_get_double(obj, "threshold"); _patternThreshold = obs_data_get_double(obj, "threshold");
_useAlphaAsMask = obs_data_get_bool(obj, "useAlphaAsMask");
_modelDataPath = obs_data_get_string(obj, "modelDataPath"); _modelDataPath = obs_data_get_string(obj, "modelDataPath");
_scaleFactor = obs_data_get_double(obj, "scaleFactor"); _scaleFactor = obs_data_get_double(obj, "scaleFactor");
if (!isScaleFactorValid(_scaleFactor)) { if (!isScaleFactorValid(_scaleFactor)) {
_scaleFactor = 1.1; _scaleFactor = 1.1;
} }
_minNeighbors = obs_data_get_int(obj, "minNeighbors"); _minNeighbors = obs_data_get_int(obj, "minNeighbors");
if (!isMinNeighborsValid(_scaleFactor)) { if (!isMinNeighborsValid(_minNeighbors)) {
_minNeighbors = minMinNeighbors; _minNeighbors = minMinNeighbors;
} }
_minSizeX = obs_data_get_int(obj, "minSizeX"); _minSizeX = obs_data_get_int(obj, "minSizeX");
@@ -169,32 +180,7 @@ void MacroConditionVideo::GetScreenshot()
obs_source_release(source); obs_source_release(source);
} }
bool MacroConditionVideo::LoadImageFromFile() // Assumption is that QImage uses Format_RGBA8888.
{
if (!_matchImage.load(QString::fromStdString(_file))) {
blog(LOG_WARNING, "Cannot load image data from file '%s'",
_file.c_str());
return false;
}
_matchImage =
_matchImage.convertToFormat(QImage::Format::Format_RGBX8888);
return true;
}
bool MacroConditionVideo::LoadModelData(std::string &path)
{
_modelDataPath = path;
try {
_objectCascade = initObjectCascade(path);
} catch (...) {
blog(LOG_WARNING, "failed to load model data \"%s\"",
path.c_str());
return false;
}
return !_objectCascade.empty();
}
// Assumption is that QImage uses Format_RGBX8888.
// Conversion from: https://github.com/dbzhang800/QtOpenCV // Conversion from: https://github.com/dbzhang800/QtOpenCV
cv::Mat QImageToMat(const QImage &img) cv::Mat QImageToMat(const QImage &img)
{ {
@@ -211,34 +197,109 @@ QImage MatToQImage(const cv::Mat &mat)
return QImage(); return QImage();
} }
return QImage(mat.data, mat.cols, mat.rows, mat.step, return QImage(mat.data, mat.cols, mat.rows, mat.step,
QImage::Format::Format_RGBX8888); QImage::Format::Format_RGBA8888);
} }
void matchPattern(QImage &img, QImage &pattern, double threshold, PatternMatchData createPatternData(QImage &pattern)
cv::Mat &result)
{ {
if (img.isNull() || pattern.isNull()) { PatternMatchData data;
if (pattern.isNull()) {
return data;
}
data.rgbaPattern = QImageToMat(pattern);
std::vector<cv::Mat1b> rgbaChannelsPattern;
cv::split(data.rgbaPattern, rgbaChannelsPattern);
std::vector<cv::Mat1b> rgbChanlesPattern(
rgbaChannelsPattern.begin(), rgbaChannelsPattern.begin() + 3);
cv::merge(rgbChanlesPattern, data.rgbPattern);
cv::threshold(rgbaChannelsPattern[3], data.mask, 0, 255,
cv::THRESH_BINARY);
return data;
}
bool MacroConditionVideo::LoadImageFromFile()
{
if (!_matchImage.load(QString::fromStdString(_file))) {
blog(LOG_WARNING, "Cannot load image data from file '%s'",
_file.c_str());
return false;
}
_matchImage =
_matchImage.convertToFormat(QImage::Format::Format_RGBA8888);
_patternData = createPatternData(_matchImage);
return true;
}
bool MacroConditionVideo::LoadModelData(std::string &path)
{
_modelDataPath = path;
_objectCascade = initObjectCascade(path);
return !_objectCascade.empty();
}
void matchPattern(QImage &img, PatternMatchData &patternData, double threshold,
cv::Mat &result, bool useAlphaAsMask = true)
{
if (img.isNull() || patternData.rgbaPattern.empty()) {
return; return;
} }
if (img.height() < pattern.height() || img.width() < pattern.width()) { if (img.height() < patternData.rgbaPattern.rows ||
img.width() < patternData.rgbaPattern.cols) {
return; return;
} }
auto i = QImageToMat(img); auto i = QImageToMat(img);
auto p = QImageToMat(pattern);
cv::matchTemplate(i, p, result, cv::TM_CCOEFF_NORMED); if (useAlphaAsMask) {
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO); std::vector<cv::Mat1b> rgbaChannelsImage;
cv::split(i, rgbaChannelsImage);
std::vector<cv::Mat1b> rgbChanlesImage(
rgbaChannelsImage.begin(),
rgbaChannelsImage.begin() + 3);
cv::Mat3b rgbImage;
cv::merge(rgbChanlesImage, rgbImage);
cv::matchTemplate(rgbImage, patternData.rgbPattern, result,
cv::TM_CCORR_NORMED, patternData.mask);
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO);
} else {
cv::matchTemplate(i, patternData.rgbaPattern, result,
cv::TM_CCOEFF_NORMED);
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO);
}
}
void matchPattern(QImage &img, QImage &pattern, double threshold,
cv::Mat &result, bool useAlphaAsMask)
{
auto data = createPatternData(pattern);
matchPattern(img, data, threshold, result, useAlphaAsMask);
} }
bool MacroConditionVideo::ScreenshotContainsPattern() bool MacroConditionVideo::ScreenshotContainsPattern()
{ {
cv::Mat result; cv::Mat result;
matchPattern(_screenshotData->image, _matchImage, _patternThreshold, matchPattern(_screenshotData->image, _patternData, _patternThreshold,
result); result, _useAlphaAsMask);
return countNonZero(result) > 0; return countNonZero(result) > 0;
} }
bool MacroConditionVideo::OutputChanged()
{
if (_usePatternForChangedCheck) {
cv::Mat result;
_patternData = createPatternData(_matchImage);
matchPattern(_screenshotData->image, _patternData,
_patternThreshold, result, _useAlphaAsMask);
return countNonZero(result) == 0;
}
return _screenshotData->image != _matchImage;
}
std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade, std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
double scaleFactor, int minNeighbors, double scaleFactor, int minNeighbors,
cv::Size minSize, cv::Size maxSize) cv::Size minSize, cv::Size maxSize)
@@ -274,9 +335,9 @@ bool MacroConditionVideo::Compare()
case VideoCondition::DIFFER: case VideoCondition::DIFFER:
return _screenshotData->image != _matchImage; return _screenshotData->image != _matchImage;
case VideoCondition::HAS_CHANGED: case VideoCondition::HAS_CHANGED:
return _screenshotData->image != _matchImage; return OutputChanged();
case VideoCondition::HAS_NOT_CHANGED: case VideoCondition::HAS_NOT_CHANGED:
return _screenshotData->image == _matchImage; return !OutputChanged();
case VideoCondition::NO_IMAGE: case VideoCondition::NO_IMAGE:
return _screenshotData->image.isNull(); return _screenshotData->image.isNull();
case VideoCondition::PATTERN: case VideoCondition::PATTERN:
@@ -353,12 +414,18 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
_imagePath = new FileSelection(); _imagePath = new FileSelection();
_imagePath->Button()->disconnect(); _imagePath->Button()->disconnect();
_usePatternForChangedCheck = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck"));
_usePatternForChangedCheck->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip"));
_patternThreshold = new ThresholdSlider( _patternThreshold = new ThresholdSlider(
0., 1., 0., 1.,
obs_module_text( obs_module_text(
"AdvSceneSwitcher.condition.video.patternThreshold"), "AdvSceneSwitcher.condition.video.patternThreshold"),
obs_module_text( obs_module_text(
"AdvSceneSwitcher.condition.video.patternThresholdDescription")); "AdvSceneSwitcher.condition.video.patternThresholdDescription"));
_useAlphaAsMask = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask"));
_modelDataPath = new FileSelection(); _modelDataPath = new FileSelection();
_objectScaleThreshold = new ThresholdSlider( _objectScaleThreshold = new ThresholdSlider(
@@ -370,6 +437,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
_minNeighbors = new QSpinBox(); _minNeighbors = new QSpinBox();
_minNeighbors->setMinimum(minMinNeighbors); _minNeighbors->setMinimum(minMinNeighbors);
_minNeighbors->setMaximum(maxMinNeighbors); _minNeighbors->setMaximum(maxMinNeighbors);
_minNeighborsDescription = new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.video.minNeighborDescription"));
_minSizeX = new QSpinBox(); _minSizeX = new QSpinBox();
_minSizeY = new QSpinBox(); _minSizeY = new QSpinBox();
_minSizeX->setMaximum(1024); _minSizeX->setMaximum(1024);
@@ -396,8 +465,12 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
SLOT(ImagePathChanged(const QString &))); SLOT(ImagePathChanged(const QString &)));
QWidget::connect(_imagePath->Button(), SIGNAL(clicked()), this, QWidget::connect(_imagePath->Button(), SIGNAL(clicked()), this,
SLOT(ImageBrowseButtonClicked())); SLOT(ImageBrowseButtonClicked()));
QWidget::connect(_usePatternForChangedCheck, SIGNAL(stateChanged(int)),
this, SLOT(UsePatternForChangedCheckChanged(int)));
QWidget::connect(_patternThreshold, SIGNAL(DoubleValueChanged(double)), QWidget::connect(_patternThreshold, SIGNAL(DoubleValueChanged(double)),
this, SLOT(PatternThresholdChanged(double))); this, SLOT(PatternThresholdChanged(double)));
QWidget::connect(_useAlphaAsMask, SIGNAL(stateChanged(int)), this,
SLOT(UseAlphaAsMaskChanged(int)));
QWidget::connect(_objectScaleThreshold, QWidget::connect(_objectScaleThreshold,
SIGNAL(DoubleValueChanged(double)), this, SIGNAL(DoubleValueChanged(double)), this,
SLOT(ObjectScaleThresholdChanged(double))); SLOT(ObjectScaleThresholdChanged(double)));
@@ -472,10 +545,13 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
showMatchLayout->addStretch(); showMatchLayout->addStretch();
QVBoxLayout *mainLayout = new QVBoxLayout; QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLine1Layout); mainLayout->addLayout(entryLine1Layout);
mainLayout->addWidget(_usePatternForChangedCheck);
mainLayout->addWidget(_patternThreshold); mainLayout->addWidget(_patternThreshold);
mainLayout->addWidget(_useAlphaAsMask);
mainLayout->addLayout(_modelPathLayout); mainLayout->addLayout(_modelPathLayout);
mainLayout->addWidget(_objectScaleThreshold); mainLayout->addWidget(_objectScaleThreshold);
mainLayout->addLayout(_neighborsControlLayout); mainLayout->addLayout(_neighborsControlLayout);
mainLayout->addWidget(_minNeighborsDescription);
mainLayout->addLayout(_minSizeControlLayout); mainLayout->addLayout(_minSizeControlLayout);
mainLayout->addLayout(_maxSizeControlLayout); mainLayout->addLayout(_maxSizeControlLayout);
mainLayout->addLayout(showMatchLayout); mainLayout->addLayout(showMatchLayout);
@@ -526,11 +602,6 @@ void MacroConditionVideoEdit::SourceChanged(const QString &text)
QString::fromStdString(_entryData->GetShortDesc())); QString::fromStdString(_entryData->GetShortDesc()));
} }
bool needsPatternThreshold(VideoCondition cond)
{
return cond == VideoCondition::PATTERN;
}
void MacroConditionVideoEdit::ConditionChanged(int cond) void MacroConditionVideoEdit::ConditionChanged(int cond)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
@@ -635,6 +706,18 @@ void MacroConditionVideoEdit::ImageBrowseButtonClicked()
ImagePathChanged(path); ImagePathChanged(path);
} }
void MacroConditionVideoEdit::UsePatternForChangedCheckChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_usePatternForChangedCheck = value;
_patternThreshold->setVisible(value);
adjustSize();
}
void MacroConditionVideoEdit::PatternThresholdChanged(double value) void MacroConditionVideoEdit::PatternThresholdChanged(double value)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
@@ -645,6 +728,17 @@ void MacroConditionVideoEdit::PatternThresholdChanged(double value)
_entryData->_patternThreshold = value; _entryData->_patternThreshold = value;
} }
void MacroConditionVideoEdit::UseAlphaAsMaskChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_useAlphaAsMask = value;
_entryData->LoadImageFromFile();
}
void MacroConditionVideoEdit::ObjectScaleThresholdChanged(double value) void MacroConditionVideoEdit::ObjectScaleThresholdChanged(double value)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
@@ -735,7 +829,7 @@ QImage markPatterns(cv::Mat &matchResult, QImage &image, QImage &pattern)
rectangle(matchImg, {col, row}, rectangle(matchImg, {col, row},
cv::Point(col + pattern.width(), cv::Point(col + pattern.width(),
row + pattern.height()), row + pattern.height()),
cv::Scalar(255, 0, 0, 0), 2, 8, 0); cv::Scalar(255, 0, 0, 255), 2, 8, 0);
} }
} }
} }
@@ -749,7 +843,7 @@ QImage markObjects(QImage &image, std::vector<cv::Rect> &objects)
rectangle(frame, cv::Point(objects[i].x, objects[i].y), rectangle(frame, cv::Point(objects[i].x, objects[i].y),
cv::Point(objects[i].x + objects[i].width, cv::Point(objects[i].x + objects[i].width,
objects[i].y + objects[i].height), objects[i].y + objects[i].height),
cv::Scalar(255, 0, 0, 0), 2, 8, 0); cv::Scalar(255, 0, 0, 255), 2, 8, 0);
} }
return MatToQImage(frame); return MatToQImage(frame);
} }
@@ -769,11 +863,12 @@ void MacroConditionVideoEdit::ShowMatchClicked()
} }
QImage markedIamge; QImage markedIamge;
QImage pattern = _entryData->GetMatchImage();
if (_entryData->_condition == VideoCondition::PATTERN) { if (_entryData->_condition == VideoCondition::PATTERN) {
cv::Mat result; cv::Mat result;
QImage pattern = _entryData->GetMatchImage();
matchPattern(screenshot->image, pattern, matchPattern(screenshot->image, pattern,
_entryData->_patternThreshold, result); _entryData->_patternThreshold, result,
_entryData->_useAlphaAsMask);
if (countNonZero(result) == 0) { if (countNonZero(result) == 0) {
DisplayMessage(obs_module_text( DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchFail")); "AdvSceneSwitcher.condition.video.patternMatchFail"));
@@ -810,9 +905,13 @@ void MacroConditionVideoEdit::ModelPathChanged(const QString &text)
return; return;
} }
std::lock_guard<std::mutex> lock(switcher->m); bool dataLoaded = false;
std::string path = text.toStdString(); {
if (!_entryData->LoadModelData(path)) { std::lock_guard<std::mutex> lock(switcher->m);
std::string path = text.toStdString();
dataLoaded = _entryData->LoadModelData(path);
}
if (!dataLoaded) {
DisplayMessage(obs_module_text( DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.modelLoadFail")); "AdvSceneSwitcher.condition.video.modelLoadFail"));
} }
@@ -835,26 +934,34 @@ bool needsThrottleControls(VideoCondition cond)
cond == VideoCondition::OBJECT; cond == VideoCondition::OBJECT;
} }
void setLayoutVisible(QLayout *layout, bool visible) bool needsThreshold(VideoCondition cond)
{ {
for (int i = 0; i < layout->count(); ++i) { return cond == VideoCondition::PATTERN ||
QWidget *widget = layout->itemAt(i)->widget(); cond == VideoCondition::HAS_CHANGED ||
if (widget != NULL) { cond == VideoCondition::HAS_NOT_CHANGED;
widget->setVisible(visible); }
}
} bool patternControlIsOptional(VideoCondition cond)
{
return cond == VideoCondition::HAS_CHANGED ||
cond == VideoCondition::HAS_NOT_CHANGED;
} }
void MacroConditionVideoEdit::SetWidgetVisibility() void MacroConditionVideoEdit::SetWidgetVisibility()
{ {
_imagePath->setVisible(requiresFileInput(_entryData->_condition)); _imagePath->setVisible(requiresFileInput(_entryData->_condition));
_patternThreshold->setVisible( _usePatternForChangedCheck->setVisible(
needsPatternThreshold(_entryData->_condition)); patternControlIsOptional(_entryData->_condition));
_patternThreshold->setVisible(needsThreshold(_entryData->_condition));
_useAlphaAsMask->setVisible(_entryData->_condition ==
VideoCondition::PATTERN);
_showMatch->setVisible(needsShowMatch(_entryData->_condition)); _showMatch->setVisible(needsShowMatch(_entryData->_condition));
_objectScaleThreshold->setVisible( _objectScaleThreshold->setVisible(
needsObjectControls(_entryData->_condition)); needsObjectControls(_entryData->_condition));
setLayoutVisible(_neighborsControlLayout, setLayoutVisible(_neighborsControlLayout,
needsObjectControls(_entryData->_condition)); needsObjectControls(_entryData->_condition));
_minNeighborsDescription->setVisible(
needsObjectControls(_entryData->_condition));
setLayoutVisible(_minSizeControlLayout, setLayoutVisible(_minSizeControlLayout,
needsObjectControls(_entryData->_condition)); needsObjectControls(_entryData->_condition));
setLayoutVisible(_maxSizeControlLayout, setLayoutVisible(_maxSizeControlLayout,
@@ -863,6 +970,13 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
needsObjectControls(_entryData->_condition)); needsObjectControls(_entryData->_condition));
setLayoutVisible(_throttleControlLayout, setLayoutVisible(_throttleControlLayout,
needsThrottleControls(_entryData->_condition)); needsThrottleControls(_entryData->_condition));
if (_entryData->_condition == VideoCondition::HAS_CHANGED ||
_entryData->_condition == VideoCondition::HAS_NOT_CHANGED) {
_patternThreshold->setVisible(
_entryData->_usePatternForChangedCheck);
}
adjustSize(); adjustSize();
} }
@@ -876,7 +990,10 @@ void MacroConditionVideoEdit::UpdateEntryData()
GetWeakSourceName(_entryData->_videoSource).c_str()); GetWeakSourceName(_entryData->_videoSource).c_str());
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition)); _condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
_imagePath->SetPath(QString::fromStdString(_entryData->_file)); _imagePath->SetPath(QString::fromStdString(_entryData->_file));
_usePatternForChangedCheck->setChecked(
_entryData->_usePatternForChangedCheck);
_patternThreshold->SetDoubleValue(_entryData->_patternThreshold); _patternThreshold->SetDoubleValue(_entryData->_patternThreshold);
_useAlphaAsMask->setChecked(_entryData->_useAlphaAsMask);
_modelDataPath->SetPath(_entryData->GetModelDataPath().c_str()); _modelDataPath->SetPath(_entryData->GetModelDataPath().c_str());
_objectScaleThreshold->SetDoubleValue(_entryData->_scaleFactor); _objectScaleThreshold->SetDoubleValue(_entryData->_scaleFactor);
_minNeighbors->setValue(_entryData->_minNeighbors); _minNeighbors->setValue(_entryData->_minNeighbors);

View File

@@ -145,3 +145,8 @@ void MacroSegmentEdit::SetFocusPolicyOfWidgets()
w->installEventFilter(new MouseWheelWidgetAdjustmentGuard(w)); w->installEventFilter(new MouseWheelWidgetAdjustmentGuard(w));
} }
} }
void MacroSegmentEdit::SetCollapsed(bool collapsed)
{
_section->SetCollapsed(collapsed);
}

View File

@@ -16,7 +16,7 @@ MacroSelection::MacroSelection(QWidget *parent) : QComboBox(parent)
firstItem->setEnabled(false); firstItem->setEnabled(false);
for (auto &m : switcher->macros) { for (auto &m : switcher->macros) {
addItem(QString::fromStdString(m.Name())); addItem(QString::fromStdString(m->Name()));
} }
QWidget::connect(parent, SIGNAL(MacroAdded(const QString &)), this, QWidget::connect(parent, SIGNAL(MacroAdded(const QString &)), this,

View File

@@ -47,7 +47,7 @@ bool AdvSceneSwitcher::addNewMacro(std::string &name)
{ {
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
switcher->macros.emplace_back(name); switcher->macros.emplace_back(std::make_shared<Macro>(name));
} }
return true; return true;
} }
@@ -84,8 +84,11 @@ void AdvSceneSwitcher::on_macroRemove_clicked()
switcher->abortMacroWait = true; switcher->abortMacroWait = true;
switcher->macroWaitCv.notify_one(); switcher->macroWaitCv.notify_one();
int idx = ui->macros->currentRow(); int idx = ui->macros->currentRow();
QString::fromStdString(switcher->macros[idx].Name()); QString::fromStdString(switcher->macros[idx]->Name());
switcher->macros.erase(switcher->macros.begin() + idx); switcher->macros.erase(switcher->macros.begin() + idx);
for (auto &m : switcher->macros) {
m->ResolveMacroRef();
}
} }
delete item; delete item;
@@ -108,7 +111,7 @@ void AdvSceneSwitcher::on_macroUp_clicked()
switcher->macros.begin() + index - 1); switcher->macros.begin() + index - 1);
for (auto &m : switcher->macros) { for (auto &m : switcher->macros) {
m.ResolveMacroRef(); m->ResolveMacroRef();
} }
} }
} }
@@ -125,7 +128,7 @@ void AdvSceneSwitcher::on_macroDown_clicked()
switcher->macros.begin() + index + 1); switcher->macros.begin() + index + 1);
for (auto &m : switcher->macros) { for (auto &m : switcher->macros) {
m.ResolveMacroRef(); m->ResolveMacroRef();
} }
} }
} }
@@ -170,42 +173,44 @@ void AdvSceneSwitcher::on_macroName_editingFinished()
emit MacroRenamed(oldName, newName); emit MacroRenamed(oldName, newName);
} }
void AdvSceneSwitcher::PopulateMacroActions(Macro &m, uint32_t afterIdx)
{
auto &actions = m.Actions();
for (; afterIdx < actions.size(); afterIdx++) {
auto newEntry = new MacroActionEdit(this, &actions[afterIdx],
actions[afterIdx]->GetId());
ConnectControlSignals(newEntry);
ui->macroEditActionLayout->addWidget(newEntry);
ui->macroEditActionHelp->setVisible(false);
}
ui->macroEditActionHelp->setVisible(actions.size() == 0);
}
void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, uint32_t afterIdx)
{
bool root = afterIdx == 0;
auto &conditions = m.Conditions();
for (; afterIdx < conditions.size(); afterIdx++) {
auto newEntry = new MacroConditionEdit(
this, &conditions[afterIdx],
conditions[afterIdx]->GetId(), root);
ConnectControlSignals(newEntry);
ui->macroEditConditionLayout->addWidget(newEntry);
ui->macroEditConditionHelp->setVisible(false);
root = false;
}
ui->macroEditConditionHelp->setVisible(conditions.size() == 0);
}
void AdvSceneSwitcher::SetEditMacro(Macro &m) void AdvSceneSwitcher::SetEditMacro(Macro &m)
{ {
ui->macroName->setText(m.Name().c_str()); ui->macroName->setText(m.Name().c_str());
clearLayout(ui->macroEditConditionLayout); clearLayout(ui->macroEditConditionLayout);
clearLayout(ui->macroEditActionLayout); clearLayout(ui->macroEditActionLayout);
bool root = true; PopulateMacroConditions(m);
for (auto &c : m.Conditions()) { PopulateMacroActions(m);
auto newEntry =
new MacroConditionEdit(this, &c, c->GetId(), root);
ConnectControlSignals(newEntry);
ui->macroEditConditionLayout->addWidget(newEntry);
ui->macroEditConditionHelp->setVisible(false);
root = false;
}
for (auto &a : m.Actions()) {
auto newEntry = new MacroActionEdit(this, &a, a->GetId());
ConnectControlSignals(newEntry);
ui->macroEditActionLayout->addWidget(newEntry);
ui->macroEditActionHelp->setVisible(false);
}
ui->macroEdit->setDisabled(false); ui->macroEdit->setDisabled(false);
if (m.Conditions().size() == 0) {
ui->macroEditConditionHelp->setVisible(true);
} else {
ui->macroEditConditionHelp->setVisible(false);
}
if (m.Actions().size() == 0) {
ui->macroEditActionHelp->setVisible(true);
} else {
ui->macroEditActionHelp->setVisible(false);
}
} }
void AdvSceneSwitcher::HighlightAction(int idx) void AdvSceneSwitcher::HighlightAction(int idx)
@@ -310,12 +315,12 @@ void AdvSceneSwitcher::on_macros_itemChanged(QListWidgetItem *item)
void AdvSceneSwitcher::setupMacroTab() void AdvSceneSwitcher::setupMacroTab()
{ {
for (auto &m : switcher->macros) { for (auto &m : switcher->macros) {
QString text = QString::fromStdString(m.Name()); QString text = QString::fromStdString(m->Name());
QListWidgetItem *item = new QListWidgetItem(text, ui->macros); QListWidgetItem *item = new QListWidgetItem(text, ui->macros);
item->setData(Qt::UserRole, text); item->setData(Qt::UserRole, text);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
if (m.Paused()) { if (m->Paused()) {
item->setCheckState(Qt::Unchecked); item->setCheckState(Qt::Unchecked);
} else { } else {
item->setCheckState(Qt::Checked); item->setCheckState(Qt::Checked);
@@ -388,8 +393,8 @@ void AdvSceneSwitcher::CopyMacro()
return; return;
} }
switcher->macros.back().Load(data); switcher->macros.back()->Load(data);
switcher->macros.back().SetName(name); switcher->macros.back()->SetName(name);
obs_data_release(data); obs_data_release(data);
QString text = QString::fromStdString(name); QString text = QString::fromStdString(name);
@@ -400,17 +405,25 @@ void AdvSceneSwitcher::CopyMacro()
ui->macros->setCurrentItem(item); ui->macros->setCurrentItem(item);
} }
void setCollapsedStateOfSegmentsIn(QLayout *layout, bool collapse)
{
QLayoutItem *item = nullptr;
for (int i = 0; i < layout->count(); i++) {
item = layout->itemAt(i);
auto segment = dynamic_cast<MacroSegmentEdit *>(item->widget());
if (segment) {
segment->SetCollapsed(collapse);
}
}
}
void AdvSceneSwitcher::ExpandAllActions() void AdvSceneSwitcher::ExpandAllActions()
{ {
auto m = getSelectedMacro(); auto m = getSelectedMacro();
if (!m) { if (!m) {
return; return;
} }
setCollapsedStateOfSegmentsIn(ui->macroEditActionLayout, false);
for (auto &a : m->Actions()) {
a->SetCollapsed(false);
}
SetEditMacro(*m);
} }
void AdvSceneSwitcher::ExpandAllConditions() void AdvSceneSwitcher::ExpandAllConditions()
@@ -419,11 +432,7 @@ void AdvSceneSwitcher::ExpandAllConditions()
if (!m) { if (!m) {
return; return;
} }
setCollapsedStateOfSegmentsIn(ui->macroEditConditionLayout, false);
for (auto &c : m->Conditions()) {
c->SetCollapsed(false);
}
SetEditMacro(*m);
} }
void AdvSceneSwitcher::CollapseAllActions() void AdvSceneSwitcher::CollapseAllActions()
@@ -432,11 +441,7 @@ void AdvSceneSwitcher::CollapseAllActions()
if (!m) { if (!m) {
return; return;
} }
setCollapsedStateOfSegmentsIn(ui->macroEditActionLayout, true);
for (auto &a : m->Actions()) {
a->SetCollapsed(true);
}
SetEditMacro(*m);
} }
void AdvSceneSwitcher::CollapseAllConditions() void AdvSceneSwitcher::CollapseAllConditions()
@@ -445,9 +450,5 @@ void AdvSceneSwitcher::CollapseAllConditions()
if (!m) { if (!m) {
return; return;
} }
setCollapsedStateOfSegmentsIn(ui->macroEditConditionLayout, true);
for (auto &c : m->Conditions()) {
c->SetCollapsed(true);
}
SetEditMacro(*m);
} }

View File

@@ -36,6 +36,11 @@ bool Macro::CeckMatch()
{ {
_matched = false; _matched = false;
for (auto &c : _conditions) { for (auto &c : _conditions) {
if (_paused) {
vblog(LOG_INFO, "Macro %s is paused", _name.c_str());
return false;
}
auto startTime = std::chrono::high_resolution_clock::now(); auto startTime = std::chrono::high_resolution_clock::now();
bool cond = c->CheckCondition(); bool cond = c->CheckCondition();
auto endTime = std::chrono::high_resolution_clock::now(); auto endTime = std::chrono::high_resolution_clock::now();
@@ -89,15 +94,11 @@ bool Macro::CeckMatch()
vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched); vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched);
// Condition checks shall still be run even if macro is paused. // TODO: Move back to PerformAction() once new scene collection frontend
// Otherwise conditions might behave in unexpected ways when resuming. // events are available - see:
// // https://github.com/obsproject/obs-studio/commit/feda1aaa283e8a99f6ba1159cfe6b9c1f2934a61
// For example, audio could immediately match after unpause, when it if (_matched && _count != std::numeric_limits<int>::max()) {
// matched before it was paused due to timers not being updated. _count++;
if (_paused) {
vblog(LOG_INFO, "Macro %s is paused", _name.c_str());
_matched = false;
return false;
} }
return _matched; return _matched;
@@ -113,9 +114,6 @@ bool Macro::PerformAction()
return false; return false;
} }
} }
if (ret && _count != std::numeric_limits<int>::max()) {
_count++;
}
return ret; return ret;
} }
@@ -125,6 +123,21 @@ void Macro::SetName(const std::string &name)
SetHotkeysDesc(); SetHotkeysDesc();
} }
void Macro::ResetTimers()
{
for (auto &c : _conditions) {
c->ResetDuration();
}
}
void Macro::SetPaused(bool pause)
{
if (_paused && !pause) {
ResetTimers();
}
_paused = pause;
}
void Macro::UpdateActionIndices() void Macro::UpdateActionIndices()
{ {
int idx = 0; int idx = 0;
@@ -290,13 +303,18 @@ void Macro::ResolveMacroRef()
MacroRefCondition *ref = MacroRefCondition *ref =
dynamic_cast<MacroRefCondition *>(c.get()); dynamic_cast<MacroRefCondition *>(c.get());
if (ref) { if (ref) {
ref->_macro.UpdateRef(); ref->ResolveMacroRef();
} }
} }
for (auto &a : _actions) { for (auto &a : _actions) {
MacroRefAction *ref = dynamic_cast<MacroRefAction *>(a.get()); MacroRefAction *ref = dynamic_cast<MacroRefAction *>(a.get());
if (ref) { if (ref) {
ref->_macro.UpdateRef(); ref->ResolveMacroRef();
}
MultiMacroRefAction *ref2 =
dynamic_cast<MultiMacroRefAction *>(a.get());
if (ref2) {
ref2->ResolveMacroRef();
} }
} }
} }
@@ -445,7 +463,7 @@ void SwitcherData::saveMacros(obs_data_t *obj)
for (auto &m : macros) { for (auto &m : macros) {
obs_data_t *array_obj = obs_data_create(); obs_data_t *array_obj = obs_data_create();
m.Save(array_obj); m->Save(array_obj);
obs_data_array_push_back(macroArray, array_obj); obs_data_array_push_back(macroArray, array_obj);
obs_data_release(array_obj); obs_data_release(array_obj);
@@ -463,14 +481,14 @@ void SwitcherData::loadMacros(obs_data_t *obj)
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(macroArray, i); obs_data_t *array_obj = obs_data_array_item(macroArray, i);
macros.emplace_back(); macros.emplace_back(std::make_shared<Macro>());
macros.back().Load(array_obj); macros.back()->Load(array_obj);
obs_data_release(array_obj); obs_data_release(array_obj);
} }
obs_data_array_release(macroArray); obs_data_array_release(macroArray);
for (auto &m : macros) { for (auto &m : macros) {
m.ResolveMacroRef(); m->ResolveMacroRef();
} }
} }
@@ -478,11 +496,11 @@ bool SwitcherData::checkMacros()
{ {
bool ret = false; bool ret = false;
for (auto &m : macros) { for (auto &m : macros) {
if (m.CeckMatch()) { if (m->CeckMatch()) {
ret = true; ret = true;
// This has to be performed here for now as actions are // This has to be performed here for now as actions are
// not performed immediately after checking conditions. // not performed immediately after checking conditions.
if (m.SwitchesScene()) { if (m->SwitchesScene()) {
switcher->macroSceneSwitched = true; switcher->macroSceneSwitched = true;
} }
} }
@@ -492,12 +510,15 @@ bool SwitcherData::checkMacros()
bool SwitcherData::runMacros() bool SwitcherData::runMacros()
{ {
for (auto &m : macros) { // TODO: Don't rely on creating a copy of each macro once new frontend
if (m.Matched()) { // events are available - see:
vblog(LOG_INFO, "running macro: %s", m.Name().c_str()); // https://github.com/obsproject/obs-studio/commit/feda1aaa283e8a99f6ba1159cfe6b9c1f2934a61
if (!m.PerformAction()) { for (auto m : macros) {
if (m->Matched()) {
vblog(LOG_INFO, "running macro: %s", m->Name().c_str());
if (!m->PerformAction()) {
blog(LOG_WARNING, "abort macro: %s", blog(LOG_WARNING, "abort macro: %s",
m.Name().c_str()); m->Name().c_str());
return false; return false;
} }
} }
@@ -508,8 +529,8 @@ bool SwitcherData::runMacros()
Macro *GetMacroByName(const char *name) Macro *GetMacroByName(const char *name)
{ {
for (auto &m : switcher->macros) { for (auto &m : switcher->macros) {
if (m.Name() == name) { if (m->Name() == name) {
return &m; return m.get();
} }
} }
@@ -570,3 +591,10 @@ void MacroRefAction::ResolveMacroRef()
{ {
_macro.UpdateRef(); _macro.UpdateRef();
} }
void MultiMacroRefAction::ResolveMacroRef()
{
for (auto &m : _macros) {
m.UpdateRef();
}
}

View File

@@ -156,7 +156,7 @@ void SceneTrigger::logMatch()
blog(LOG_INFO, blog(LOG_INFO,
"scene '%s' in status '%s' triggering action '%s' after %f seconds", "scene '%s' in status '%s' triggering action '%s' after %f seconds",
GetWeakSourceName(scene).c_str(), statusName.c_str(), GetWeakSourceName(scene).c_str(), statusName.c_str(),
actionName.c_str(), duration); actionName.c_str(), duration.seconds);
} }
void frontEndActionThread(sceneTriggerAction action, double delay) void frontEndActionThread(sceneTriggerAction action, double delay)
@@ -265,13 +265,15 @@ void SceneTrigger::performAction()
std::thread t; std::thread t;
if (isFrontendAction(triggerAction)) { if (isFrontendAction(triggerAction)) {
t = std::thread(frontEndActionThread, triggerAction, duration); t = std::thread(frontEndActionThread, triggerAction,
duration.seconds);
} else if (isAudioAction(triggerAction)) { } else if (isAudioAction(triggerAction)) {
bool mute = triggerAction == sceneTriggerAction::MUTE_SOURCE; bool mute = triggerAction == sceneTriggerAction::MUTE_SOURCE;
t = std::thread(muteThread, audioSource, duration, mute); t = std::thread(muteThread, audioSource, duration.seconds,
mute);
} else if (isSwitcherStatusAction(triggerAction)) { } else if (isSwitcherStatusAction(triggerAction)) {
bool stop = triggerAction == sceneTriggerAction::STOP_SWITCHER; bool stop = triggerAction == sceneTriggerAction::STOP_SWITCHER;
t = std::thread(statusThread, duration, stop); t = std::thread(statusThread, duration.seconds, stop);
} else { } else {
blog(LOG_WARNING, "ignoring unknown action '%d'", blog(LOG_WARNING, "ignoring unknown action '%d'",
static_cast<int>(triggerAction)); static_cast<int>(triggerAction));
@@ -374,7 +376,7 @@ void SceneTrigger::save(obs_data_t *obj)
obs_data_set_string(obj, "scene", GetWeakSourceName(scene).c_str()); obs_data_set_string(obj, "scene", GetWeakSourceName(scene).c_str());
obs_data_set_int(obj, "triggerType", static_cast<int>(triggerType)); obs_data_set_int(obj, "triggerType", static_cast<int>(triggerType));
obs_data_set_int(obj, "triggerAction", static_cast<int>(triggerAction)); obs_data_set_int(obj, "triggerAction", static_cast<int>(triggerAction));
obs_data_set_double(obj, "duration", duration); duration.Save(obj, "duration");
obs_data_set_string(obj, "audioSource", obs_data_set_string(obj, "audioSource",
GetWeakSourceName(audioSource).c_str()); GetWeakSourceName(audioSource).c_str());
} }
@@ -388,7 +390,7 @@ void SceneTrigger::load(obs_data_t *obj)
obs_data_get_int(obj, "triggerType")); obs_data_get_int(obj, "triggerType"));
triggerAction = static_cast<sceneTriggerAction>( triggerAction = static_cast<sceneTriggerAction>(
obs_data_get_int(obj, "triggerAction")); obs_data_get_int(obj, "triggerAction"));
duration = obs_data_get_double(obj, "duration"); duration.Load(obj, "duration");
const char *audioSourceName = obs_data_get_string(obj, "audioSource"); const char *audioSourceName = obs_data_get_string(obj, "audioSource");
audioSource = GetWeakSourceByName(audioSourceName); audioSource = GetWeakSourceByName(audioSourceName);
@@ -451,19 +453,17 @@ SceneTriggerWidget::SceneTriggerWidget(QWidget *parent, SceneTrigger *s)
{ {
triggers = new QComboBox(); triggers = new QComboBox();
actions = new QComboBox(); actions = new QComboBox();
duration = new QDoubleSpinBox(); duration = new DurationSelection();
audioSources = new QComboBox(); audioSources = new QComboBox();
duration->setMinimum(0.0);
duration->setMaximum(99.000000);
duration->setSuffix("s");
QWidget::connect(triggers, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(triggers, SIGNAL(currentIndexChanged(int)), this,
SLOT(TriggerTypeChanged(int))); SLOT(TriggerTypeChanged(int)));
QWidget::connect(actions, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(TriggerActionChanged(int))); SLOT(TriggerActionChanged(int)));
QWidget::connect(duration, SIGNAL(valueChanged(double)), this, QWidget::connect(duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double))); SLOT(DurationChanged(double)));
QWidget::connect(duration, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(DurationUnitChanged(DurationUnit)));
QWidget::connect(audioSources, QWidget::connect(audioSources,
SIGNAL(currentTextChanged(const QString &)), this, SIGNAL(currentTextChanged(const QString &)), this,
SLOT(AudioSourceChanged(const QString &))); SLOT(AudioSourceChanged(const QString &)));
@@ -475,7 +475,7 @@ SceneTriggerWidget::SceneTriggerWidget(QWidget *parent, SceneTrigger *s)
if (s) { if (s) {
triggers->setCurrentIndex(static_cast<int>(s->triggerType)); triggers->setCurrentIndex(static_cast<int>(s->triggerType));
actions->setCurrentIndex(static_cast<int>(s->triggerAction)); actions->setCurrentIndex(static_cast<int>(s->triggerAction));
duration->setValue(s->duration); duration->SetDuration(s->duration);
audioSources->setCurrentText( audioSources->setCurrentText(
GetWeakSourceName(s->audioSource).c_str()); GetWeakSourceName(s->audioSource).c_str());
@@ -552,14 +552,24 @@ void SceneTriggerWidget::TriggerActionChanged(int index)
} }
} }
void SceneTriggerWidget::DurationChanged(double dur) void SceneTriggerWidget::DurationChanged(double seconds)
{ {
if (loading || !switchData) { if (loading || !switchData) {
return; return;
} }
std::lock_guard<std::mutex> lock(switcher->m); std::lock_guard<std::mutex> lock(switcher->m);
switchData->duration = dur; switchData->duration.seconds = seconds;
}
void SceneTriggerWidget::DurationUnitChanged(DurationUnit unit)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->duration.displayUnit = unit;
} }
void SceneTriggerWidget::AudioSourceChanged(const QString &text) void SceneTriggerWidget::AudioSourceChanged(const QString &text)

View File

@@ -79,7 +79,7 @@ void AdvSSScreenshotObj::Copy()
uint8_t *videoData = nullptr; uint8_t *videoData = nullptr;
uint32_t videoLinesize = 0; uint32_t videoLinesize = 0;
image = QImage(cx, cy, QImage::Format::Format_RGBX8888); image = QImage(cx, cy, QImage::Format::Format_RGBA8888);
if (gs_stagesurface_map(stagesurf, &videoData, &videoLinesize)) { if (gs_stagesurface_map(stagesurf, &videoData, &videoLinesize)) {
int linesize = image.bytesPerLine(); int linesize = image.bytesPerLine();

View File

@@ -93,6 +93,11 @@ void Section::AddHeaderWidget(QWidget *w)
_headerWidgetLayout->addWidget(w); _headerWidgetLayout->addWidget(w);
} }
void Section::SetCollapsed(bool collapsed)
{
Collapse(collapsed);
}
bool Section::eventFilter(QObject *obj, QEvent *event) bool Section::eventFilter(QObject *obj, QEvent *event)
{ {
if (event->type() == QEvent::Resize && !_transitioning && !_collapsed) { if (event->type() == QEvent::Resize && !_transitioning && !_collapsed) {

View File

@@ -392,6 +392,11 @@ SceneSequenceSwitch *SceneSequenceSwitch::extend()
} }
extendedSequence = std::make_unique<SceneSequenceSwitch>(); extendedSequence = std::make_unique<SceneSequenceSwitch>();
extendedSequence->startScene = scene; extendedSequence->startScene = scene;
if (targetType == SwitchTargetType::SceneGroup) {
extendedSequence->startScene = nullptr;
extendedSequence->startTargetType =
SwitchTargetType::SceneGroup;
}
return extendedSequence.get(); return extendedSequence.get();
} }

View File

@@ -266,7 +266,7 @@ bool VideoSwitch::loadImageFromFile()
return false; return false;
} }
matchImage = matchImage =
matchImage.convertToFormat(QImage::Format::Format_RGBX8888); matchImage.convertToFormat(QImage::Format::Format_RGBA8888);
return true; return true;
} }

View File

@@ -12,6 +12,7 @@
#include <QTimer> #include <QTimer>
#include <QMessageBox> #include <QMessageBox>
#include <QJsonDocument> #include <QJsonDocument>
#include <QSystemTrayIcon>
#include <unordered_map> #include <unordered_map>
#include <regex> #include <regex>
#include <set> #include <set>
@@ -213,10 +214,10 @@ void deleteLayoutItem(QLayoutItem *item)
} }
} }
void clearLayout(QLayout *layout) void clearLayout(QLayout *layout, int afterIdx)
{ {
QLayoutItem *item; QLayoutItem *item;
while ((item = layout->takeAt(0))) { while (item = layout->takeAt(afterIdx)) {
if (item->layout()) { if (item->layout()) {
clearLayout(item->layout()); clearLayout(item->layout());
delete item->layout(); delete item->layout();
@@ -228,6 +229,16 @@ void clearLayout(QLayout *layout)
} }
} }
void setLayoutVisible(QLayout *layout, bool visible)
{
for (int i = 0; i < layout->count(); ++i) {
QWidget *widget = layout->itemAt(i)->widget();
if (widget != NULL) {
widget->setVisible(visible);
}
}
}
bool compareIgnoringLineEnding(QString &s1, QString &s2) bool compareIgnoringLineEnding(QString &s1, QString &s2)
{ {
// Let QT deal with different types of lineendings // Let QT deal with different types of lineendings
@@ -443,6 +454,17 @@ bool DisplayMessage(const QString &msg, bool question)
return false; return false;
} }
void DisplayTrayMessage(const QString &title, const QString &msg)
{
auto tray = reinterpret_cast<QSystemTrayIcon *>(
obs_frontend_get_system_tray());
if (!tray) {
return;
}
tray->showMessage(title, msg);
}
void addSelectionEntry(QComboBox *sel, const char *description, bool selectable, void addSelectionEntry(QComboBox *sel, const char *description, bool selectable,
const char *tooltip) const char *tooltip)
{ {
@@ -788,7 +810,8 @@ void populateSceneItemSelection(QComboBox *list, SceneSelection &s)
{ {
std::set<QString> names; std::set<QString> names;
if (s.GetType() == SceneSelectionType::CURRENT) { if (s.GetType() == SceneSelectionType::CURRENT ||
s.GetType() == SceneSelectionType::PREVIOUS) {
auto enumScenes = [](void *param, obs_source_t *source) { auto enumScenes = [](void *param, obs_source_t *source) {
if (!source) { if (!source) {
return true; return true;
@@ -816,6 +839,36 @@ void populateSceneItemSelection(QComboBox *list, SceneSelection &s)
list->setCurrentIndex(0); list->setCurrentIndex(0);
} }
void populateSourceTypeSelection(QComboBox *list)
{
std::set<QString> sourceTypeNames;
auto enumSourcesTypes = [](void *param, obs_source_t *source) {
if (!source) {
return true;
}
std::set<QString> *names =
reinterpret_cast<std::set<QString> *>(param);
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT) {
return true;
}
names->insert(
obs_source_get_display_name(obs_source_get_id(source)));
return true;
};
obs_enum_sources(enumSourcesTypes, &sourceTypeNames);
for (auto &name : sourceTypeNames) {
if (!name.isEmpty()) {
list->addItem(name);
}
}
list->model()->sort(0);
addSelectionEntry(list, obs_module_text("AdvSceneSwitcher.selectItem"));
list->setCurrentIndex(0);
}
QMetaObject::Connection PulseWidget(QWidget *widget, QColor startColor, QMetaObject::Connection PulseWidget(QWidget *widget, QColor startColor,
QColor endColor, QString specifier, QColor endColor, QString specifier,
bool once) bool once)