Compare commits

...

57 Commits
1.10 ... 1.12

Author SHA1 Message Date
WarmUpTill
2ce0b35921 Add volume to control to audio action 2021-05-15 09:51:20 -07:00
WarmUpTill
33d2def126 Explicitly cast enum class to int 2021-05-15 09:05:43 -07:00
WarmUpTill
c44b0a2ac2 Remove unused verbose parameter 2021-05-15 09:05:43 -07:00
WarmUpTill
3abeb936b3 Fix random order of various macro entries 2021-05-15 01:03:07 -07:00
WarmUpTill
cef513f775 Adjust plugin state macro condition id 2021-05-14 14:03:04 -07:00
WarmUpTill
a66961eac1 Add macro condition "idle" 2021-05-14 14:03:04 -07:00
WarmUpTill
92c8767e1a Move plugin state condition to end 2021-05-14 09:44:49 -07:00
WarmUpTill
5b12468a46 Add macro condition process 2021-05-14 09:44:49 -07:00
WarmUpTill
675021c284 Add Macro tab (#170)
* add action switch scene

* add window condition

* Add macro action wait

* Add macro condition region

* Add audio macro condition

* Add file based macro condition

* Add macro condition media

* Add video macro condition

* Add macro action audio

* Add macro action streaming

* Add macro action recording

* Add replay buffer macro action

* Add stream macro condition

* Add macro condition recording

* Add macro condition plugin state

* Add macro action to run an arbitrary command
2021-05-14 16:37:01 +02:00
WarmUpTill
6bf122d0e8 Add support for transition duration in switchScene() (#178) 2021-05-12 19:50:50 +02:00
Pander
8b8e479098 added system-wide Flatpak paths (#175) 2021-05-06 20:38:11 +02:00
WarmUpTill
717d41a527 Adjust default setting values of transition tab 2021-05-04 11:32:45 -07:00
WarmUpTill
ad5587e084 Fix transition overrides not restoring duration 2021-05-04 11:32:45 -07:00
WarmUpTill
f37f384b5a Move DisplayMessage() to utility.hpp 2021-05-04 11:32:45 -07:00
WarmUpTill
dad36689c4 Move AdvSSScreenshotObj to separate files 2021-05-04 11:32:45 -07:00
WarmUpTill
0bd7d004ad Reduce includes of platform specific files
This is done to avoid build issues in future.
For example previously the class "Duration" class caused build issues on
the MacOS platform due to a name conflict.
2021-05-04 11:32:45 -07:00
WarmUpTill
f8c589c7a2 Only send scene switch msg if scene was switched
This change is to avoid spamming the connected clients with scene change
messages which might not even do anything as the scene change already
took place.
Additionally it allows scene switch messages to be sent by just calling
the switchScene() function.
2021-05-04 11:32:45 -07:00
WarmUpTill
e714df7ad0 Use Duration class and DurationSelection widget 2021-05-04 11:32:45 -07:00
WarmUpTill
6277da1bbd Add Duration class and DurationSelection widget 2021-05-04 11:32:45 -07:00
WarmUpTill
1bef309264 Only try to change transition overrides if needed 2021-05-04 11:32:45 -07:00
WarmUpTill
2e1d660e17 Clean up loading of transition tab settings 2021-05-04 11:32:45 -07:00
WarmUpTill
3f492f468a Move compareIgnoringLineEnding() to utility.hpp 2021-05-04 11:32:45 -07:00
WarmUpTill
c86bba84b3 Add scene-group.hpp include to switch-generic
This is to allow easier use of generic scene switch structs and widgets
in other areas. (Macro tab)
2021-05-04 11:32:45 -07:00
WarmUpTill
c468b0698b Move definition of SwitchTargetType 2021-05-04 11:32:45 -07:00
WarmUpTill
2af82fe22a Fix ignoring match of checkSwitchInfoFromFile() if checkFileContent() does not match 2021-05-04 11:32:45 -07:00
WarmUpTill
5d720219bf Clean up transition tab save and load 2021-05-04 11:32:45 -07:00
WarmUpTill
484e1aa191 Remove unnused function 2021-05-04 11:32:45 -07:00
WarmUpTill
0fdfe757b4 Reset tab order if config is invalid 2021-05-04 11:32:45 -07:00
WarmUpTill
bc5a04651f Remove unnecessary indirection 2021-05-04 11:32:45 -07:00
WarmUpTill
d9dfc1913d Fix previous scene not working for some sequences
If a duration for a transition is used that is longer than the scene
switcher check interval it could happen that the "incorrect" previous
scene could be chosen for uninterruptible scene sequences, as at the
time the sequence check was preformed the "previous scene" variable
was not yet updated as the transition was still ongoing.
2021-05-04 11:32:45 -07:00
WarmUpTill
1310d0b3f3 Add comment 2021-05-04 11:32:45 -07:00
WarmUpTill
139df8a641 Adjust function signature
Don't pass bool by reference if not necessary.
2021-05-04 11:32:45 -07:00
WarmUpTill
acf1ae63aa Drop support for old settings format 2021-05-04 11:32:45 -07:00
WarmUpTill
041a9e7f19 Add option to set transition duration (#171) 2021-05-01 00:20:25 +02:00
Pander
427ed34a1b added Flatpak paths for Ubuntu (#172) 2021-04-27 18:35:04 +02:00
WarmUpTill
45fa4dac8f Add option "has changed" to video tab (#168) 2021-04-22 20:51:35 +02:00
WarmUpTill
e4bf5fa749 Add option to use transition overrides for transition changes (#164)
* Adjust switchScene() signature to support adjustActiveTransitionType and verbose parameters and only change the transition type if adjustActiveTransitionType, otherwise rely on transition overrides

* Add new adjustActiveTransitionType checkbox

* save and load adjustActiveTransitionType

* Add check to either enable transition type change or use of transition
overrides

* Add locale

* Adjust layout and locale
2021-04-10 19:58:17 +02:00
WarmUpTill
eb346da6f4 CI: Fix macOS build by disabling browser build (#163) 2021-04-09 20:57:35 +02:00
WarmUpTill
866480cde6 Fix crash on Linux if no ewmh is available (#159) 2021-04-07 21:03:54 +02:00
WarmUpTill
10e1c7b3a3 Resolve asio network crashes on macOS (#157)
* Reenable network tab functionality for macOS

* Set asio to b73dc1d and  websocketpp to c6d7e29
2021-04-03 22:02:42 +02:00
WarmUpTill
beb482bd5a Disable Network tab functionality on macOS (#155)
The asio library ran into issues when the obs-websocket plugin was also running leading to a crash in the obs-websocket's listen() calls.

As the use obs-websocket plugin is more widespread than the use of the advanced scene switcher the network tab functionality will be disabled for macOS for the time being.
2021-04-03 18:52:16 +02:00
WarmUpTill
e561d378dd CI: Disable PipeWire on Ubuntu (#156) 2021-04-03 12:43:52 +02:00
WarmUpTill
73ce88e2f0 Add option to save window size and position (#154) 2021-04-03 11:55:42 +02:00
WarmUpTill
dfccc4b16d Add network tab (#149)
The network tab allows you to open a web-socket or connect to web-socket to synchronize the active scene between two or more instances of OBS.
The functionality is mostly based on https://github.com/Palakis/obs-websocket.

An OBS instance running a server will send its currently active scene to all connected clients on scene change.
By default all scene switches will trigger a message to be sent to all clients, but you also have the option to limit sending these messages only when an automated scene change is triggered by the advanced scene switcher.
2021-03-24 20:51:22 +01:00
WarmUpTill
b0ef1fee71 Fix window check always testing for focus (#152) 2021-03-22 23:06:44 +01:00
WarmUpTill
1ef8ce4b4a Resolve Qt runtime warning by renaming function. (#151) 2021-03-22 22:31:29 +01:00
WarmUpTill
1bf4bff014 Merge pull request #150 from WarmUpTill/CI
CI: Improve format check and init submodules for build
2021-03-21 21:48:21 +01:00
WarmUpTill
4b6e00b2b2 CI: Init submodules for build 2021-03-21 21:39:11 +01:00
WarmUpTill
c99cbb0c0f CI: Improve format check.
The format check used to check too many files, which would be worsened
when introducing additional submodules.
2021-03-21 21:37:10 +01:00
WarmUpTill
2a87334d38 Init noMatchDelay to zero. (#148)
A non-zero value might be confusing for users which set up the plugin
for the first time.
2021-03-21 14:44:42 +01:00
WarmUpTill
1a4724ff21 Fix audio tab not supporting mono audio output. (#145) 2021-03-19 20:01:15 +01:00
WarmUpTill
59d8fb5d28 Fix MacOS build. (#146) 2021-03-19 19:43:58 +01:00
WarmUpTill
8823c05aeb Fix scene groups not being selected when using more than one entry on the Random tab. (#143) 2021-03-18 21:22:33 +01:00
WarmUpTill
2d0a52f8d1 Enable use of Scene Groups for Random tab (#141) 2021-03-17 20:00:06 +01:00
WarmUpTill
1273446ce5 Add option to configure default transition delay. (#140) 2021-03-17 18:21:42 +01:00
WarmUpTill
177ca4aea2 CI: Build on Ubuntu 18.04, use newer clang-format. (#139) 2021-03-13 17:02:04 +01:00
WarmUpTill
7526cbe1a3 Add option to check volume of 'inactive' audio sources. (#137) 2021-03-12 18:48:06 +01:00
100 changed files with 8855 additions and 1482 deletions

View File

@@ -27,6 +27,7 @@ jobs:
uses: actions/checkout@v2.3.4
with:
path: UI/frontend-plugins/${{ env.PLUGIN_NAME }}
submodules: 'recursive'
- name: Fetch Git Tags
run: |
cd UI/frontend-plugins/${{ env.PLUGIN_NAME }}
@@ -62,7 +63,7 @@ jobs:
echo "add_subdirectory(${{ env.PLUGIN_NAME }})" >> UI/frontend-plugins/CMakeLists.txt
mkdir ./build
cd ./build
cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -DDISABLE_PYTHON=ON -DDepsPath="/tmp/obsdeps" -DQTDIR="/tmp/obsdeps" ..
cmake -DBUILD_BROWSER=OFF -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -DDISABLE_PYTHON=ON -DDepsPath="/tmp/obsdeps" -DQTDIR="/tmp/obsdeps" ..
cd -
- name: Build
shell: bash
@@ -75,8 +76,9 @@ jobs:
if: success()
shell: bash
run: |
curl -L -O https://s3-us-west-2.amazonaws.com/obs-nightly/Packages.pkg
sudo installer -pkg ./Packages.pkg -target /
curl -L -O http://s.sudre.free.fr/Software/files/Packages.dmg
sudo hdiutil attach ./Packages.dmg
sudo installer -pkg /Volumes/Packages\ 1.2.9/Install\ Packages.pkg -target /
- name: Package
if: success()
shell: bash
@@ -102,7 +104,7 @@ jobs:
path: ./nightly/*.pkg
ubuntu64:
name: 'Linux/Ubuntu 64-bit'
runs-on: [ubuntu-latest]
runs-on: [ubuntu-18.04]
steps:
- name: Checkout
uses: actions/checkout@v2.3.4
@@ -113,6 +115,7 @@ jobs:
uses: actions/checkout@v2.3.4
with:
path: UI/frontend-plugins/${{ env.PLUGIN_NAME }}
submodules: 'recursive'
- name: Add plugin to obs cmake
shell: bash
run: echo "add_subdirectory(${{ env.PLUGIN_NAME }})" >> UI/frontend-plugins/CMakeLists.txt
@@ -170,7 +173,7 @@ jobs:
run: |
mkdir ./build
cd ./build
cmake -DUNIX_STRUCTURE=0 -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/obs-studio-portable" -DWITH_RTMPS=OFF -DBUILD_BROWSER=OFF ..
cmake -DENABLE_PIPEWIRE=OFF -DUNIX_STRUCTURE=0 -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/obs-studio-portable" -DWITH_RTMPS=OFF -DBUILD_BROWSER=OFF ..
- name: 'Build'
shell: bash
working-directory: ${{ github.workspace }}/build
@@ -210,6 +213,7 @@ jobs:
uses: actions/checkout@v2.3.4
with:
path: UI/frontend-plugins/${{ env.PLUGIN_NAME}}
submodules: 'recursive'
- name: Add plugin to obs cmake
shell: cmd
run: echo add_subdirectory(${{ env.PLUGIN_NAME }}) >> UI/frontend-plugins/CMakeLists.txt
@@ -271,7 +275,7 @@ jobs:
release:
needs: [macos64, ubuntu64, windows]
name: 'Create Release'
runs-on: [ubuntu-latest]
runs-on: [ubuntu-18.04]
steps:
- name: "Checkout plugin"
uses: actions/checkout@v2.3.4

View File

@@ -8,23 +8,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: 'recursive'
- name: Install clang format
run: |
# gets us newer clang
sudo bash -c "cat >> /etc/apt/sources.list" << LLVMAPT
# 3.8
deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-8 main
deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-8 main
LLVMAPT
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
sudo apt-get -qq update
sudo apt-get install -y clang-format-8
sudo apt-get install -y clang-format-10
- name: Check the Formatting
run: |

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "deps/asio"]
path = deps/asio
url = https://github.com/chriskohlhoff/asio.git
[submodule "deps/websocketpp"]
path = deps/websocketpp
url = https://github.com/zaphoyd/websocketpp.git

View File

@@ -6,6 +6,8 @@ set -o errexit
set -o pipefail
set -o nounset
SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
# Runs the Clang Formatter in parallel on the code base.
# Return codes:
# - 1 there are files to be formatted
@@ -19,13 +21,15 @@ if [[ $OS = "Linux" || $OS = "Darwin" ]] ; then
fi
# Discover clang-format
if type clang-format-8 2> /dev/null ; then
if type clang-format-10 2> /dev/null ; then
CLANG_FORMAT=clang-format-10
elif type clang-format-8 2> /dev/null ; then
CLANG_FORMAT=clang-format-8
else
CLANG_FORMAT=clang-format
fi
find .. -type d \( -path ./deps \
-o -path ./cmake \
-o -path ./build \) -prune -type f -o -name '*.h' -or -name '*.hpp' -or -name '*.m' -or -name '*.mm' -or -name '*.c' -or -name '*.cpp' \
find $SCRIPTPATH/.. -type d \( -path $SCRIPTPATH/../deps \
-o -path $SCRIPTPATH/../cmake \
-o -path $SCRIPTPATH/../build \) -prune -type f -o -name '*.h' -or -name '*.hpp' -or -name '*.m' -or -name '*.mm' -or -name '*.c' -or -name '*.cpp' \
| xargs -L100 -P${NPROC} ${CLANG_FORMAT} -i -style=file -fallback-style=none

View File

@@ -27,6 +27,8 @@ Examples are ...
/usr/lib/x86_64-linux-gnu/obs-plugins/
/usr/share/obs/obs-plugins/
~/.config/obs-studio/plugins/advanced-scene-switcher/bin/64bit/
~/.local/share/flatpak/app/com.obsproject.Studio/x86_64/stable/active/files/lib/obs-plugins/
/var/lib/flatpak/app/com.obsproject.Studio/x86_64/stable/active/files/lib/obs-plugins/
Unfortunately the expected location of the locale, which can be found in the data folder, can vary also.
The data folder usually should be copied to:
@@ -34,5 +36,7 @@ The data folder usually should be copied to:
If this does not work you can try to copy the "locale" folder found inside the data folder to:
/usr/share/obs/obs-plugins/advanced-scene-switcher/locale
~/.local/share/flatpak/app/com.obsproject.Studio/x86_64/stable/active/files/share/obs/obs-plugins/advanced-scene-switcher/locale
/var/lib/flatpak/app/com.obsproject.Studio/x86_64/stable/active/files/share/obs/obs-plugins/advanced-scene-switcher/locale
In doubt please check where other "en-US.ini" files are located on your system.

View File

@@ -1,6 +1,7 @@
cmake_minimum_required(VERSION 3.5)
project(advanced-scene-switcher)
add_definitions(-DASIO_STANDALONE)
# generate version info
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
@@ -52,13 +53,16 @@ if(UNIX AND NOT APPLE)
include_directories(${X11_INCLUDE_DIR})
endif()
include_directories(
"${CMAKE_CURRENT_SOURCE_DIR}/deps/asio/asio/include"
"${CMAKE_CURRENT_SOURCE_DIR}/deps/websocketpp"
)
set(advanced-scene-switcher_HEADERS
${advanced-scene-switcher_HEADERS}
src/headers/advanced-scene-switcher.hpp
src/headers/switcher-data-structs.hpp
src/headers/utility.hpp
src/headers/curl-helper.hpp
src/headers/volume-control.hpp
src/headers/scene-group.hpp
src/headers/scene-trigger.hpp
src/headers/switch-audio.hpp
@@ -66,6 +70,7 @@ set(advanced-scene-switcher_HEADERS
src/headers/switch-file.hpp
src/headers/switch-idle.hpp
src/headers/switch-media.hpp
src/headers/switch-network.hpp
src/headers/switch-pause.hpp
src/headers/switch-random.hpp
src/headers/switch-screen-region.hpp
@@ -75,6 +80,34 @@ set(advanced-scene-switcher_HEADERS
src/headers/switch-sequence.hpp
src/headers/switch-video.hpp
src/headers/switch-generic.hpp
src/headers/macro-action-edit.hpp
src/headers/macro-action-audio.hpp
src/headers/macro-action-recording.hpp
src/headers/macro-action-replay-buffer.hpp
src/headers/macro-action-run.hpp
src/headers/macro-action-streaming.hpp
src/headers/macro-action-switch-scene.hpp
src/headers/macro-action-wait.hpp
src/headers/macro-condition-edit.hpp
src/headers/macro-condition-audio.hpp
src/headers/macro-condition-file.hpp
src/headers/macro-condition-idle.hpp
src/headers/macro-condition-media.hpp
src/headers/macro-condition-plugin-state.hpp
src/headers/macro-condition-process.hpp
src/headers/macro-condition-recording.hpp
src/headers/macro-condition-region.hpp
src/headers/macro-condition-scene.hpp
src/headers/macro-condition-streaming.hpp
src/headers/macro-condition-video.hpp
src/headers/macro-condition-window.hpp
src/headers/macro.hpp
src/headers/curl-helper.hpp
src/headers/screenshot-helper.hpp
src/headers/name-dialog.hpp
src/headers/duration-control.hpp
src/headers/section.hpp
src/headers/volume-control.hpp
src/headers/version.h
)
@@ -94,6 +127,7 @@ set(advanced-scene-switcher_SOURCES
src/switch-file.cpp
src/switch-window.cpp
src/switch-media.cpp
src/switch-network.cpp
src/hotkey.cpp
src/general.cpp
src/switch-pause.cpp
@@ -102,7 +136,34 @@ set(advanced-scene-switcher_SOURCES
src/switch-audio.cpp
src/switch-video.cpp
src/switch-generic.cpp
src/macro-action-edit.cpp
src/macro-action-audio.cpp
src/macro-action-recording.cpp
src/macro-action-replay-buffer.cpp
src/macro-action-run.cpp
src/macro-action-streaming.cpp
src/macro-action-switch-scene.cpp
src/macro-action-wait.cpp
src/macro-condition-edit.cpp
src/macro-condition-audio.cpp
src/macro-condition-file.cpp
src/macro-condition-idle.cpp
src/macro-condition-media.cpp
src/macro-condition-plugin-state.cpp
src/macro-condition-process.cpp
src/macro-condition-recording.cpp
src/macro-condition-region.cpp
src/macro-condition-scene.cpp
src/macro-condition-streaming.cpp
src/macro-condition-video.cpp
src/macro-condition-window.cpp
src/macro.cpp
src/macro-tab.cpp
src/curl-helper.cpp
src/screenshot-helper.cpp
src/name-dialog.cpp
src/duration-control.cpp
src/section.cpp
src/volume-control.cpp
src/version.cpp
)
@@ -113,6 +174,8 @@ set(advanced-scene-switcher_UI
)
if(WIN32)
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
set(advanced-scene-switcher_PLATFORM_SOURCES
src/win/advanced-scene-switcher-win.cpp)
elseif(APPLE)

View File

@@ -1,7 +1,7 @@
# SceneSwitcher
An automated scene switcher for OBS Studio
More information can be found on https://obsproject.com/forum/resources/automatic-scene-switching.395/
More information can be found on https://obsproject.com/forum/resources/automatic-scene-switching.395/ and see https://github.com/WarmUpTill/SceneSwitcher/blob/master/CI/release/README.txt for installing releases.
## Compiling in tree (recommended)
Add the "SceneSwitcher" source directory to your obs-studio source directory under obs-studio/UI/frontend-plugins/:

View File

@@ -27,6 +27,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="Wechsle zu:"
AdvSceneSwitcher.generalTab.generalBehavior.cooldown="Nach einem automatisierten Szenenwechsel wechsle nicht mehr für"
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="In diesem Zeitraum werden potentielle erfüllte Bedingungen ignoriert!"
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Ausführliches Logging"
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Fensterposition und -größe speichern"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Deaktiviere UI Tipps"
AdvSceneSwitcher.generalTab.priority="Priorität"
AdvSceneSwitcher.generalTab.priority.description="Szenenwechselmethoden sortiert nach Priorität"
@@ -53,13 +54,18 @@ AdvSceneSwitcher.generalTab.priority.video="Video"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Szenenübergänge"
AdvSceneSwitcher.transitionTab.transitionOverride="Szenenübergänge, die im Szenenwechsler konfiguriert werden, haben Vorrang vor Übergangsüberschreibung"
AdvSceneSwitcher.transitionTab.setTransitionBy="Beim Ändern des Szenenübergangs:"
AdvSceneSwitcher.transitionTab.transitionOverride="Verwende Übergangsüberschreibungen"
AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="Wechsle den aktiven Szenenübergangtyp"
AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError="Mindestens eine Option muss aktiv sein:\n\n - Verwende Übergangsüberschreibungen\n\n - Wechsle den aktiven Szenenübergangtyp"
AdvSceneSwitcher.transitionTab.transitionForAToB="Szenenübergänge für automatisierte Szenenwechsel von Szene A zu Szene B"
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>Diese Einstellungen beeinflussen <span style=\"font-style:italic;\">nur</span> vom Szenenwechsler ausgelöste Szenenübergänge - Siehe <a href=\"https://obsproject.com/forum/resources/transition-table.1174/\"><span style=\" text-decoration: underline; color:#268bd2;\">Transition Table</span></a> um auch manuelle Szenenübergänge zu konfigurieren.<br/>Einstellungen auf diesem Tab haben Vorrang, vor denen welche auf den übrigen Tabs konfiguriert wurden.<br/><br/>Klicke auf das Plus Symbol, um einen neuen Eintrag hinzuzufügen.</p></body></html>"
AdvSceneSwitcher.transitionTab.defaultTransition="Ändere den Szenenübergang wenn eine Szene aktiv ist"
AdvSceneSwitcher.transitionTab.entry="Wechsle von {{scenes}} zu {{scenes2}} mit {{transitions}}"
AdvSceneSwitcher.transitionTab.entry="Wechsle von {{scenes}} zu {{scenes2}} mit {{transitions}} mit einer Dauer von {{duration}}"
AdvSceneSwitcher.transitionTab.defaultTransitionEntry="Wenn {{scenes}} aktiv ist ändere den Szenenübergang zu {{transitions}}"
AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="Klicke auf das Plus Symbol, um einen neuen Eintrag hinzuzufügen."
AdvSceneSwitcher.transitionTab.defaultTransition.delay="Wechsle Szenenübergang {{defTransitionDelay}} nach Szenenwechsel."
AdvSceneSwitcher.transitionTab.defaultTransition.delay.help="Diese Verzögerung dient dazu Szenenwechsel zu unterbrechen.\nDies kann passieren, wenn der Szenenübergangtyp gewechselt wird während ein Szenenübergang grade noch im Gang ist."
; Pause Scenes Tab
AdvSceneSwitcher.pauseTab.title="Pause"
@@ -187,17 +193,18 @@ AdvSceneSwitcher.sceneSequenceTab.fileType="Text Dateien (*.txt)"
AdvSceneSwitcher.sceneSequenceTab.interruptible="kann unterbrochen werden"
AdvSceneSwitcher.sceneSequenceTab.interruptibleHint="Andere Szenenwechselmethoden können diese Sequenz unterbrechen"
AdvSceneSwitcher.sceneSequenceTab.extendEdit="Szenenfolge erweitern"
AdvSceneSwitcher.sceneSequenceTab.extendEntry="Nach {{delay}} {{delayUnits}} wechsle zu {{scenes}} mit {{transitions}}"
AdvSceneSwitcher.sceneSequenceTab.entry="Wenn {{startScenes}} aktiv ist wechsle zu {{scenes}} nach {{delay}} {{delayUnits}} mit {{transitions}} {{interruptible}}"
AdvSceneSwitcher.sceneSequenceTab.extendEntry="Nach {{delay}} wechsle zu {{scenes}} mit {{transitions}}"
AdvSceneSwitcher.sceneSequenceTab.entry="Wenn {{startScenes}} aktiv ist wechsle zu {{scenes}} nach {{delay}} mit {{transitions}} {{interruptible}}"
AdvSceneSwitcher.sceneSequenceTab.help="Dieser Tab ermöglicht es automatisiert zu einer anderen Szene zu wechseln, nachdem eine Szene für die konfigurierte Zeit aktiv war.\nZum Beispiel kann so automatisch zwischen zwei Szenen hin und her gewechselt werden.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
; Audio Tab
AdvSceneSwitcher.audioTab.title="Audio"
AdvSceneSwitcher.audioTab.condition.above="über"
AdvSceneSwitcher.audioTab.condition.below="unter"
AdvSceneSwitcher.audioTab.entry="Wenn die Lautstärke von {{audioSources}} {{condition}} {{volumeWidget}} ist für {{duration}} wechsle zu {{scenes}} mit {{transitions}}"
AdvSceneSwitcher.audioTab.ignoreInactiveSource="außer Audio Quelle ist inaktiv"
AdvSceneSwitcher.audioTab.entry="Wenn die Lautstärke von {{audioSources}} {{condition}} {{volumeWidget}} ist für {{duration}} Sekunden wechsle zu {{scenes}} mit {{transitions}} {{ignoreInactiveSource}}"
AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="Wenn mehrere Einträge zutreffen ..."
AdvSceneSwitcher.audioTab.multiMatchfallback="... für {{duration}} wechsle zu {{scenes}} mit {{transitions}}"
AdvSceneSwitcher.audioTab.multiMatchfallback="... für {{duration}} Sekunden wechsle zu {{scenes}} mit {{transitions}}"
AdvSceneSwitcher.audioTab.help="Dieser Tab ermöglicht es basierend auf der Lautstärke von Audioquellen Szenen zu wechseln.\nSo kann zum Beispiel automatisch zu einer Szene gewechselt werden, wenn die Lautstärke eines Mikrofons eine konfigurierte Schwelle überschreitet.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
; Video Tab
@@ -208,10 +215,34 @@ AdvSceneSwitcher.videoTab.condition.match="genau übereinstimmt mit"
AdvSceneSwitcher.videoTab.condition.match.tooltip="Eine genaue Übereinstimmung setzt voraus, dass sowohl Zielquelle als auch das Bild die gleiche Auflösung besitzen.\nZusätzlich muss jeder einzelne Pixel zwischen beiden Bildquellen übereinstimmen weshalb Bildformate, welche das Bild komprimieren (z. B. .JPG) nicht zu empfehlen sind."
AdvSceneSwitcher.videoTab.condition.differ="nicht übereinstimmt mit"
AdvSceneSwitcher.videoTab.condition.hasNotChanged="sich nicht verändert"
AdvSceneSwitcher.videoTab.condition.hasNotChanged="sich verändert"
AdvSceneSwitcher.videoTab.ignoreInactiveSource="außer Video Quelle ist inaktiv"
AdvSceneSwitcher.videoTab.entry="Wenn {{videoSources}} {{condition}} {{filePath}} {{browseButton}} für {{duration}} wechsle zu {{scenes}} mit {{transitions}} {{ignoreInactiveSource}}"
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>Dieser Tab ermöglicht es basierend auf der Videoausgabe von Quellen scenen zu wechseln.<br/>Für eine noch bessere Implementierung dieser Funktionalität siehe <a href=\"https://obsproject.com/forum/resources/pixel-match-switcher.1202/\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a>.<br/><br/>Klicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen..</p></body></html>"
; Network Tab
AdvSceneSwitcher.networkTab.title="Netzwerk"
AdvSceneSwitcher.networkTab.warning="Die Verwendung des Servers außerhalb eines lokalen Netzwerks kann dazu führen, dass die aktive Szene von dritten Personen ausgelesen werden kann."
AdvSceneSwitcher.networkTab.Disabledwarning="Diese Funktionalität musste unter macOS leider aufgrund von Bibliotheksinkompatibilitäten mit dem obs-websocket plugin deaktiviert werden."
AdvSceneSwitcher.networkTab.server="Server starten (Sendet Szenenwechselnachrichten zu allen verbundenen Clients)"
AdvSceneSwitcher.networkTab.server.port="Port"
AdvSceneSwitcher.networkTab.server.lockToIPv4="Nur IPv4 verwenden (deaktiviert IPv6)"
AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Sende nur Szenenwechselnachrichten für automatisierte Szenenwechsel"
AdvSceneSwitcher.networkTab.startFailed.message="Der WebSocket-Server konnte nicht gestartet werden, mögliche Gründe:\n - Der TCP-Port %1 wird möglicherweise gerade von einem anderen Programm verwendet. Versuchen Sie einen anderen Port in den Websocket-Servereinstellungen zu setzen oder alle Programme zu beenden, die den Port möglicherweise verwenden.\n - Fehler: %2"
AdvSceneSwitcher.networkTab.server.status.currentStatus="Aktueller Status"
AdvSceneSwitcher.networkTab.server.status.notRunning="Nicht gestartet"
AdvSceneSwitcher.networkTab.server.status.starting="Startet"
AdvSceneSwitcher.networkTab.server.status.running="Server läuft"
AdvSceneSwitcher.networkTab.server.restart="Server neu starten"
AdvSceneSwitcher.networkTab.client="Client starten (Empfängt Szenenwechselnachrichten )"
AdvSceneSwitcher.networkTab.client.address="Hostname oder IP-Adresse"
AdvSceneSwitcher.networkTab.client.port="Port"
AdvSceneSwitcher.networkTab.client.status.currentStatus="Aktueller status"
AdvSceneSwitcher.networkTab.client.status.disconnected="Verbindung getrennt"
AdvSceneSwitcher.networkTab.client.status.connecting="Verbinde"
AdvSceneSwitcher.networkTab.client.status.connected="Verbunden"
AdvSceneSwitcher.networkTab.client.reconnect="Erneutes Verbinden erzwingen"
; Scene Group Tab
AdvSceneSwitcher.sceneGroupTab.title="Szenengruppe"
AdvSceneSwitcher.sceneGroupTab.list="Szenengruppen"

View File

@@ -27,6 +27,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="Switch to:"
AdvSceneSwitcher.generalTab.generalBehavior.cooldown="After a match do not switch scenes for"
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="During this time potential matches will be ignored!"
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Enable verbose logging"
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Save window position and size"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Disable UI hints"
AdvSceneSwitcher.generalTab.priority="Priority"
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
@@ -50,16 +51,126 @@ AdvSceneSwitcher.generalTab.priority.media="Media"
AdvSceneSwitcher.generalTab.priority.time="Time"
AdvSceneSwitcher.generalTab.priority.audio="Audio"
AdvSceneSwitcher.generalTab.priority.video="Video"
AdvSceneSwitcher.generalTab.priority.macro="Macro"
; Macro Tab
AdvSceneSwitcher.macroTab.title="Macro"
AdvSceneSwitcher.macroTab.macros="Macros"
AdvSceneSwitcher.macroTab.help="Macros allow you to execute a string of actions depending on multiple conditions.\n\nClick on the highlighted plus symbol to add a new Macro."
AdvSceneSwitcher.macroTab.editConditionHelp="This section allows you to define Macro conditions.\n\nSelect an existing or add a new Macro on the left."
AdvSceneSwitcher.macroTab.editActionHelp="This section allows you to define Macro actions.\n\nSelect an existing or add a new Macro on the left."
AdvSceneSwitcher.macroTab.edit="Edit macro"
AdvSceneSwitcher.macroTab.edit.logic="Logic type:"
AdvSceneSwitcher.macroTab.edit.condition="Condition type:"
AdvSceneSwitcher.macroTab.edit.action="Action type:"
AdvSceneSwitcher.macroTab.add="Add new macro"
AdvSceneSwitcher.macroTab.name="Name:"
AdvSceneSwitcher.macroTab.defaultname="Macro %1"
AdvSceneSwitcher.macroTab.exists="Macro name exists already"
AdvSceneSwitcher.macroTab.copy="Create copy"
; Macro Logic
AdvSceneSwitcher.logic.none="Ignore entry"
AdvSceneSwitcher.logic.and="And"
AdvSceneSwitcher.logic.or="Or"
AdvSceneSwitcher.logic.andNot="And not"
AdvSceneSwitcher.logic.orNot="Or not"
AdvSceneSwitcher.logic.rootNone="If"
AdvSceneSwitcher.logic.not="If not"
; Macro Conditions
AdvSceneSwitcher.condition.audio="Audio"
AdvSceneSwitcher.ondition.audio.state.below="Below"
AdvSceneSwitcher.ondition.audio.state.above="Above"
AdvSceneSwitcher.condition.audio.entry="Volume of {{audioSources}} is {{condition}} {{volume}} for {{duration}} seconds"
AdvSceneSwitcher.condition.region="Screen region"
AdvSceneSwitcher.condition.region.entry="Cursor is in {{minX}} {{minY}} x {{maxX}} {{maxY}}"
AdvSceneSwitcher.condition.scene="Scene"
AdvSceneSwitcher.condition.scene.type.current="Current"
AdvSceneSwitcher.condition.scene.type.previous="Previous"
AdvSceneSwitcher.condition.scene.entry="{{sceneType}} scene is {{scenes}} for {{duration}}"
AdvSceneSwitcher.condition.window="Window"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} exist and ..."
AdvSceneSwitcher.condition.window.entry.line2="... is {{fullscreen}} fullscreen {{maximized}} maximized {{focused}} focused"
AdvSceneSwitcher.condition.file="File"
AdvSceneSwitcher.condition.file.entry.line1="Content of {{fileType}} {{filePath}} {{browseButton}} matches:"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.condition.media="Media"
AdvSceneSwitcher.condition.media.entry="{{mediaSources}} state is {{states}} and {{timeRestrictions}} {{time}}"
AdvSceneSwitcher.condition.video="Video"
AdvSceneSwitcher.condition.video.condition.match="exactly matches"
AdvSceneSwitcher.condition.video.condition.differ="does not match"
AdvSceneSwitcher.condition.video.condition.hasChanged="has changed"
AdvSceneSwitcher.condition.video.condition.hasNotChanged="has not changed"
AdvSceneSwitcher.condition.video.condition.noImage="has no output"
AdvSceneSwitcher.condition.video.askFileAction="Do you want to use an existing file or create a screenshot of the currently selected source?"
AdvSceneSwitcher.condition.video.askFileAction.file="Use existing file"
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot"
AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{filePath}} {{browseButton}} for {{duration}}"
AdvSceneSwitcher.condition.stream="Streaming"
AdvSceneSwitcher.condition.stream.state.start="Stream running"
AdvSceneSwitcher.condition.stream.state.stop="Stream stopped"
AdvSceneSwitcher.condition.stream.entry="{{streamState}} for {{duration}}"
AdvSceneSwitcher.condition.record="Recording"
AdvSceneSwitcher.condition.record.state.start="Recording running"
AdvSceneSwitcher.condition.record.state.pause="Recording paused"
AdvSceneSwitcher.condition.record.state.stop="Recording stopped"
AdvSceneSwitcher.condition.record.entry="{{recordState}} for {{duration}}"
AdvSceneSwitcher.condition.process="Process"
AdvSceneSwitcher.condition.process.entry="{{processes}} is running {{focused}} and is focused"
AdvSceneSwitcher.condition.idle="Idle"
AdvSceneSwitcher.condition.idle.entry="No keyboard or mouse inputs for {{duration}}"
AdvSceneSwitcher.condition.pluginState="Plugin state"
AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="Automated scene change was triggered in this interval"
AdvSceneSwitcher.condition.pluginState.entry="{{condition}}"
; Macro Actions
AdvSceneSwitcher.action.switchScene="Switch scene"
AdvSceneSwitcher.action.scene.entry="Switch to scene {{scenes}} using {{transitions}} with a duration of {{duration}} seconds"
AdvSceneSwitcher.action.wait="Wait"
AdvSceneSwitcher.action.wait.type.fixed="fixed"
AdvSceneSwitcher.action.wait.type.random="random"
AdvSceneSwitcher.action.wait.entry.fixed="Wait for {{waitType}} duration of {{duration}}"
AdvSceneSwitcher.action.wait.entry.random="Wait for {{waitType}} duration from {{duration}} to {{duration2}}"
AdvSceneSwitcher.action.audio="Audio"
AdvSceneSwitcher.action.audio.type.mute="Mute"
AdvSceneSwitcher.action.audio.type.unmute="Unmute"
AdvSceneSwitcher.action.audio.type.sourceVolume="Set source volume"
AdvSceneSwitcher.action.audio.type.masterVolume="Set master volume"
AdvSceneSwitcher.action.audio.entry="{{actions}} {{audioSources}} {{volume}}"
AdvSceneSwitcher.action.recording="Recording"
AdvSceneSwitcher.action.recording.type.stop="Stop recording"
AdvSceneSwitcher.action.recording.type.start="Start recording"
AdvSceneSwitcher.action.recording.type.pause="Pause recording"
AdvSceneSwitcher.action.recording.type.unpause="Unpause recording"
AdvSceneSwitcher.action.recording.pause.hint="Note that depending on your recording settings you might not be able to pause recording"
AdvSceneSwitcher.action.recording.entry="{{actions}}{{pauseHint}}"
AdvSceneSwitcher.action.replay="Replay buffer"
AdvSceneSwitcher.action.replay.type.stop="Stop replay buffer"
AdvSceneSwitcher.action.replay.type.start="Start replay buffer"
AdvSceneSwitcher.action.replay.type.save="Save replay buffer"
AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.streaming="Streaming"
AdvSceneSwitcher.action.streaming.type.stop="Stop streaming"
AdvSceneSwitcher.action.streaming.type.start="Start streaming"
AdvSceneSwitcher.action.streaming.entry="{{actions}}"
AdvSceneSwitcher.action.run="Run"
AdvSceneSwitcher.action.run.entry="Run {{filePath}} {{browseButton}}"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Transition"
AdvSceneSwitcher.transitionTab.transitionOverride="Scene transitions defined in the scene switcher take priority over Transition Overrides defined per scene"
AdvSceneSwitcher.transitionTab.setTransitionBy="When changing transitions:"
AdvSceneSwitcher.transitionTab.transitionOverride="Set transition overrides"
AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="Change active transition type"
AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError="At least one option must be enabled:\n\n - Use transition overrides\n\n - Change active transition type"
AdvSceneSwitcher.transitionTab.transitionForAToB="Use transition for automated scene switch from scene A to scene B"
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>These settings <span style=\"font-style:italic;\">only</span> affect transitions caused by the scene switcher - Check out <a href=\"https://obsproject.com/forum/resources/transition-table.1174/\"><span style=\" text-decoration: underline; color:#268bd2;\">Transition Table</span></a> if you want to configure this for manual scene changes.<br/>Settings defined here take priority over transition settings configured elsewhere in the scene switcher.<br/><br/>Click the plus symbol below to add a new entry.</p></body></html>"
AdvSceneSwitcher.transitionTab.defaultTransition="Change transition if scene is active"
AdvSceneSwitcher.transitionTab.entry="Switch from {{scenes}} to {{scenes2}} using {{transitions}}"
AdvSceneSwitcher.transitionTab.entry="Switch from {{scenes}} to {{scenes2}} using {{transitions}} with a duration of {{duration}}"
AdvSceneSwitcher.transitionTab.defaultTransitionEntry="When scene {{scenes}} is active change default scene transition to {{transitions}}"
AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="Click on the plus symbol to add an entry."
AdvSceneSwitcher.transitionTab.defaultTransition.delay="Switch transition {{defTransitionDelay}} after scene change."
AdvSceneSwitcher.transitionTab.defaultTransition.delay.help="The delay is used to avoid cancelled scene switches, which can happen if the transition type is changed while a transition is still ongoing."
; Pause Scenes Tab
AdvSceneSwitcher.pauseTab.title="Pause"
@@ -186,18 +297,19 @@ AdvSceneSwitcher.sceneSequenceTab.loadSuccess="Advanced Scene Switcher settings
AdvSceneSwitcher.sceneSequenceTab.fileType="Text files (*.txt)"
AdvSceneSwitcher.sceneSequenceTab.interruptible="interruptible"
AdvSceneSwitcher.sceneSequenceTab.interruptibleHint="Other switching methods are allowed to interrupt this scene sequence"
AdvSceneSwitcher.sceneSequenceTab.entry="When {{startScenes}} is active switch to {{scenes}} after {{delay}} {{delayUnits}} using {{transitions}} {{interruptible}}"
AdvSceneSwitcher.sceneSequenceTab.entry="When {{startScenes}} is active switch to {{scenes}} after {{delay}} using {{transitions}} {{interruptible}}"
AdvSceneSwitcher.sceneSequenceTab.extendEdit="Extend Sequence"
AdvSceneSwitcher.sceneSequenceTab.extendEntry="After {{delay}} {{delayUnits}} switch to {{scenes}} using {{transitions}}"
AdvSceneSwitcher.sceneSequenceTab.extendEntry="After {{delay}} switch to {{scenes}} using {{transitions}}"
AdvSceneSwitcher.sceneSequenceTab.help="This tab will allow you to automatically switch to a different scene if a scene was active for a configured period of time.\nFor example, you could automatically cycle back and forth between two scenes automatically.\n\nClick on the highlighted plus symbol to continue."
; Audio Tab
AdvSceneSwitcher.audioTab.title="Audio"
AdvSceneSwitcher.audioTab.condition.above="above"
AdvSceneSwitcher.audioTab.condition.below="below"
AdvSceneSwitcher.audioTab.entry="When the volume of {{audioSources}} is {{condition}} {{volumeWidget}} for {{duration}} switch to {{scenes}} using {{transitions}}"
AdvSceneSwitcher.audioTab.ignoreInactiveSource="unless source is inactive"
AdvSceneSwitcher.audioTab.entry="When the volume of {{audioSources}} is {{condition}} {{volumeWidget}} for {{duration}} seconds switch to {{scenes}} using {{transitions}} {{ignoreInactiveSource}}"
AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="If multiple entries match ..."
AdvSceneSwitcher.audioTab.multiMatchfallback="... for {{duration}} switch to {{scenes}} using {{transitions}}"
AdvSceneSwitcher.audioTab.multiMatchfallback="... for {{duration}} seconds switch to {{scenes}} using {{transitions}}"
AdvSceneSwitcher.audioTab.help="This tab will allow you to switch scenes based on the volume of sources.\nFor example, you could automatically switch to a different scene if the volume of your microphone reaches a certain threshold.\n\nClick on the highlighted plus symbol to continue."
; Video Tab
@@ -208,10 +320,34 @@ AdvSceneSwitcher.videoTab.condition.match="exactly matches"
AdvSceneSwitcher.videoTab.condition.match.tooltip="An exact match requires the target and the source image to be of the same resolution.\nAdditionally every single pixel needs to match, which is why use of image formats which use compression (e.g. .JPG) is not recommended!"
AdvSceneSwitcher.videoTab.condition.differ="does not match"
AdvSceneSwitcher.videoTab.condition.hasNotChanged="has not changed"
AdvSceneSwitcher.videoTab.condition.hasChanged="has changed"
AdvSceneSwitcher.videoTab.ignoreInactiveSource="unless source is inactive"
AdvSceneSwitcher.videoTab.entry="When {{videoSources}} {{condition}} {{filePath}} {{browseButton}} for {{duration}} switch to {{scenes}} using {{transitions}} {{ignoreInactiveSource}}"
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>This tab will allow you to switch scenes based on the current video output of selected sources.<br/>Make sure to check out <a href=\"https://obsproject.com/forum/resources/pixel-match-switcher.1202/\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a> for an even better implementation of this functionality.<br/><br/> Click on the highlighted plus symbol to continue.</p></body></html>"
; Network Tab
AdvSceneSwitcher.networkTab.title="Network"
AdvSceneSwitcher.networkTab.warning="Running the server outside of a local network will allow third parties to read the active scene."
AdvSceneSwitcher.networkTab.DisabledWarning="This functionality unfortunately had to be disabled on macOS due to library incompatibilities when running the obs-websocket plugin in parallel."
AdvSceneSwitcher.networkTab.server="Start server (Sends scene switch messages to all connected clients)"
AdvSceneSwitcher.networkTab.server.port="Port"
AdvSceneSwitcher.networkTab.server.lockToIPv4="Lock server to only using IPv4"
AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Only send messages for automated scene switches"
AdvSceneSwitcher.networkTab.startFailed.message="The WebSockets server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - Error message: %2"
AdvSceneSwitcher.networkTab.server.status.currentStatus="Current status"
AdvSceneSwitcher.networkTab.server.status.notRunning="Not running"
AdvSceneSwitcher.networkTab.server.status.starting="Starting"
AdvSceneSwitcher.networkTab.server.status.running="Running"
AdvSceneSwitcher.networkTab.server.restart="Restart server"
AdvSceneSwitcher.networkTab.client="Start client (Receives scene switches messages)"
AdvSceneSwitcher.networkTab.client.address="Hostname or IP address"
AdvSceneSwitcher.networkTab.client.port="Port"
AdvSceneSwitcher.networkTab.client.status.currentStatus="Current status"
AdvSceneSwitcher.networkTab.client.status.disconnected="Disconnected"
AdvSceneSwitcher.networkTab.client.status.connecting="Connecting"
AdvSceneSwitcher.networkTab.client.status.connected="Connected"
AdvSceneSwitcher.networkTab.client.reconnect="Force reconnect"
; Scene Group Tab
AdvSceneSwitcher.sceneGroupTab.title="Scene Group"
AdvSceneSwitcher.sceneGroupTab.list="Scene Groups"

View File

@@ -168,7 +168,7 @@ AdvSceneSwitcher.sceneSequenceTab.loadTitle="从文件加载序列 ..."
AdvSceneSwitcher.sceneSequenceTab.loadFail="导入失败"
AdvSceneSwitcher.sceneSequenceTab.loadSuccess="导入失败成功"
AdvSceneSwitcher.sceneSequenceTab.fileType="文本文件 (*.txt)"
AdvSceneSwitcher.sceneSequenceTab.entry="当场景 {{startScenes}} 被激活 {{delay}} {{delayUnits}}后使用转场特效 {{transitions}} 切换到场景 {{scenes}} {{interruptible}}"
AdvSceneSwitcher.sceneSequenceTab.entry="当场景 {{startScenes}} 被激活 {{delay}} 后使用转场特效 {{transitions}} 切换到场景 {{scenes}} {{interruptible}}"
; Audio Tab
AdvSceneSwitcher.audioTab.title="音频"

1
deps/asio vendored Submodule

Submodule deps/asio added at b73dc1d2c0

1
deps/websocketpp vendored Submodule

Submodule deps/websocketpp added at c6d7e295bf

View File

@@ -161,46 +161,8 @@
<property name="title">
<string>AdvSceneSwitcher.generalTab.generalBehavior</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.cooldown</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="cooldownTime">
<property name="suffix">
<string>s</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 row="3" column="0">
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_32">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_43">
@@ -264,7 +226,7 @@
</layout>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_46">
<layout class="QHBoxLayout" name="noMatchLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
@@ -281,21 +243,29 @@
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="noMatchDelay">
<property name="suffix">
<string>s</string>
</property>
<property name="maximum">
<double>999999.989999999990687</double>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item row="4" column="0">
<item>
<layout class="QHBoxLayout" name="cooldownLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.cooldown</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_52">
<item>
<widget class="QCheckBox" name="verboseLogging">
@@ -319,7 +289,31 @@
</item>
</layout>
</item>
<item row="5" column="0">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_50">
<item>
<widget class="QCheckBox" name="saveWindowGeo">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_55">
<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>
<layout class="QHBoxLayout" name="horizontalLayout_48">
<item>
<widget class="QCheckBox" name="uiHintsDisable">
@@ -536,24 +530,456 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="macroTab">
<attribute name="title">
<string>AdvSceneSwitcher.macroTab.title</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_11" stretch="1,3">
<item>
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>AdvSceneSwitcher.macroTab.macros</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_36">
<item>
<layout class="QGridLayout" name="gridLayout_26">
<item row="0" column="0">
<widget class="QListWidget" name="macros">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sortingEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="macroHelp">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>AdvSceneSwitcher.macroTab.help</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_61">
<item>
<widget class="QPushButton" name="macroAdd">
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">addIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="macroRemove">
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">removeIconSmall</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_130">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line_46">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_131">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="macroUp">
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>../../../forms/images/up.svg</normaloff>../../../forms/images/up.svg</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string>upArrowIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="macroDown">
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>../../../forms/images/down.svg</normaloff>../../../forms/images/down.svg</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string>downArrowIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_65">
<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>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="macroEdit">
<property name="title">
<string>AdvSceneSwitcher.macroTab.edit</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_34" stretch="1,20,10">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_18" stretch="0,0,0">
<item>
<widget class="QLabel" name="label_20">
<property name="text">
<string>AdvSceneSwitcher.macroTab.name</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="macroName"/>
</item>
<item>
<spacer name="horizontalSpacer_13">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QScrollArea" name="macroConditions">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>767</width>
<height>293</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_35" stretch="10,1">
<item>
<layout class="QGridLayout" name="gridLayout_27">
<item row="0" column="0">
<widget class="QLabel" name="macroEditConditionHelp">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>AdvSceneSwitcher.macroTab.editConditionHelp</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="macroEditConditionLayout"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QPushButton" name="conditionAdd">
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">addIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="conditionRemove">
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">removeIconSmall</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_15">
<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>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QScrollArea" name="macroActions">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_3">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>767</width>
<height>145</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_33" stretch="10,1">
<item>
<layout class="QGridLayout" name="gridLayout_25">
<item row="0" column="0">
<widget class="QLabel" name="macroEditActionHelp">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>AdvSceneSwitcher.macroTab.editActionHelp</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="macroEditActionLayout"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_21">
<item>
<widget class="QPushButton" name="actionAdd">
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">addIconSmall</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="actionRemove">
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">removeIconSmall</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_14">
<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>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="transitionsTab">
<attribute name="title">
<string>AdvSceneSwitcher.transitionTab.title</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_15">
<item>
<widget class="QCheckBox" name="transitionOverridecheckBox">
<property name="text">
<string>AdvSceneSwitcher.transitionTab.transitionOverride</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_17">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
<layout class="QGridLayout" name="gridLayout_5">
<item row="1" column="1">
<widget class="QCheckBox" name="adjustActiveTransitionType">
<property name="text">
<string>AdvSceneSwitcher.transitionTab.adjustActiveTransitionType</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>AdvSceneSwitcher.transitionTab.setTransitionBy</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="transitionOverridecheckBox">
<property name="text">
<string>AdvSceneSwitcher.transitionTab.transitionOverride</string>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer_9">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="transitionsGroup">
@@ -737,6 +1163,9 @@
<string>AdvSceneSwitcher.transitionTab.defaultTransition</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_12">
<item>
<layout class="QHBoxLayout" name="defTransitionDelayLayout"/>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_10">
<item row="0" column="0">
@@ -2825,7 +3254,7 @@
</widget>
</item>
<item>
<widget class="Line" name="line">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
@@ -3269,6 +3698,184 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="networkTab">
<attribute name="title">
<string>AdvSceneSwitcher.networkTab.title</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_30">
<item>
<widget class="QLabel" name="networkTabWarningLabel">
<property name="text">
<string>AdvSceneSwitcher.networkTab.warning</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="serverSettings">
<property name="title">
<string>AdvSceneSwitcher.networkTab.server</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_31">
<item>
<layout class="QGridLayout" name="gridLayout_24">
<item row="0" column="1">
<widget class="QSpinBox" name="serverPort">
<property name="minimum">
<number>1024</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>55555</number>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>AdvSceneSwitcher.networkTab.server.port</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="lockToIPv4">
<property name="text">
<string>AdvSceneSwitcher.networkTab.server.lockToIPv4</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>AdvSceneSwitcher.networkTab.server.status.currentStatus</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="serverStatus">
<property name="text">
<string>AdvSceneSwitcher.networkTab.server.status.notRunning</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="restrictSend">
<property name="text">
<string>AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="serverRestart">
<property name="text">
<string>AdvSceneSwitcher.networkTab.server.restart</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="clientSettings">
<property name="title">
<string>AdvSceneSwitcher.networkTab.client</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_23">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout_21">
<item row="1" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>AdvSceneSwitcher.networkTab.client.port</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>AdvSceneSwitcher.networkTab.client.address</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="clientHostname">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="clientPort">
<property name="minimum">
<number>1024</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>55555</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="clientStatus">
<property name="text">
<string>AdvSceneSwitcher.networkTab.client.status.disconnected</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>AdvSceneSwitcher.networkTab.client.status.currentStatus</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="clientReconnect">
<property name="text">
<string>AdvSceneSwitcher.networkTab.client.reconnect</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="sceneGroupTab">
<attribute name="title">
<string>AdvSceneSwitcher.sceneGroupTab.title</string>

View File

@@ -11,6 +11,7 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/curl-helper.hpp"
#include "headers/utility.hpp"
#include "headers/version.h"
SwitcherData *switcher = nullptr;
@@ -60,10 +61,13 @@ void AdvSceneSwitcher::loadUI()
setupTimeTab();
setupAudioTab();
setupVideoTab();
setupNetworkTab();
setupSceneGroupTab();
setupTriggerTab();
setupMacroTab();
setTabOrder();
restoreWindowGeo();
loading = false;
}
@@ -71,28 +75,6 @@ void AdvSceneSwitcher::loadUI()
/******************************************************************************
* UI helpers
******************************************************************************/
bool AdvSceneSwitcher::DisplayMessage(QString msg, bool question)
{
if (question) {
QMessageBox::StandardButton reply;
reply = QMessageBox::question(
nullptr, "Advanced Scene Switcher", msg,
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
return true;
} else {
return false;
}
} else {
QMessageBox Msgbox;
Msgbox.setWindowTitle("Advanced Scene Switcher");
Msgbox.setText(msg);
Msgbox.exec();
}
return false;
}
void AdvSceneSwitcher::addSelectionEntry(QComboBox *sel,
const char *description,
bool selectable, const char *tooltip)
@@ -477,6 +459,11 @@ void SwitcherData::Thread()
bool match = false;
OBSWeakSource scene;
OBSWeakSource transition;
// The previous scene might have changed during the linger duration,
// if a longer transition is used than the configured check interval
bool setPrevSceneAfterLinger = false;
bool macroMatch = false;
macroSceneSwitched = false;
endTime = std::chrono::high_resolution_clock::now();
@@ -503,15 +490,16 @@ void SwitcherData::Thread()
sleep = 0;
linger = 0;
switcher->Prune();
if (switcher->stop) {
Prune();
if (stop) {
break;
}
if (checkPause()) {
continue;
}
match = checkForMatch(scene, transition, linger);
if (switcher->stop) {
match = checkForMatch(scene, transition, linger,
setPrevSceneAfterLinger, macroMatch);
if (stop) {
break;
}
checkNoMatchSwitch(match, scene, transition, sleep);
@@ -524,7 +512,7 @@ void SwitcherData::Thread()
cv.wait_for(lock, duration);
if (switcher->stop) {
if (stop) {
break;
}
@@ -534,6 +522,8 @@ void SwitcherData::Thread()
match = false;
linger = 0;
} else if (setPrevSceneAfterLinger) {
scene = previousScene;
}
}
@@ -546,8 +536,11 @@ void SwitcherData::Thread()
lock.unlock();
if (match) {
switchScene(scene, transition,
tansitionOverrideOverride);
if (macroMatch) {
runMacros();
} else {
switchScene({scene, transition, 0});
}
}
writeSceneInfoToFile();
@@ -557,12 +550,15 @@ void SwitcherData::Thread()
}
bool SwitcherData::checkForMatch(OBSWeakSource &scene,
OBSWeakSource &transition, int &linger)
OBSWeakSource &transition, int &linger,
bool &setPrevSceneAfterLinger,
bool &macroMatch)
{
bool match = false;
if (uninterruptibleSceneSequenceActive) {
checkSceneSequence(match, scene, transition, linger);
match = checkSceneSequence(scene, transition, linger,
setPrevSceneAfterLinger);
if (match) {
return match;
}
@@ -571,39 +567,46 @@ bool SwitcherData::checkForMatch(OBSWeakSource &scene,
for (int switchFuncName : functionNamesByPriority) {
switch (switchFuncName) {
case read_file_func:
checkSwitchInfoFromFile(match, scene, transition);
checkFileContent(match, scene, transition);
match = checkSwitchInfoFromFile(scene, transition) ||
checkFileContent(scene, transition);
break;
case idle_func:
checkIdleSwitch(match, scene, transition);
match = checkIdleSwitch(scene, transition);
break;
case exe_func:
checkExeSwitch(match, scene, transition);
match = checkExeSwitch(scene, transition);
break;
case screen_region_func:
checkScreenRegionSwitch(match, scene, transition);
match = checkScreenRegionSwitch(scene, transition);
break;
case window_title_func:
checkWindowTitleSwitch(match, scene, transition);
match = checkWindowTitleSwitch(scene, transition);
break;
case round_trip_func:
checkSceneSequence(match, scene, transition, linger);
match = checkSceneSequence(scene, transition, linger,
setPrevSceneAfterLinger);
break;
case media_func:
checkMediaSwitch(match, scene, transition);
match = checkMediaSwitch(scene, transition);
break;
case time_func:
checkTimeSwitch(match, scene, transition);
match = checkTimeSwitch(scene, transition);
break;
case audio_func:
checkAudioSwitch(match, scene, transition);
match = checkAudioSwitch(scene, transition);
break;
case video_func:
checkVideoSwitch(match, scene, transition);
match = checkVideoSwitch(scene, transition);
break;
case macro_func:
if (checkMacros()) {
match = true;
macroMatch = true;
}
break;
}
if (switcher->stop) {
if (stop) {
return false;
}
if (match) {
@@ -613,29 +616,33 @@ bool SwitcherData::checkForMatch(OBSWeakSource &scene,
return match;
}
void switchScene(OBSWeakSource &scene, OBSWeakSource &transition,
bool &transitionOverrideOverride)
void switchScene(sceneSwitchInfo sceneSwitch)
{
if (!scene && switcher->verbose) {
if (!sceneSwitch.scene && switcher->verbose) {
blog(LOG_INFO, "nothing to switch to");
return;
}
obs_source_t *source = obs_weak_source_get_source(scene);
obs_source_t *source = obs_weak_source_get_source(sceneSwitch.scene);
obs_source_t *currentSource = obs_frontend_get_current_scene();
if (source && source != currentSource) {
transitionData td;
setNextTransition(scene, currentSource, transition,
transitionOverrideOverride, td);
transitionData currentTransitionData;
setNextTransition(sceneSwitch, currentSource,
currentTransitionData);
obs_frontend_set_current_scene(source);
if (transitionOverrideOverride) {
restoreTransitionOverride(source, td);
if (switcher->transitionOverrideOverride) {
restoreTransitionOverride(source,
currentTransitionData);
}
if (switcher->verbose) {
blog(LOG_INFO, "switched scene");
}
if (switcher->networkConfig.ServerEnabled) {
switcher->server.sendMessage(sceneSwitch);
}
}
obs_source_release(currentSource);
obs_source_release(source);
@@ -645,19 +652,27 @@ void SwitcherData::Start()
{
if (!(th && th->isRunning())) {
stop = false;
switcher->th = new SwitcherThread();
switcher->th->start(
(QThread::Priority)switcher->threadPriority);
th = new SwitcherThread();
th->start((QThread::Priority)threadPriority);
// Will be overwritten quickly but might be useful
writeToStatusFile("Advanced Scene Switcher running");
}
if (networkConfig.ServerEnabled) {
server.start(networkConfig.ServerPort,
networkConfig.LockToIPv4);
}
if (networkConfig.ClientEnabled) {
client.connect(networkConfig.GetClientUri());
}
}
void SwitcherData::Stop()
{
if (th && th->isRunning()) {
switcher->stop = true;
stop = true;
transitionCv.notify_one();
cv.notify_one();
th->wait();
@@ -666,6 +681,9 @@ void SwitcherData::Stop()
writeToStatusFile("Advanced Scene Switcher stopped");
}
server.stop();
client.disconnect();
}
bool SwitcherData::sceneChangedDuringWait()
@@ -714,6 +732,11 @@ void handleSceneChange(SwitcherData *s)
s->checkTriggers();
s->checkDefaultSceneTransitions();
if (switcher->networkConfig.ServerEnabled &&
switcher->networkConfig.SendAll) {
switcher->server.sendMessage({ws, nullptr, 0});
}
}
void setLiveTime(SwitcherData *s)

139
src/duration-control.cpp Normal file
View File

@@ -0,0 +1,139 @@
#include "headers/duration-control.hpp"
#include "obs-module.h"
#include <sstream>
#include <iomanip>
#include <QHBoxLayout>
void Duration::Save(obs_data_t *obj, const char *secondsName,
const char *unitName)
{
obs_data_set_double(obj, secondsName, seconds);
obs_data_set_int(obj, unitName, static_cast<int>(displayUnit));
}
void Duration::Load(obs_data_t *obj, const char *secondsName,
const char *unitName)
{
seconds = obs_data_get_double(obj, secondsName);
displayUnit =
static_cast<DurationUnit>(obs_data_get_int(obj, unitName));
}
bool Duration::DurationReached()
{
if (_startTime.time_since_epoch().count() == 0) {
_startTime = std::chrono::high_resolution_clock::now();
}
auto runTime = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now() - _startTime);
return runTime.count() >= seconds * 1000;
}
void Duration::Reset()
{
_startTime = {};
}
int durationUnitToMultiplier(DurationUnit u)
{
switch (u) {
case DurationUnit::SECONDS:
return 1;
case DurationUnit::MINUTES:
return 60;
case DurationUnit::HOURS:
return 3600;
default:
break;
}
return 0;
}
std::string durationUnitToString(DurationUnit u)
{
switch (u) {
case DurationUnit::SECONDS:
return obs_module_text("AdvSceneSwitcher.unit.secends");
case DurationUnit::MINUTES:
return obs_module_text("AdvSceneSwitcher.unit.minutes");
case DurationUnit::HOURS:
return obs_module_text("AdvSceneSwitcher.unit.hours");
default:
break;
}
return "";
}
std::string Duration::ToString()
{
std::ostringstream ss;
ss << std::fixed << std::setprecision(2)
<< seconds / durationUnitToMultiplier(displayUnit) << " "
<< durationUnitToString(displayUnit);
return ss.str();
}
static void populateUnits(QComboBox *list)
{
list->addItem(obs_module_text("AdvSceneSwitcher.unit.secends"));
list->addItem(obs_module_text("AdvSceneSwitcher.unit.minutes"));
list->addItem(obs_module_text("AdvSceneSwitcher.unit.hours"));
}
DurationSelection::DurationSelection(QWidget *parent, bool showUnitSelection)
: QWidget(parent), _unitMultiplier(1)
{
_duration = new QDoubleSpinBox(parent);
_duration->setMaximum(86400); // 24 hours
_unitSelection = new QComboBox();
populateUnits(_unitSelection);
QWidget::connect(_duration, SIGNAL(valueChanged(double)), this,
SLOT(_DurationChanged(double)));
QWidget::connect(_unitSelection, SIGNAL(currentIndexChanged(int)), this,
SLOT(_UnitChanged(int)));
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(_duration);
if (showUnitSelection) {
layout->addWidget(_unitSelection);
}
setLayout(layout);
}
void DurationSelection::SetValue(double value)
{
_duration->setValue(value / _unitMultiplier);
}
void DurationSelection::SetUnit(DurationUnit u)
{
_unitSelection->setCurrentIndex(static_cast<int>(u));
}
void DurationSelection::SetDuration(Duration d)
{
SetUnit(d.displayUnit);
SetValue(d.seconds);
}
void DurationSelection::_DurationChanged(double value)
{
emit DurationChanged(value * _unitMultiplier);
}
void DurationSelection::_UnitChanged(int idx)
{
DurationUnit unit = static_cast<DurationUnit>(idx);
double prevMultiplier = _unitMultiplier;
_unitMultiplier = durationUnitToMultiplier(unit);
_duration->setValue(_duration->value() *
(prevMultiplier / _unitMultiplier));
emit UnitChanged(unit);
}

View File

@@ -4,8 +4,6 @@
#include "headers/utility.hpp"
#include "headers/version.h"
constexpr auto tab_count = 16;
QMetaObject::Connection inactivePluse;
void AdvSceneSwitcher::on_close_clicked()
@@ -61,14 +59,44 @@ void AdvSceneSwitcher::on_noMatchRandomSwitch_clicked()
ui->randomDisabledWarning->setVisible(false);
}
void AdvSceneSwitcher::on_noMatchDelay_valueChanged(double i)
void AdvSceneSwitcher::NoMatchDelayDurationChanged(double sec)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->noMatchDelay = i;
switcher->noMatchDelay.seconds = sec;
}
void AdvSceneSwitcher::NoMatchDelayUnitChanged(DurationUnit unit)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->noMatchDelay.displayUnit = unit;
}
void AdvSceneSwitcher::CooldownDurationChanged(double sec)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->cooldown.seconds = sec;
}
void AdvSceneSwitcher::CooldownUnitChanged(DurationUnit unit)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->cooldown.displayUnit = unit;
}
void AdvSceneSwitcher::on_startupBehavior_currentIndexChanged(int index)
@@ -102,16 +130,6 @@ void AdvSceneSwitcher::on_noMatchSwitchScene_currentTextChanged(
UpdateNonMatchingScene(text);
}
void AdvSceneSwitcher::on_cooldownTime_valueChanged(double i)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->cooldown = i;
}
void AdvSceneSwitcher::on_checkInterval_valueChanged(int value)
{
if (loading) {
@@ -156,6 +174,9 @@ void AdvSceneSwitcher::on_toggleStartButton_clicked()
void AdvSceneSwitcher::closeEvent(QCloseEvent *)
{
switcher->windowPos = this->pos();
switcher->windowSize = this->size();
obs_frontend_save();
}
@@ -168,6 +189,15 @@ void AdvSceneSwitcher::on_verboseLogging_stateChanged(int state)
switcher->verbose = state;
}
void AdvSceneSwitcher::on_saveWindowGeo_stateChanged(int state)
{
if (loading) {
return;
}
switcher->saveWindowGeo = state;
}
void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
{
if (loading) {
@@ -290,48 +320,54 @@ int findTabIndex(QTabWidget *tabWidget, int pos)
tabName = "generalTab";
break;
case 1:
tabName = "transitionsTab";
tabName = "macroTab";
break;
case 2:
tabName = "pauseTab";
tabName = "transitionsTab";
break;
case 3:
tabName = "windowTitleTab";
tabName = "pauseTab";
break;
case 4:
tabName = "executableTab";
tabName = "windowTitleTab";
break;
case 5:
tabName = "screenRegionTab";
tabName = "executableTab";
break;
case 6:
tabName = "mediaTab";
tabName = "screenRegionTab";
break;
case 7:
tabName = "fileTab";
tabName = "mediaTab";
break;
case 8:
tabName = "randomTab";
tabName = "fileTab";
break;
case 9:
tabName = "timeTab";
tabName = "randomTab";
break;
case 10:
tabName = "idleTab";
tabName = "timeTab";
break;
case 11:
tabName = "sceneSequenceTab";
tabName = "idleTab";
break;
case 12:
tabName = "audioTab";
tabName = "sceneSequenceTab";
break;
case 13:
tabName = "videoTab";
tabName = "audioTab";
break;
case 14:
tabName = "sceneGroupTab";
tabName = "videoTab";
break;
case 15:
tabName = "networkTab";
break;
case 16:
tabName = "sceneGroupTab";
break;
case 17:
tabName = "sceneTriggerTab";
break;
}
@@ -350,6 +386,10 @@ int findTabIndex(QTabWidget *tabWidget, int pos)
void AdvSceneSwitcher::setTabOrder()
{
if (!switcher->tabOrderValid()) {
switcher->resetTabOrder();
}
QTabBar *bar = ui->tabWidget->tabBar();
for (int i = 0; i < bar->count(); ++i) {
int curPos = findTabIndex(ui->tabWidget, switcher->tabOrder[i]);
@@ -362,6 +402,14 @@ void AdvSceneSwitcher::setTabOrder()
connect(bar, &QTabBar::tabMoved, this, &AdvSceneSwitcher::on_tabMoved);
}
void AdvSceneSwitcher::restoreWindowGeo()
{
if (switcher->saveWindowGeo) {
this->resize(switcher->windowSize);
this->move(switcher->windowPos);
}
}
void AdvSceneSwitcher::on_tabMoved(int from, int to)
{
if (loading) {
@@ -387,6 +435,7 @@ void SwitcherData::loadSettings(obs_data_t *obj)
}
loadSceneGroups(obj);
loadMacros(obj);
loadWindowTitleSwitches(obj);
loadScreenRegionSwitches(obj);
loadPauseSwitches(obj);
@@ -400,6 +449,7 @@ void SwitcherData::loadSettings(obs_data_t *obj)
loadTimeSwitches(obj);
loadAudioSwitches(obj);
loadVideoSwitches(obj);
loadNetworkSettings(obj);
loadSceneTriggers(obj);
loadGeneralSettings(obj);
loadHotkeys(obj);
@@ -411,7 +461,11 @@ void SwitcherData::saveSettings(obs_data_t *obj)
return;
}
// Needs to be loaded before any entries which might rely on scene group
// selections to be available.
saveSceneGroups(obj);
saveMacros(obj);
saveWindowTitleSwitches(obj);
saveScreenRegionSwitches(obj);
savePauseSwitches(obj);
@@ -425,6 +479,7 @@ void SwitcherData::saveSettings(obs_data_t *obj)
saveTimeSwitches(obj);
saveAudioSwitches(obj);
saveVideoSwitches(obj);
saveNetworkSwitches(obj);
saveSceneTriggers(obj);
saveGeneralSettings(obj);
saveHotkeys(obj);
@@ -433,105 +488,95 @@ void SwitcherData::saveSettings(obs_data_t *obj)
void SwitcherData::saveGeneralSettings(obs_data_t *obj)
{
obs_data_set_int(obj, "interval", switcher->interval);
obs_data_set_int(obj, "interval", interval);
std::string nonMatchingSceneName =
GetWeakSourceName(switcher->nonMatchingScene);
std::string nonMatchingSceneName = GetWeakSourceName(nonMatchingScene);
obs_data_set_string(obj, "non_matching_scene",
nonMatchingSceneName.c_str());
obs_data_set_int(obj, "switch_if_not_matching",
switcher->switchIfNotMatching);
obs_data_set_double(obj, "noMatchDelay", switcher->noMatchDelay);
obs_data_set_int(obj, "switch_if_not_matching", switchIfNotMatching);
noMatchDelay.Save(obj, "noMatchDelay", "noMatchDelayUnit");
obs_data_set_double(obj, "cooldown", switcher->cooldown);
cooldown.Save(obj, "cooldown", "cooldownUnit");
obs_data_set_bool(obj, "active", !switcher->stop);
obs_data_set_int(obj, "startup_behavior", switcher->startupBehavior);
obs_data_set_bool(obj, "active", !stop);
obs_data_set_int(obj, "startup_behavior", startupBehavior);
obs_data_set_int(obj, "autoStartEvent",
static_cast<int>(switcher->autoStartEvent));
static_cast<int>(autoStartEvent));
obs_data_set_bool(obj, "verbose", switcher->verbose);
obs_data_set_bool(obj, "disableHints", switcher->disableHints);
obs_data_set_bool(obj, "verbose", verbose);
obs_data_set_bool(obj, "disableHints", disableHints);
obs_data_set_int(obj, "priority0",
switcher->functionNamesByPriority[0]);
obs_data_set_int(obj, "priority1",
switcher->functionNamesByPriority[1]);
obs_data_set_int(obj, "priority2",
switcher->functionNamesByPriority[2]);
obs_data_set_int(obj, "priority3",
switcher->functionNamesByPriority[3]);
obs_data_set_int(obj, "priority4",
switcher->functionNamesByPriority[4]);
obs_data_set_int(obj, "priority5",
switcher->functionNamesByPriority[5]);
obs_data_set_int(obj, "priority6",
switcher->functionNamesByPriority[6]);
obs_data_set_int(obj, "priority7",
switcher->functionNamesByPriority[7]);
obs_data_set_int(obj, "priority8",
switcher->functionNamesByPriority[8]);
obs_data_set_int(obj, "priority9",
switcher->functionNamesByPriority[9]);
obs_data_set_int(obj, "priority0", functionNamesByPriority[0]);
obs_data_set_int(obj, "priority1", functionNamesByPriority[1]);
obs_data_set_int(obj, "priority2", functionNamesByPriority[2]);
obs_data_set_int(obj, "priority3", functionNamesByPriority[3]);
obs_data_set_int(obj, "priority4", functionNamesByPriority[4]);
obs_data_set_int(obj, "priority5", functionNamesByPriority[5]);
obs_data_set_int(obj, "priority6", functionNamesByPriority[6]);
obs_data_set_int(obj, "priority7", functionNamesByPriority[7]);
obs_data_set_int(obj, "priority8", functionNamesByPriority[8]);
obs_data_set_int(obj, "priority9", functionNamesByPriority[9]);
obs_data_set_int(obj, "priority10", functionNamesByPriority[10]);
obs_data_set_int(obj, "threadPriority", switcher->threadPriority);
obs_data_set_int(obj, "threadPriority", threadPriority);
// After fresh install of OBS the vector can be empty
// as save() might be called before first load()
if (tabOrder.size() < tab_count) {
resetTabOrder();
}
obs_data_set_int(obj, "generalTabPos", tabOrder[0]);
obs_data_set_int(obj, "macroTabPos", tabOrder[1]);
obs_data_set_int(obj, "transitionTabPos", tabOrder[2]);
obs_data_set_int(obj, "pauseTabPos", tabOrder[3]);
obs_data_set_int(obj, "titleTabPos", tabOrder[4]);
obs_data_set_int(obj, "exeTabPos", tabOrder[5]);
obs_data_set_int(obj, "regionTabPos", tabOrder[6]);
obs_data_set_int(obj, "mediaTabPos", tabOrder[7]);
obs_data_set_int(obj, "fileTabPos", tabOrder[8]);
obs_data_set_int(obj, "randomTabPos", tabOrder[9]);
obs_data_set_int(obj, "timeTabPos", tabOrder[10]);
obs_data_set_int(obj, "idleTabPos", tabOrder[11]);
obs_data_set_int(obj, "sequenceTabPos", tabOrder[12]);
obs_data_set_int(obj, "audioTabPos", tabOrder[13]);
obs_data_set_int(obj, "videoTabPos", tabOrder[14]);
obs_data_set_int(obj, "networkTabPos", tabOrder[15]);
obs_data_set_int(obj, "sceneGroupTabPos", tabOrder[16]);
obs_data_set_int(obj, "triggerTabPos", tabOrder[17]);
obs_data_set_int(obj, "generalTabPos", switcher->tabOrder[0]);
obs_data_set_int(obj, "transitionTabPos", switcher->tabOrder[1]);
obs_data_set_int(obj, "pauseTabPos", switcher->tabOrder[2]);
obs_data_set_int(obj, "titleTabPos", switcher->tabOrder[3]);
obs_data_set_int(obj, "exeTabPos", switcher->tabOrder[4]);
obs_data_set_int(obj, "regionTabPos", switcher->tabOrder[5]);
obs_data_set_int(obj, "mediaTabPos", switcher->tabOrder[6]);
obs_data_set_int(obj, "fileTabPos", switcher->tabOrder[7]);
obs_data_set_int(obj, "randomTabPos", switcher->tabOrder[8]);
obs_data_set_int(obj, "timeTabPos", switcher->tabOrder[9]);
obs_data_set_int(obj, "idleTabPos", switcher->tabOrder[10]);
obs_data_set_int(obj, "sequenceTabPos", switcher->tabOrder[11]);
obs_data_set_int(obj, "audioTabPos", switcher->tabOrder[12]);
obs_data_set_int(obj, "videoTabPos", switcher->tabOrder[13]);
obs_data_set_int(obj, "sceneGroupTabPos", switcher->tabOrder[14]);
obs_data_set_int(obj, "triggerTabPos", switcher->tabOrder[15]);
obs_data_set_bool(obj, "saveWindowGeo", saveWindowGeo);
obs_data_set_int(obj, "windowPosX", windowPos.x());
obs_data_set_int(obj, "windowPosY", windowPos.y());
obs_data_set_int(obj, "windowWidth", windowSize.width());
obs_data_set_int(obj, "windowHeight", windowSize.height());
}
void SwitcherData::loadGeneralSettings(obs_data_t *obj)
{
obs_data_set_default_int(obj, "interval", default_interval);
switcher->interval = obs_data_get_int(obj, "interval");
interval = obs_data_get_int(obj, "interval");
obs_data_set_default_int(obj, "switch_if_not_matching", NO_SWITCH);
switcher->switchIfNotMatching =
switchIfNotMatching =
(NoMatch)obs_data_get_int(obj, "switch_if_not_matching");
std::string nonMatchingScene =
std::string nonMatchingSceneName =
obs_data_get_string(obj, "non_matching_scene");
switcher->nonMatchingScene =
GetWeakSourceByName(nonMatchingScene.c_str());
switcher->noMatchDelay = obs_data_get_double(obj, "noMatchDelay");
nonMatchingScene = GetWeakSourceByName(nonMatchingSceneName.c_str());
noMatchDelay.Load(obj, "noMatchDelay", "noMatchDelayUnit");
switcher->cooldown = obs_data_get_double(obj, "cooldown");
cooldown.Load(obj, "cooldown", "cooldownUnit");
switcher->stop = !obs_data_get_bool(obj, "active");
switcher->startupBehavior =
stop = !obs_data_get_bool(obj, "active");
startupBehavior =
(StartupBehavior)obs_data_get_int(obj, "startup_behavior");
if (switcher->startupBehavior == START) {
switcher->stop = false;
if (startupBehavior == START) {
stop = false;
}
if (switcher->startupBehavior == STOP) {
switcher->stop = true;
if (startupBehavior == STOP) {
stop = true;
}
switcher->autoStartEvent = static_cast<AutoStartEvent>(
autoStartEvent = static_cast<AutoStartEvent>(
obs_data_get_int(obj, "autoStartEvent"));
switcher->verbose = obs_data_get_bool(obj, "verbose");
switcher->disableHints = obs_data_get_bool(obj, "disableHints");
verbose = obs_data_get_bool(obj, "verbose");
disableHints = obs_data_get_bool(obj, "disableHints");
obs_data_set_default_int(obj, "priority0", default_priority_0);
obs_data_set_default_int(obj, "priority1", default_priority_1);
@@ -543,122 +588,114 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
obs_data_set_default_int(obj, "priority7", default_priority_7);
obs_data_set_default_int(obj, "priority8", default_priority_8);
switcher->functionNamesByPriority[0] =
(obs_data_get_int(obj, "priority0"));
switcher->functionNamesByPriority[1] =
(obs_data_get_int(obj, "priority1"));
switcher->functionNamesByPriority[2] =
(obs_data_get_int(obj, "priority2"));
switcher->functionNamesByPriority[3] =
(obs_data_get_int(obj, "priority3"));
switcher->functionNamesByPriority[4] =
(obs_data_get_int(obj, "priority4"));
switcher->functionNamesByPriority[5] =
(obs_data_get_int(obj, "priority5"));
switcher->functionNamesByPriority[6] =
(obs_data_get_int(obj, "priority6"));
switcher->functionNamesByPriority[7] =
(obs_data_get_int(obj, "priority7"));
switcher->functionNamesByPriority[8] =
(obs_data_get_int(obj, "priority8"));
switcher->functionNamesByPriority[9] =
(obs_data_get_int(obj, "priority9"));
if (!switcher->prioFuncsValid()) {
switcher->functionNamesByPriority[0] = (default_priority_0);
switcher->functionNamesByPriority[1] = (default_priority_1);
switcher->functionNamesByPriority[2] = (default_priority_2);
switcher->functionNamesByPriority[3] = (default_priority_3);
switcher->functionNamesByPriority[4] = (default_priority_4);
switcher->functionNamesByPriority[5] = (default_priority_5);
switcher->functionNamesByPriority[6] = (default_priority_6);
switcher->functionNamesByPriority[7] = (default_priority_7);
switcher->functionNamesByPriority[8] = (default_priority_8);
switcher->functionNamesByPriority[9] = (default_priority_9);
functionNamesByPriority[0] = (obs_data_get_int(obj, "priority0"));
functionNamesByPriority[1] = (obs_data_get_int(obj, "priority1"));
functionNamesByPriority[2] = (obs_data_get_int(obj, "priority2"));
functionNamesByPriority[3] = (obs_data_get_int(obj, "priority3"));
functionNamesByPriority[4] = (obs_data_get_int(obj, "priority4"));
functionNamesByPriority[5] = (obs_data_get_int(obj, "priority5"));
functionNamesByPriority[6] = (obs_data_get_int(obj, "priority6"));
functionNamesByPriority[7] = (obs_data_get_int(obj, "priority7"));
functionNamesByPriority[8] = (obs_data_get_int(obj, "priority8"));
functionNamesByPriority[9] = (obs_data_get_int(obj, "priority9"));
functionNamesByPriority[10] = (obs_data_get_int(obj, "priority10"));
if (!prioFuncsValid()) {
functionNamesByPriority[0] = (default_priority_0);
functionNamesByPriority[1] = (default_priority_1);
functionNamesByPriority[2] = (default_priority_2);
functionNamesByPriority[3] = (default_priority_3);
functionNamesByPriority[4] = (default_priority_4);
functionNamesByPriority[5] = (default_priority_5);
functionNamesByPriority[6] = (default_priority_6);
functionNamesByPriority[7] = (default_priority_7);
functionNamesByPriority[8] = (default_priority_8);
functionNamesByPriority[9] = (default_priority_9);
functionNamesByPriority[10] = (default_priority_10);
}
obs_data_set_default_int(obj, "threadPriority",
QThread::NormalPriority);
switcher->threadPriority = obs_data_get_int(obj, "threadPriority");
threadPriority = obs_data_get_int(obj, "threadPriority");
obs_data_set_default_int(obj, "generalTabPos", 0);
obs_data_set_default_int(obj, "transitionTabPos", 1);
obs_data_set_default_int(obj, "pauseTabPos", 2);
obs_data_set_default_int(obj, "titleTabPos", 3);
obs_data_set_default_int(obj, "exeTabPos", 4);
obs_data_set_default_int(obj, "regionTabPos", 5);
obs_data_set_default_int(obj, "mediaTabPos", 6);
obs_data_set_default_int(obj, "fileTabPos", 7);
obs_data_set_default_int(obj, "randomTabPos", 8);
obs_data_set_default_int(obj, "timeTabPos", 9);
obs_data_set_default_int(obj, "idleTabPos", 10);
obs_data_set_default_int(obj, "sequenceTabPos", 11);
obs_data_set_default_int(obj, "audioTabPos", 12);
obs_data_set_default_int(obj, "videoTabPos", 13);
obs_data_set_default_int(obj, "sceneGroupTabPos", 14);
obs_data_set_default_int(obj, "triggerTabPos", 15);
obs_data_set_default_int(obj, "macroTabPos", 1);
obs_data_set_default_int(obj, "transitionTabPos", 2);
obs_data_set_default_int(obj, "pauseTabPos", 3);
obs_data_set_default_int(obj, "titleTabPos", 4);
obs_data_set_default_int(obj, "exeTabPos", 5);
obs_data_set_default_int(obj, "regionTabPos", 6);
obs_data_set_default_int(obj, "mediaTabPos", 7);
obs_data_set_default_int(obj, "fileTabPos", 8);
obs_data_set_default_int(obj, "randomTabPos", 9);
obs_data_set_default_int(obj, "timeTabPos", 10);
obs_data_set_default_int(obj, "idleTabPos", 11);
obs_data_set_default_int(obj, "sequenceTabPos", 12);
obs_data_set_default_int(obj, "audioTabPos", 13);
obs_data_set_default_int(obj, "videoTabPos", 14);
obs_data_set_default_int(obj, "networkTabPos", 15);
obs_data_set_default_int(obj, "sceneGroupTabPos", 16);
obs_data_set_default_int(obj, "triggerTabPos", 17);
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "generalTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "transitionTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "pauseTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "titleTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "exeTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "regionTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "mediaTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "fileTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "randomTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "timeTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "idleTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "sequenceTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "audioTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "videoTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "sceneGroupTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "triggerTabPos")));
tabOrder.clear();
tabOrder.emplace_back((int)(obs_data_get_int(obj, "generalTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "macroTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "transitionTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "pauseTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "titleTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "exeTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "regionTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "mediaTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "fileTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "randomTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "timeTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "idleTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "sequenceTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "audioTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "videoTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "networkTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "sceneGroupTabPos")));
tabOrder.emplace_back((int)(obs_data_get_int(obj, "triggerTabPos")));
if (!tabOrderValid()) {
resetTabOrder();
}
saveWindowGeo = obs_data_get_bool(obj, "saveWindowGeo");
windowPos = {(int)obs_data_get_int(obj, "windowPosX"),
(int)obs_data_get_int(obj, "windowPosY")};
windowSize = {(int)obs_data_get_int(obj, "windowWidth"),
(int)obs_data_get_int(obj, "windowHeight")};
}
bool SwitcherData::tabOrderValid()
{
auto tmp = tabOrder;
std::sort(tmp.begin(), tmp.end());
auto it = std::unique(tmp.begin(), tmp.end());
return it == tmp.end();
auto tmp = std::vector<int>(tab_count);
std::iota(tmp.begin(), tmp.end(), 0);
for (auto &p : tmp) {
auto it = std::find(tabOrder.begin(), tabOrder.end(), p);
if (it == tabOrder.end()) {
return false;
}
}
return true;
}
void SwitcherData::resetTabOrder()
{
tabOrder = std::vector<int>(tab_count);
std::iota(switcher->tabOrder.begin(), switcher->tabOrder.end(), 0);
std::iota(tabOrder.begin(), tabOrder.end(), 0);
}
void SwitcherData::checkNoMatchSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition, int &sleep)
{
if (match) {
noMatchCount = 0;
noMatchDelay.Reset();
return;
}
if ((noMatchCount * interval) / 1000.0 < noMatchDelay) {
noMatchCount++;
if (!noMatchDelay.DurationReached()) {
return;
}
@@ -668,27 +705,22 @@ void SwitcherData::checkNoMatchSwitch(bool &match, OBSWeakSource &scene,
transition = nullptr;
}
if (switchIfNotMatching == RANDOM_SWITCH) {
checkRandom(match, scene, transition, sleep);
match = checkRandom(scene, transition, sleep);
}
}
void SwitcherData::checkSwitchCooldown(bool &match)
{
if (!match || cooldown == 0.) {
if (!match) {
return;
}
auto now = std::chrono::high_resolution_clock::now();
auto timePassed = std::chrono::duration_cast<std::chrono::milliseconds>(
now - lastMatchTime);
if (timePassed.count() > cooldown * 1000) {
lastMatchTime = now;
return;
if (cooldown.DurationReached()) {
cooldown.Reset();
} else {
match = false;
vblog(LOG_INFO, "cooldown active - ignoring match");
}
match = false;
vblog(LOG_INFO, "cooldown active - ignoring match");
}
void populateStartupBehavior(QComboBox *cb)
@@ -744,16 +776,32 @@ void AdvSceneSwitcher::setupGeneralTab()
}
ui->noMatchSwitchScene->setCurrentText(
GetWeakSourceName(switcher->nonMatchingScene).c_str());
ui->noMatchDelay->setValue(switcher->noMatchDelay);
ui->noMatchDelay->setToolTip(obs_module_text(
DurationSelection *noMatchDelay = new DurationSelection();
noMatchDelay->SetDuration(switcher->noMatchDelay);
noMatchDelay->setToolTip(obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMetDelayTooltip"));
ui->noMatchLayout->addWidget(noMatchDelay);
QWidget::connect(noMatchDelay, SIGNAL(DurationChanged(double)), this,
SLOT(NoMatchDelayDurationChanged(double)));
QWidget::connect(noMatchDelay, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(NoMatchDelayUnitChanged(DurationUnit)));
ui->checkInterval->setValue(switcher->interval);
ui->cooldownTime->setValue(switcher->cooldown);
ui->cooldownTime->setToolTip(obs_module_text(
DurationSelection *cooldownTime = new DurationSelection();
cooldownTime->SetDuration(switcher->cooldown);
cooldownTime->setToolTip(obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint"));
ui->cooldownLayout->addWidget(cooldownTime);
ui->cooldownLayout->addStretch();
QWidget::connect(cooldownTime, SIGNAL(DurationChanged(double)), this,
SLOT(CooldownDurationChanged(double)));
QWidget::connect(cooldownTime, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(CooldownUnitChanged(DurationUnit)));
ui->verboseLogging->setChecked(switcher->verbose);
ui->saveWindowGeo->setChecked(switcher->saveWindowGeo);
ui->uiHintsDisable->setChecked(switcher->disableHints);
for (int p : switcher->functionNamesByPriority) {
@@ -799,6 +847,10 @@ void AdvSceneSwitcher::setupGeneralTab()
s = obs_module_text(
"AdvSceneSwitcher.generalTab.priority.video");
break;
case macro_func:
s = obs_module_text(
"AdvSceneSwitcher.generalTab.priority.macro");
break;
}
QString text(s.c_str());
QListWidgetItem *item =

View File

@@ -8,7 +8,7 @@
#define blog(level, msg, ...) blog(level, "[adv-ss] " msg, ##__VA_ARGS__)
#define vblog(level, msg, ...) \
if (verbose) { \
if (switcher->verbose) { \
blog(level, msg, ##__VA_ARGS__); \
}
@@ -43,6 +43,10 @@ public:
void OpenSequenceExtendEdit(SequenceWidget *sw);
void SetEditSceneGroup(SceneGroup &sg);
bool addNewMacro(std::string &name);
Macro *getSelectedMacro();
void SetEditMacro(Macro &m);
void loadUI();
void setupGeneralTab();
void setupTitleTab();
@@ -60,9 +64,11 @@ public:
void setupSceneGroupTab();
void setupTriggerTab();
void setupVideoTab();
void setupNetworkTab();
void setupMacroTab();
void setTabOrder();
void restoreWindowGeo();
static bool DisplayMessage(QString msg, bool question = false);
static void AskBackup(obs_data_t *obj);
static void addSelectionEntry(QComboBox *sel, const char *description,
@@ -113,8 +119,10 @@ public slots:
void on_noMatchDontSwitch_clicked();
void on_noMatchSwitch_clicked();
void on_noMatchRandomSwitch_clicked();
void on_noMatchDelay_valueChanged(double i);
void on_cooldownTime_valueChanged(double i);
void NoMatchDelayDurationChanged(double);
void NoMatchDelayUnitChanged(DurationUnit);
void CooldownDurationChanged(double);
void CooldownUnitChanged(DurationUnit);
void on_startupBehavior_currentIndexChanged(int index);
void on_autoStartEvent_currentIndexChanged(int index);
void on_noMatchSwitchScene_currentTextChanged(const QString &text);
@@ -123,6 +131,19 @@ public slots:
void on_tabMoved(int from, int to);
void on_tabWidget_currentChanged(int index);
void on_macroAdd_clicked();
void on_macroRemove_clicked();
void on_macroUp_clicked();
void on_macroDown_clicked();
void on_macroName_editingFinished();
void on_macros_currentRowChanged(int idx);
void on_conditionAdd_clicked();
void on_conditionRemove_clicked();
void on_actionAdd_clicked();
void on_actionRemove_clicked();
void showMacroContextMenu(const QPoint &);
void copyMacro();
void on_screenRegionSwitches_currentRowChanged(int idx);
void on_showFrame_clicked();
void on_screenRegionAdd_clicked();
@@ -149,6 +170,7 @@ public slots:
void on_sceneSequenceSwitches_itemDoubleClicked(QListWidgetItem *item);
void on_verboseLogging_stateChanged(int state);
void on_saveWindowGeo_stateChanged(int state);
void on_uiHintsDisable_stateChanged(int state);
void on_exportSettings_clicked();
@@ -163,6 +185,8 @@ public slots:
void on_defaultTransitionsUp_clicked();
void on_defaultTransitionsDown_clicked();
void on_transitionOverridecheckBox_stateChanged(int state);
void on_adjustActiveTransitionType_stateChanged(int state);
void defTransitionDelayValueChanged(int value);
void on_browseButton_clicked();
void on_readFileCheckBox_stateChanged(int state);
@@ -211,6 +235,18 @@ public slots:
void on_videoDown_clicked();
void on_getScreenshot_clicked();
void on_serverSettings_toggled(bool on);
void on_serverPort_valueChanged(int value);
void on_lockToIPv4_stateChanged(int state);
void on_serverRestart_clicked();
void updateServerStatus();
void on_clientSettings_toggled(bool on);
void on_clientHostname_textChanged(const QString &text);
void on_clientPort_valueChanged(int value);
void on_restrictSend_stateChanged(int state);
void on_clientReconnect_clicked();
void updateClientStatus();
void on_sceneGroupAdd_clicked();
void on_sceneGroupRemove_clicked();
void on_sceneGroupUp_clicked();
@@ -269,14 +305,11 @@ bool isInFocus(const QString &executable);
* Sceneswitch helper
******************************************************************************/
void setNextTransition(OBSWeakSource &targetScene, obs_source_t *currentSource,
OBSWeakSource &transition,
bool &transitionOverrideOverride, transitionData &td);
void overwriteTransitionOverride(obs_weak_source_t *sceneWs,
obs_source_t *transition, transitionData &td);
void setNextTransition(sceneSwitchInfo &ssi, obs_source_t *currentSource,
transitionData &td);
void overwriteTransitionOverride(sceneSwitchInfo ssi, transitionData &td);
void restoreTransitionOverride(obs_source_t *scene, transitionData td);
void switchScene(OBSWeakSource &scene, OBSWeakSource &transition,
bool &transitionOverrideOverride);
void switchScene(sceneSwitchInfo ssi);
/******************************************************************************
* Main SwitcherData

View File

@@ -0,0 +1,55 @@
#pragma once
#include <QWidget>
#include <QDoubleSpinBox>
#include <QComboBox>
#include <chrono>
#include "obs-data.h"
enum class DurationUnit {
SECONDS,
MINUTES,
HOURS,
};
class Duration {
public:
void Save(obs_data_t *obj, const char *secondsName = "seconds",
const char *unitName = "displayUnit");
void Load(obs_data_t *obj, const char *secondsName = "seconds",
const char *unitName = "displayUnit");
bool DurationReached();
void Reset();
std::string ToString();
double seconds = 0.;
// only used for UI
DurationUnit displayUnit = DurationUnit::SECONDS;
private:
std::chrono::high_resolution_clock::time_point _startTime;
};
class DurationSelection : public QWidget {
Q_OBJECT
public:
DurationSelection(QWidget *parent = nullptr,
bool showUnitSelection = true);
void SetValue(double value);
void SetUnit(DurationUnit u);
void SetDuration(Duration d);
private slots:
void _DurationChanged(double value);
void _UnitChanged(int idx);
signals:
void DurationChanged(double value); // always reutrn value in seconds
void UnitChanged(DurationUnit u);
private:
QDoubleSpinBox *_duration;
QComboBox *_unitSelection;
double _unitMultiplier;
};

View File

@@ -0,0 +1,63 @@
#pragma once
#include <QSpinBox>
#include "macro-action-edit.hpp"
enum class AudioAction {
MUTE,
UNMUTE,
SOURCE_VOLUME,
MASTER_VOLUME,
};
class MacroActionAudio : public MacroAction {
public:
bool PerformAction();
void LogAction();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroAction> Create()
{
return std::make_shared<MacroActionAudio>();
}
OBSWeakSource _audioSource;
AudioAction _action = AudioAction::MUTE;
int _volume = 0;
private:
static bool _registered;
static const int id;
};
class MacroActionAudioEdit : public QWidget {
Q_OBJECT
public:
MacroActionAudioEdit(
QWidget *parent,
std::shared_ptr<MacroActionAudio> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionAudioEdit(
parent,
std::dynamic_pointer_cast<MacroActionAudio>(action));
}
private slots:
void SourceChanged(const QString &text);
void ActionChanged(int value);
void VolumeChanged(int value);
protected:
QComboBox *_audioSources;
QComboBox *_actions;
QSpinBox *_volumePercent;
std::shared_ptr<MacroActionAudio> _entryData;
private:
QHBoxLayout *_mainLayout;
bool _loading = true;
};

View File

@@ -0,0 +1,54 @@
#pragma once
#include <QWidget>
#include <QComboBox>
#include <QVBoxLayout>
#include <QGroupBox>
#include <deque>
#include "macro.hpp"
#include "section.hpp"
struct MacroActionInfo {
using TCreateMethod = std::shared_ptr<MacroAction> (*)();
using TCreateWidgetMethod = QWidget *(*)(QWidget *parent,
std::shared_ptr<MacroAction>);
TCreateMethod _createFunc;
TCreateWidgetMethod _createWidgetFunc;
std::string _name;
};
class MacroActionFactory {
public:
MacroActionFactory() = delete;
static bool Register(int id, MacroActionInfo);
static std::shared_ptr<MacroAction> Create(const int id);
static QWidget *CreateWidget(const int id, QWidget *parent,
std::shared_ptr<MacroAction> action);
static auto GetActionTypes() { return _methods; }
private:
static std::map<int, MacroActionInfo> _methods;
};
class MacroActionEdit : public QWidget {
Q_OBJECT
public:
MacroActionEdit(QWidget *parent = nullptr,
std::shared_ptr<MacroAction> * = nullptr, int type = 0,
bool startCollapsed = false);
void UpdateEntryData(int type);
void Collapse(bool collapsed);
private slots:
void ActionSelectionChanged(int idx);
protected:
QComboBox *_actionSelection;
Section *_section;
std::shared_ptr<MacroAction> *_entryData;
private:
bool _loading = true;
};

View File

@@ -0,0 +1,59 @@
#pragma once
#include <QDoubleSpinBox>
#include <QLabel>
#include "macro-action-edit.hpp"
enum class RecordAction {
STOP,
START,
PAUSE,
UNPAUSE,
};
class MacroActionRecord : public MacroAction {
public:
bool PerformAction();
void LogAction();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroAction> Create()
{
return std::make_shared<MacroActionRecord>();
}
RecordAction _action = RecordAction::STOP;
private:
static bool _registered;
static const int id;
};
class MacroActionRecordEdit : public QWidget {
Q_OBJECT
public:
MacroActionRecordEdit(
QWidget *parent,
std::shared_ptr<MacroActionRecord> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionRecordEdit(
parent,
std::dynamic_pointer_cast<MacroActionRecord>(action));
}
private slots:
void ActionChanged(int value);
protected:
QComboBox *_actions;
QLabel *_pauseHint;
std::shared_ptr<MacroActionRecord> _entryData;
private:
QHBoxLayout *_mainLayout;
bool _loading = true;
};

View File

@@ -0,0 +1,66 @@
#pragma once
#include <QDoubleSpinBox>
#include "macro-action-edit.hpp"
#include "duration-control.hpp"
enum class ReplayBufferAction {
STOP,
START,
SAVE,
};
class MacroActionReplayBuffer : public MacroAction {
public:
bool PerformAction();
void LogAction();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroAction> Create()
{
return std::make_shared<MacroActionReplayBuffer>();
}
ReplayBufferAction _action = ReplayBufferAction::STOP;
private:
// Add artifical delay before trying to save the replay buffer again.
//
// Continiously calling obs_frontend_replay_buffer_save() does not
// result in any output actually being written.
// OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED also seems to be sent before
// any data is written.
Duration _duration;
static bool _registered;
static const int id;
};
class MacroActionReplayBufferEdit : public QWidget {
Q_OBJECT
public:
MacroActionReplayBufferEdit(
QWidget *parent,
std::shared_ptr<MacroActionReplayBuffer> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionReplayBufferEdit(
parent,
std::dynamic_pointer_cast<MacroActionReplayBuffer>(
action));
}
private slots:
void ActionChanged(int value);
protected:
QComboBox *_actions;
std::shared_ptr<MacroActionReplayBuffer> _entryData;
private:
QHBoxLayout *_mainLayout;
bool _loading = true;
};

View File

@@ -0,0 +1,52 @@
#pragma once
#include "macro-action-edit.hpp"
#include <QLineEdit>
#include <QPushButton>
class MacroActionRun : public MacroAction {
public:
bool PerformAction();
void LogAction();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroAction> Create()
{
return std::make_shared<MacroActionRun>();
}
std::string _path = obs_module_text("AdvSceneSwitcher.enterPath");
private:
static bool _registered;
static const int id;
};
class MacroActionRunEdit : public QWidget {
Q_OBJECT
public:
MacroActionRunEdit(QWidget *parent,
std::shared_ptr<MacroActionRun> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionRunEdit(
parent,
std::dynamic_pointer_cast<MacroActionRun>(action));
}
private slots:
void FilePathChanged();
void BrowseButtonClicked();
protected:
std::shared_ptr<MacroActionRun> _entryData;
private:
QLineEdit *_filePath;
QPushButton *_browseButton;
bool _loading = true;
};

View File

@@ -0,0 +1,60 @@
#pragma once
#include <QDoubleSpinBox>
#include "macro-action-edit.hpp"
#include "duration-control.hpp"
enum class StreamAction {
STOP,
START,
};
class MacroActionStream : public MacroAction {
public:
bool PerformAction();
void LogAction();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroAction> Create()
{
return std::make_shared<MacroActionStream>();
}
StreamAction _action = StreamAction::STOP;
private:
// Acts as a safeguard for misconfigured streaming setups leading to an
// endless error spam.
Duration _retryCooldown;
static bool _registered;
static const int id;
};
class MacroActionStreamEdit : public QWidget {
Q_OBJECT
public:
MacroActionStreamEdit(
QWidget *parent,
std::shared_ptr<MacroActionStream> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionStreamEdit(
parent,
std::dynamic_pointer_cast<MacroActionStream>(action));
}
private slots:
void ActionChanged(int value);
protected:
QComboBox *_actions;
std::shared_ptr<MacroActionStream> _entryData;
private:
QHBoxLayout *_mainLayout;
bool _loading = true;
};

View File

@@ -0,0 +1,49 @@
#pragma once
#include "macro-action-edit.hpp"
#include "switch-generic.hpp"
#include "duration-control.hpp"
class MacroActionSwitchScene : public MacroAction, public SceneSwitcherEntry {
public:
bool PerformAction();
void LogAction();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroAction> Create()
{
return std::make_shared<MacroActionSwitchScene>();
}
Duration _duration;
private:
const char *getType() { return "MacroActionSwitchScene"; }
static bool _registered;
static const int id;
};
class MacroActionSwitchSceneEdit : public SwitchWidget {
Q_OBJECT
public:
MacroActionSwitchSceneEdit(
QWidget *parent,
std::shared_ptr<MacroActionSwitchScene> entryData = nullptr);
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionSwitchSceneEdit(
parent,
std::dynamic_pointer_cast<MacroActionSwitchScene>(
action));
}
private slots:
void DurationChanged(double seconds);
protected:
DurationSelection *_duration;
std::shared_ptr<MacroActionSwitchScene> _entryData;
};

View File

@@ -0,0 +1,64 @@
#pragma once
#include <QDoubleSpinBox>
#include "macro-action-edit.hpp"
#include "duration-control.hpp"
enum class WaitType {
FIXED,
RANDOM,
};
class MacroActionWait : public MacroAction {
public:
bool PerformAction();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroAction> Create()
{
return std::make_shared<MacroActionWait>();
}
Duration _duration;
Duration _duration2;
WaitType _waitType = WaitType::FIXED;
private:
static bool _registered;
static const int id;
};
class MacroActionWaitEdit : public QWidget {
Q_OBJECT
public:
MacroActionWaitEdit(
QWidget *parent,
std::shared_ptr<MacroActionWait> entryData = nullptr);
void UpdateEntryData();
void SetupFixedDurationEdit();
void SetupRandomDurationEdit();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionWaitEdit(
parent,
std::dynamic_pointer_cast<MacroActionWait>(action));
}
private slots:
void DurationChanged(double value);
void DurationUnitChanged(DurationUnit unit);
void Duration2Changed(double value);
void Duration2UnitChanged(DurationUnit unit);
void TypeChanged(int value);
protected:
DurationSelection *_duration;
DurationSelection *_duration2;
QComboBox *_waitType;
std::shared_ptr<MacroActionWait> _entryData;
private:
QHBoxLayout *_mainLayout;
bool _loading = true;
};

View File

@@ -0,0 +1,78 @@
#pragma once
#include "macro.hpp"
#include "volume-control.hpp"
#include <limits>
#include <QWidget>
#include <QComboBox>
#include <chrono>
#include "duration-control.hpp"
enum class AudioCondition {
ABOVE,
BELOW,
};
class MacroConditionAudio : public MacroCondition {
public:
~MacroConditionAudio();
bool CheckCondition();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroCondition> Create()
{
return std::make_shared<MacroConditionAudio>();
}
static void SetVolumeLevel(void *data,
const float magnitude[MAX_AUDIO_CHANNELS],
const float peak[MAX_AUDIO_CHANNELS],
const float inputPeak[MAX_AUDIO_CHANNELS]);
void ResetVolmeter();
OBSWeakSource _audioSource;
int _volume = 0;
AudioCondition _condition = AudioCondition::ABOVE;
Duration _duration;
obs_volmeter_t *_volmeter = nullptr;
private:
float _peak = -std::numeric_limits<float>::infinity();
static bool _registered;
static const int id;
};
class MacroConditionAudioEdit : public QWidget {
Q_OBJECT
public:
MacroConditionAudioEdit(
QWidget *parent,
std::shared_ptr<MacroConditionAudio> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionAudioEdit(
parent,
std::dynamic_pointer_cast<MacroConditionAudio>(cond));
}
void UpdateVolmeterSource();
private slots:
void SourceChanged(const QString &text);
void VolumeThresholdChanged(int vol);
void ConditionChanged(int cond);
void DurationChanged(double seconds);
protected:
QComboBox *_audioSources;
QComboBox *_condition;
QSpinBox *_volume;
DurationSelection *_duration;
VolControl *_volMeter = nullptr;
std::shared_ptr<MacroConditionAudio> _entryData;
private:
bool _loading = true;
};

View File

@@ -0,0 +1,59 @@
#pragma once
#include "advanced-scene-switcher.hpp"
#include "macro.hpp"
#include "macro-condition-scene.hpp"
#include "section.hpp"
#include "utility.hpp"
#include <QGroupBox>
struct MacroConditionInfo {
using TCreateMethod = std::shared_ptr<MacroCondition> (*)();
using TCreateWidgetMethod =
QWidget *(*)(QWidget *parent, std::shared_ptr<MacroCondition>);
TCreateMethod _createFunc;
TCreateWidgetMethod _createWidgetFunc;
std::string _name;
};
class MacroConditionFactory {
public:
MacroConditionFactory() = delete;
static bool Register(int id, MacroConditionInfo);
static std::shared_ptr<MacroCondition> Create(const int id);
static QWidget *CreateWidget(const int id, QWidget *parent,
std::shared_ptr<MacroCondition>);
static auto GetConditionTypes() { return _methods; }
private:
static std::map<int, MacroConditionInfo> _methods;
};
class MacroConditionEdit : public QWidget {
Q_OBJECT
public:
MacroConditionEdit(QWidget *parent = nullptr,
std::shared_ptr<MacroCondition> * = nullptr,
int type = 0, bool root = true,
bool startCollapsed = false);
bool IsRootNode();
void UpdateEntryData(int type);
void Collapse(bool collapsed);
private slots:
void LogicSelectionChanged(int idx);
void ConditionSelectionChanged(int idx);
protected:
QComboBox *_logicSelection;
QComboBox *_conditionSelection;
Section *_section;
std::shared_ptr<MacroCondition> *_entryData;
private:
bool _isRoot = true;
bool _loading = true;
};

View File

@@ -0,0 +1,82 @@
#pragma once
#include "macro.hpp"
#include <QWidget>
#include <QComboBox>
#include <QDateTime>
#include <QLineEdit>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QCheckBox>
enum class FileType {
LOCAL,
REMOTE,
};
class MacroConditionFile : public MacroCondition {
public:
bool CheckCondition();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroCondition> Create()
{
return std::make_shared<MacroConditionFile>();
}
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
std::string _text = obs_module_text("AdvSceneSwitcher.enterText");
FileType _fileType = FileType::LOCAL;
bool _useRegex = false;
bool _useTime = false;
bool _onlyMatchIfChanged = false;
private:
bool matchFileContent(QString &filedata);
bool checkRemoteFileContent();
bool checkLocalFileContent();
QDateTime _lastMod;
size_t _lastHash = 0;
static bool _registered;
static const int id;
};
class MacroConditionFileEdit : public QWidget {
Q_OBJECT
public:
MacroConditionFileEdit(
QWidget *parent,
std::shared_ptr<MacroConditionFile> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionFileEdit(
parent,
std::dynamic_pointer_cast<MacroConditionFile>(cond));
}
private slots:
void FileTypeChanged(int index);
void FilePathChanged();
void BrowseButtonClicked();
void MatchTextChanged();
void UseRegexChanged(int state);
void CheckModificationDateChanged(int state);
void OnlyMatchIfChangedChanged(int state);
protected:
QComboBox *_fileType;
QLineEdit *_filePath;
QPushButton *_browseButton;
QPlainTextEdit *_matchText;
QCheckBox *_useRegex;
QCheckBox *_checkModificationDate;
QCheckBox *_checkFileContent;
std::shared_ptr<MacroConditionFile> _entryData;
private:
bool _loading = true;
};

View File

@@ -0,0 +1,51 @@
#pragma once
#include "macro.hpp"
#include <QWidget>
#include <QComboBox>
#include "duration-control.hpp"
class MacroConditionIdle : public MacroCondition {
public:
bool CheckCondition();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroCondition> Create()
{
return std::make_shared<MacroConditionIdle>();
}
Duration _duration;
private:
static bool _registered;
static const int id;
};
class MacroConditionIdleEdit : public QWidget {
Q_OBJECT
public:
MacroConditionIdleEdit(
QWidget *parent,
std::shared_ptr<MacroConditionIdle> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionIdleEdit(
parent,
std::dynamic_pointer_cast<MacroConditionIdle>(cond));
}
private slots:
void DurationChanged(double seconds);
void DurationUnitChanged(DurationUnit unit);
protected:
DurationSelection *_duration;
std::shared_ptr<MacroConditionIdle> _entryData;
private:
bool _loading = true;
};

View File

@@ -0,0 +1,103 @@
#pragma once
#include "macro.hpp"
#include <limits>
#include <QWidget>
#include <QComboBox>
#include "duration-control.hpp"
enum class MediaTimeRestriction {
TIME_RESTRICTION_NONE,
TIME_RESTRICTION_SHORTER,
TIME_RESTRICTION_LONGER,
TIME_RESTRICTION_REMAINING_SHORTER,
TIME_RESTRICTION_REMAINING_LONGER,
};
constexpr auto custom_media_states_offset = 100;
enum class MediaState {
// OBS's internal states
OBS_MEDIA_STATE_NONE,
OBS_MEDIA_STATE_PLAYING,
OBS_MEDIA_STATE_OPENING,
OBS_MEDIA_STATE_BUFFERING,
OBS_MEDIA_STATE_PAUSED,
OBS_MEDIA_STATE_STOPPED,
OBS_MEDIA_STATE_ENDED,
OBS_MEDIA_STATE_ERROR,
// Just a marker
LAST_OBS_MEDIA_STATE,
// states added for use in the plugin
PLAYED_TO_END = custom_media_states_offset,
ANY,
};
class MacroConditionMedia : public MacroCondition {
public:
~MacroConditionMedia();
bool CheckCondition();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroCondition> Create()
{
return std::make_shared<MacroConditionMedia>();
}
void ClearSignalHandler();
void ResetSignalHandler();
static void MediaStopped(void *data, calldata_t *);
static void MediaEnded(void *data, calldata_t *);
OBSWeakSource _source = nullptr;
MediaState _state = MediaState::OBS_MEDIA_STATE_NONE;
MediaTimeRestriction _restriction =
MediaTimeRestriction::TIME_RESTRICTION_NONE;
Duration _time;
private:
std::atomic_bool _stopped = {false};
std::atomic_bool _ended = {false};
// Trigger scene change only once even if media state might trigger repeatedly
bool _alreadyMatched = false;
// Workaround to enable use of "ended" to specify end of VLC playlist
bool _previousStateEnded = false;
bool _playedToEnd = false;
static bool _registered;
static const int id;
};
class MacroConditionMediaEdit : public QWidget {
Q_OBJECT
public:
MacroConditionMediaEdit(
QWidget *parent,
std::shared_ptr<MacroConditionMedia> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionMediaEdit(
parent,
std::dynamic_pointer_cast<MacroConditionMedia>(cond));
}
private slots:
void SourceChanged(const QString &text);
void StateChanged(int index);
void TimeRestrictionChanged(int index);
void TimeChanged(double seconds);
void TimeUnitChanged(DurationUnit unit);
protected:
QComboBox *_mediaSources;
QComboBox *_states;
QComboBox *_timeRestrictions;
DurationSelection *_time;
std::shared_ptr<MacroConditionMedia> _entryData;
private:
bool _loading = true;
};

View File

@@ -0,0 +1,54 @@
#pragma once
#include "macro.hpp"
#include <QWidget>
#include <QComboBox>
enum class PluginStateCondition {
SCENESWITCHED,
};
class MacroConditionPluginState : public MacroCondition {
public:
bool CheckCondition();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroCondition> Create()
{
return std::make_shared<MacroConditionPluginState>();
}
PluginStateCondition _condition = PluginStateCondition::SCENESWITCHED;
private:
static bool _registered;
static const int id;
};
class MacroConditionPluginStateEdit : public QWidget {
Q_OBJECT
public:
MacroConditionPluginStateEdit(
QWidget *parent,
std::shared_ptr<MacroConditionPluginState> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionPluginStateEdit(
parent,
std::dynamic_pointer_cast<MacroConditionPluginState>(
cond));
}
private slots:
void ConditionChanged(int cond);
protected:
QComboBox *_condition;
std::shared_ptr<MacroConditionPluginState> _entryData;
private:
bool _loading = true;
};

View File

@@ -0,0 +1,52 @@
#pragma once
#include "macro.hpp"
#include <QWidget>
#include <QComboBox>
class MacroConditionProcess : public MacroCondition {
public:
bool CheckCondition();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroCondition> Create()
{
return std::make_shared<MacroConditionProcess>();
}
std::string _process;
bool _focus = true;
private:
static bool _registered;
static const int id;
};
class MacroConditionProcessEdit : public QWidget {
Q_OBJECT
public:
MacroConditionProcessEdit(
QWidget *parent,
std::shared_ptr<MacroConditionProcess> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionProcessEdit(
parent,
std::dynamic_pointer_cast<MacroConditionProcess>(cond));
}
private slots:
void ProcessChanged(const QString &text);
void FocusChanged(int state);
protected:
QComboBox *_processSelection;
QCheckBox *_focused;
std::shared_ptr<MacroConditionProcess> _entryData;
private:
bool _loading = true;
};

View File

@@ -0,0 +1,60 @@
#pragma once
#include "macro.hpp"
#include <QWidget>
#include <QComboBox>
#include "duration-control.hpp"
enum class RecordState {
STOP,
PAUSE,
START,
};
class MacroConditionRecord : public MacroCondition {
public:
bool CheckCondition();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroCondition> Create()
{
return std::make_shared<MacroConditionRecord>();
}
RecordState _recordState;
Duration _duration;
private:
static bool _registered;
static const int id;
};
class MacroConditionRecordEdit : public QWidget {
Q_OBJECT
public:
MacroConditionRecordEdit(
QWidget *parent,
std::shared_ptr<MacroConditionRecord> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionRecordEdit(
parent,
std::dynamic_pointer_cast<MacroConditionRecord>(cond));
}
private slots:
void StateChanged(int value);
void DurationChanged(double seconds);
void DurationUnitChanged(DurationUnit unit);
protected:
QComboBox *_recordState;
DurationSelection *_duration;
std::shared_ptr<MacroConditionRecord> _entryData;
private:
bool _loading = true;
};

View File

@@ -0,0 +1,55 @@
#pragma once
#include "macro.hpp"
#include <QWidget>
#include <QComboBox>
class MacroConditionRegion : public MacroCondition {
public:
bool CheckCondition();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroCondition> Create()
{
return std::make_shared<MacroConditionRegion>();
}
int _minX = 0, _minY = 0, _maxX = 0, _maxY = 0;
private:
static bool _registered;
static const int id;
};
class MacroConditionRegionEdit : public QWidget {
Q_OBJECT
public:
MacroConditionRegionEdit(
QWidget *parent,
std::shared_ptr<MacroConditionRegion> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionRegionEdit(
parent,
std::dynamic_pointer_cast<MacroConditionRegion>(cond));
}
private slots:
void MinXChanged(int pos);
void MinYChanged(int pos);
void MaxXChanged(int pos);
void MaxYChanged(int pos);
protected:
QSpinBox *_minX;
QSpinBox *_minY;
QSpinBox *_maxX;
QSpinBox *_maxY;
std::shared_ptr<MacroConditionRegion> _entryData;
private:
bool _loading = true;
};

View File

@@ -0,0 +1,62 @@
#pragma once
#include "macro.hpp"
#include <QWidget>
#include <QComboBox>
#include "duration-control.hpp"
enum class SceneType {
CURRENT,
PREVIOUS,
};
class MacroConditionScene : public MacroCondition {
public:
bool CheckCondition();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroCondition> Create()
{
return std::make_shared<MacroConditionScene>();
}
OBSWeakSource _scene;
SceneType _type;
Duration _duration;
private:
static bool _registered;
static const int id;
};
class MacroConditionSceneEdit : public QWidget {
Q_OBJECT
public:
MacroConditionSceneEdit(
QWidget *parent,
std::shared_ptr<MacroConditionScene> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionSceneEdit(
parent,
std::dynamic_pointer_cast<MacroConditionScene>(cond));
}
private slots:
void SceneChanged(const QString &text);
void TypeChanged(int value);
void DurationChanged(double seconds);
void DurationUnitChanged(DurationUnit unit);
protected:
QComboBox *_sceneSelection;
QComboBox *_sceneType;
DurationSelection *_duration;
std::shared_ptr<MacroConditionScene> _entryData;
private:
bool _loading = true;
};

View File

@@ -0,0 +1,59 @@
#pragma once
#include "macro.hpp"
#include <QWidget>
#include <QComboBox>
#include "duration-control.hpp"
enum class StreamState {
STOP,
START,
};
class MacroConditionStream : public MacroCondition {
public:
bool CheckCondition();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroCondition> Create()
{
return std::make_shared<MacroConditionStream>();
}
StreamState _streamState;
Duration _duration;
private:
static bool _registered;
static const int id;
};
class MacroConditionStreamEdit : public QWidget {
Q_OBJECT
public:
MacroConditionStreamEdit(
QWidget *parent,
std::shared_ptr<MacroConditionStream> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionStreamEdit(
parent,
std::dynamic_pointer_cast<MacroConditionStream>(cond));
}
private slots:
void StateChanged(int value);
void DurationChanged(double seconds);
void DurationUnitChanged(DurationUnit unit);
protected:
QComboBox *_streamState;
DurationSelection *_duration;
std::shared_ptr<MacroConditionStream> _entryData;
private:
bool _loading = true;
};

View File

@@ -0,0 +1,82 @@
#pragma once
#include "macro.hpp"
#include <QWidget>
#include <QComboBox>
#include <chrono>
#include "duration-control.hpp"
#include "screenshot-helper.hpp"
enum class VideoCondition {
MATCH,
DIFFER,
HAS_NOT_CHANGED,
HAS_CHANGED,
NO_IMAGE,
};
class MacroConditionVideo : public MacroCondition {
public:
bool CheckCondition();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
QImage GetMatchImage() { return _matchImage; };
static std::shared_ptr<MacroCondition> Create()
{
return std::make_shared<MacroConditionVideo>();
}
void GetScreenshot();
bool LoadImageFromFile();
OBSWeakSource _videoSource;
VideoCondition _condition = VideoCondition::MATCH;
Duration _duration;
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
private:
bool Compare();
std::unique_ptr<AdvSSScreenshotObj> _screenshotData = nullptr;
QImage _matchImage;
static bool _registered;
static const int id;
};
class MacroConditionVideoEdit : public QWidget {
Q_OBJECT
public:
MacroConditionVideoEdit(
QWidget *parent,
std::shared_ptr<MacroConditionVideo> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionVideoEdit(
parent,
std::dynamic_pointer_cast<MacroConditionVideo>(cond));
}
void UpdatePreviewTooltip();
void SetFilePath(const QString &text);
private slots:
void SourceChanged(const QString &text);
void ConditionChanged(int cond);
void FilePathChanged();
void BrowseButtonClicked();
void DurationChanged(double seconds);
void DurationUnitChanged(DurationUnit unit);
protected:
QComboBox *_videoSelection;
DurationSelection *_duration;
QComboBox *_condition;
QLineEdit *_filePath;
QPushButton *_browseButton;
std::shared_ptr<MacroConditionVideo> _entryData;
private:
bool _loading = true;
};

View File

@@ -0,0 +1,64 @@
#pragma once
#include "macro.hpp"
#include <QWidget>
#include <QComboBox>
class MacroConditionWindow : public MacroCondition {
public:
bool CheckCondition();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
int GetId() { return id; };
static std::shared_ptr<MacroCondition> Create()
{
return std::make_shared<MacroConditionWindow>();
}
private:
bool CheckWindowTitleSwitchDirect(std::string &currentWindowTitle);
bool CheckWindowTitleSwitchRegex(std::string &currentWindowTitle,
std::vector<std::string> &windowList);
public:
std::string _window;
bool _fullscreen = false;
bool _maximized = false;
bool _focus = true;
private:
static bool _registered;
static const int id;
};
class MacroConditionWindowEdit : public QWidget {
Q_OBJECT
public:
MacroConditionWindowEdit(
QWidget *parent,
std::shared_ptr<MacroConditionWindow> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionWindowEdit(
parent,
std::dynamic_pointer_cast<MacroConditionWindow>(cond));
}
private slots:
void WindowChanged(const QString &text);
void FullscreenChanged(int state);
void MaximizedChanged(int state);
void FocusChanged(int state);
protected:
QComboBox *_windowSelection;
QCheckBox *_fullscreen;
QCheckBox *_maximized;
QCheckBox *_focused;
std::shared_ptr<MacroConditionWindow> _entryData;
private:
bool _loading = true;
};

88
src/headers/macro.hpp Normal file
View File

@@ -0,0 +1,88 @@
#pragma once
#include <string>
#include <deque>
#include <memory>
#include <map>
#include <obs.hpp>
#include <obs-module.h>
constexpr auto macro_func = 10;
constexpr auto default_priority_10 = macro_func;
constexpr auto logic_root_offset = 100;
enum class LogicType {
ROOT_NONE = 0,
ROOT_NOT,
ROOT_LAST,
// leave some space for potential expansion
NONE = 100,
AND,
OR,
AND_NOT,
OR_NOT,
LAST,
};
static inline bool isRootLogicType(LogicType l)
{
return static_cast<int>(l) < logic_root_offset;
}
struct LogicTypeInfo {
std::string _name;
};
class MacroCondition {
public:
virtual bool CheckCondition() = 0;
virtual bool Save(obs_data_t *obj) = 0;
virtual bool Load(obs_data_t *obj) = 0;
virtual int GetId() = 0;
LogicType GetLogicType() { return _logic; }
void SetLogicType(LogicType logic) { _logic = logic; }
static const std::map<LogicType, LogicTypeInfo> logicTypes;
private:
LogicType _logic;
};
class MacroAction {
public:
virtual bool PerformAction() = 0;
virtual bool Save(obs_data_t *obj) = 0;
virtual bool Load(obs_data_t *obj) = 0;
virtual int GetId() = 0;
virtual void LogAction();
};
class Macro {
public:
Macro(std::string name = "");
virtual ~Macro();
bool CeckMatch();
bool PerformAction();
bool Matched() { return _matched; }
std::string Name() { return _name; }
void SetName(std::string name) { _name = name; }
std::deque<std::shared_ptr<MacroCondition>> &Conditions()
{
return _conditions;
}
std::deque<std::shared_ptr<MacroAction>> &Actions() { return _actions; }
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
// Helper function for plugin state condition regarding scene change
bool SwitchesScene();
private:
std::string _name = "";
std::deque<std::shared_ptr<MacroCondition>> _conditions;
std::deque<std::shared_ptr<MacroAction>> _actions;
bool _matched = false;
};

View File

@@ -0,0 +1,24 @@
#pragma once
#include <QDialog>
#include <QLabel>
#include <QLineEdit>
// Based on OBS's NameDialog
class AdvSSNameDialog : public QDialog {
Q_OBJECT
public:
AdvSSNameDialog(QWidget *parent);
// Returns true if user clicks OK, false otherwise
// userTextInput returns string that user typed into dialog
static bool AskForName(QWidget *parent, const QString &title,
const QString &text, std::string &userTextInput,
const QString &placeHolder = QString(""),
int maxSize = 170);
private:
QLabel *label;
QLineEdit *userText;
};

View File

@@ -54,11 +54,6 @@ struct SceneGroup {
}
};
enum class SwitchTargetType {
Scene,
SceneGroup,
};
class SceneGroupEditWidget : public QWidget {
Q_OBJECT
@@ -87,22 +82,3 @@ private:
SceneGroup *sceneGroup = nullptr;
};
// Based on OBS's NameDialog
class SGNameDialog : public QDialog {
Q_OBJECT
public:
SGNameDialog(QWidget *parent);
// Returns true if user clicks OK, false otherwise
// userTextInput returns string that user typed into dialog
static bool AskForName(QWidget *parent, const QString &title,
const QString &text, std::string &userTextInput,
const QString &placeHolder = QString(""),
int maxSize = 170);
private:
QLabel *label;
QLineEdit *userText;
};

View File

@@ -0,0 +1,29 @@
#pragma once
#include <obs.hpp>
#include <string>
#include <QImage>
#include <chrono>
class AdvSSScreenshotObj {
public:
AdvSSScreenshotObj(obs_source_t *source);
~AdvSSScreenshotObj();
void Screenshot();
void Download();
void Copy();
void MarkDone();
gs_texrender_t *texrender = nullptr;
gs_stagesurf_t *stagesurf = nullptr;
OBSWeakSource weakSource;
std::string path;
QImage image;
uint32_t cx = 0;
uint32_t cy = 0;
int stage = 0;
bool done = false;
std::chrono::high_resolution_clock::time_point time;
};

31
src/headers/section.hpp Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include <QFrame>
#include <QGridLayout>
#include <QParallelAnimationGroup>
#include <QScrollArea>
#include <QToolButton>
#include <QWidget>
class Section : public QWidget {
Q_OBJECT
public:
explicit Section(const int animationDuration = 300,
QWidget *parent = 0);
void SetContent(QWidget *w);
void AddHeaderWidget(QWidget *);
public slots:
void Collapse(bool collapsed);
private:
QGridLayout *_mainLayout;
QHBoxLayout *_headerWidgetLayout;
QToolButton *_toggleButton;
QFrame *_headerLine;
QParallelAnimationGroup *_toggleAnimation = nullptr;
QScrollArea *_contentArea = nullptr;
int _animationDuration;
};

View File

@@ -1,7 +1,9 @@
#pragma once
#include <QSpinBox>
#include <limits>
#include "switch-generic.hpp"
#include "duration-control.hpp"
#include "volume-control.hpp"
constexpr auto audio_func = 8;
@@ -17,9 +19,9 @@ struct AudioSwitch : virtual SceneSwitcherEntry {
OBSWeakSource audioSource = nullptr;
int volumeThreshold = 0;
audioCondition condition = ABOVE;
double duration = 0;
unsigned int matchCount = 0;
float peak = -1;
Duration duration;
bool ignoreInactiveSource = true;
float peak = -std::numeric_limits<float>::infinity();
obs_volmeter_t *volmeter = nullptr;
const char *getType() { return "audio"; }
@@ -48,8 +50,7 @@ struct AudioSwitchFallback : virtual SceneSwitcherEntry {
void load(obs_data_t *obj);
bool enable = false;
double duration = 0;
unsigned int matchCount = 0;
Duration duration;
};
class AudioSwitchWidget : public SwitchWidget {
@@ -69,12 +70,14 @@ private slots:
void VolumeThresholdChanged(int vol);
void ConditionChanged(int cond);
void DurationChanged(double dur);
void IgnoreInactiveChanged(int state);
private:
QComboBox *audioSources;
QComboBox *condition;
QSpinBox *audioVolumeThreshold;
QDoubleSpinBox *duration;
DurationSelection *duration;
QCheckBox *ignoreInactiveSource;
VolControl *volMeter;
AudioSwitch *switchData;
@@ -90,7 +93,7 @@ private slots:
void DurationChanged(double dur);
private:
QDoubleSpinBox *duration;
DurationSelection *duration;
AudioSwitchFallback *switchData;
};

View File

@@ -2,6 +2,13 @@
#include <obs.hpp>
#include <QComboBox>
#include "scene-group.hpp"
enum class SwitchTargetType {
Scene,
SceneGroup,
};
struct SceneSwitcherEntry {
SwitchTargetType targetType = SwitchTargetType::Scene;
SceneGroup *group = nullptr;

View File

@@ -0,0 +1,113 @@
/*
Most of this code is based on https://github.com/Palakis/obs-websocket
*/
#pragma once
#include <set>
#include <QtCore/QObject>
#include <QtCore/QMutex>
#include <QtCore/QSharedPointer>
#include <QtCore/QVariantHash>
#include <QtCore/QThreadPool>
#include <mutex>
#include <condition_variable>
#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include <websocketpp/client.hpp>
using websocketpp::connection_hdl;
typedef websocketpp::server<websocketpp::config::asio> server;
typedef websocketpp::client<websocketpp::config::asio_client> client;
struct sceneSwitchInfo;
class NetworkConfig {
public:
NetworkConfig();
void Load(obs_data_t *obj);
void Save(obs_data_t *obj);
void SetDefaults(obs_data_t *obj);
std::string GetClientUri();
// Server
bool ServerEnabled;
uint64_t ServerPort;
bool LockToIPv4;
// Client
bool ClientEnabled;
std::string Address;
uint64_t ClientPort;
bool SendAll;
};
class WSServer : public QObject {
Q_OBJECT
public:
explicit WSServer();
virtual ~WSServer();
void start(quint16 port, bool lockToIPv4);
void stop();
void sendMessage(sceneSwitchInfo sceneSwitch);
QThreadPool *threadPool() { return &_threadPool; }
private:
void onOpen(connection_hdl hdl);
void onMessage(connection_hdl hdl, server::message_ptr message);
void onClose(connection_hdl hdl);
QString getRemoteEndpoint(connection_hdl hdl);
server _server;
quint16 _serverPort;
bool _lockToIPv4;
std::set<connection_hdl, std::owner_less<connection_hdl>> _connections;
QMutex _clMutex;
QThreadPool _threadPool;
};
enum class ServerStatus {
NOT_RUNNING,
STARTING,
RUNNING,
};
class WSClient : public QObject {
Q_OBJECT
public:
explicit WSClient();
virtual ~WSClient();
void connect(std::string uri);
void disconnect();
std::string getFail() { return _failMsg; }
private:
void onOpen(connection_hdl hdl);
void onFail(connection_hdl hdl);
void onMessage(connection_hdl hdl, client::message_ptr message);
void onClose(connection_hdl hdl);
void connectThread();
client _client;
std::string _uri;
connection_hdl _connection;
std::thread _thread;
bool _retry = false;
std::atomic_bool _connected = {false};
std::mutex _waitMtx;
std::condition_variable _cv;
std::string _failMsg;
};
enum class ClientStatus {
DISCONNECTED,
CONNECTING,
CONNECTED,
FAIL,
};

View File

@@ -2,24 +2,17 @@
#include <QVBoxLayout>
#include "switch-generic.hpp"
#include "duration-control.hpp"
constexpr auto round_trip_func = 1;
constexpr auto default_priority_1 = round_trip_func;
typedef enum {
SECONDS,
MINUTES,
HOURS,
} delay_units;
struct SceneSequenceSwitch : SceneSwitcherEntry {
static bool pause;
SwitchTargetType startTargetType = SwitchTargetType::Scene;
OBSWeakSource startScene = nullptr;
double delay = 0;
int delayMultiplier = 1;
Duration delay;
bool interruptible = false;
unsigned int matchCount = 0;
// nullptr marks start point and reaching end of extended sequence
SceneSequenceSwitch *activeSequence = nullptr;
@@ -57,22 +50,20 @@ public:
static void swapSwitchData(SequenceWidget *s1, SequenceWidget *s2);
void UpdateDelay();
void UpdateWidgetStatus(bool showExtendText);
void setExtendedSequenceStartScene();
private slots:
void SceneChanged(const QString &text);
void DelayChanged(double delay);
void DelayUnitsChanged(int idx);
void DelayUnitsChanged(DurationUnit);
void StartSceneChanged(const QString &text);
void InterruptibleChanged(int state);
void ExtendClicked();
void ReduceClicked();
protected:
QDoubleSpinBox *delay;
QComboBox *delayUnits;
DurationSelection *delay;
QComboBox *startScenes;
QCheckBox *interruptible;
QVBoxLayout *extendSequenceLayout;

View File

@@ -1,25 +1,35 @@
#pragma once
#include <QDoubleSpinBox>
#include "switch-generic.hpp"
struct SceneTransition : SceneSwitcherEntry {
OBSWeakSource scene2 = nullptr;
double duration = 0.3;
const char *getType() { return "transition"; }
bool initialized();
bool valid();
void save(obs_data_t *obj);
void load(obs_data_t *obj);
inline SceneTransition(){};
inline SceneTransition(OBSWeakSource scene_, OBSWeakSource scene2_,
OBSWeakSource transition_)
: SceneSwitcherEntry(scene_, transition_), scene2(scene2_)
OBSWeakSource transition_, double duration_)
: SceneSwitcherEntry(scene_, transition_),
scene2(scene2_),
duration(duration_)
{
}
};
struct DefaultSceneTransition : SceneSwitcherEntry {
static bool pause;
static unsigned int delay;
const char *getType() { return "def_transition"; }
void save(obs_data_t *obj);
void load(obs_data_t *obj);
bool checkMatch(OBSWeakSource currentScene);
void setTransition();
@@ -44,9 +54,11 @@ public:
private slots:
void Scene2Changed(const QString &text);
void DurationChanged(double dur);
private:
QComboBox *scenes2;
QDoubleSpinBox *duration;
SceneTransition *switchData;
};

View File

@@ -2,6 +2,7 @@
#include <QSpinBox>
#include "switch-generic.hpp"
#include "screenshot-helper.hpp"
constexpr auto video_func = 9;
constexpr auto default_priority_9 = video_func;
@@ -10,30 +11,7 @@ enum class videoSwitchType {
MATCH,
DIFFER,
HAS_NOT_CHANGED,
};
class AdvSSScreenshotObj {
public:
AdvSSScreenshotObj(obs_source_t *source);
~AdvSSScreenshotObj();
void Screenshot();
void Download();
void Copy();
void MarkDone();
gs_texrender_t *texrender = nullptr;
gs_stagesurf_t *stagesurf = nullptr;
OBSWeakSource weakSource;
std::string path;
QImage image;
uint32_t cx = 0;
uint32_t cy = 0;
int stage = 0;
bool done = false;
std::chrono::high_resolution_clock::time_point time;
HAS_CHANGED,
};
struct VideoSwitch : virtual SceneSwitcherEntry {

View File

@@ -22,10 +22,15 @@
#include "switch-window.hpp"
#include "switch-sequence.hpp"
#include "switch-video.hpp"
#include "switch-network.hpp"
#include "macro.hpp"
#include "duration-control.hpp"
constexpr auto default_interval = 300;
constexpr auto previous_scene_name = "Previous Scene";
constexpr auto current_transition_name = "Current Transition";
constexpr auto tab_count = 18;
typedef enum { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 } NoMatch;
typedef enum { PERSIST = 0, START = 1, STOP = 2 } StartupBehavior;
@@ -37,6 +42,13 @@ enum class AutoStartEvent {
RECORINDG_OR_STREAMING,
};
typedef struct sceneSwitchInfo {
OBSWeakSource scene;
OBSWeakSource transition;
int duration = 0;
} sceneSwitchInfo;
typedef struct transitionData {
std::string name = "";
int duration = 0;
@@ -60,7 +72,8 @@ struct SwitcherData {
bool verbose = false;
bool disableHints = false;
bool showFrame = false;
bool tansitionOverrideOverride = false;
bool transitionOverrideOverride = false;
bool adjustActiveTransitionType = true;
int interval = default_interval;
@@ -68,16 +81,21 @@ struct SwitcherData {
OBSWeakSource previousScene = nullptr;
OBSWeakSource previousSceneHelper = nullptr;
OBSWeakSource lastRandomScene;
SceneGroup *lastRandomSceneGroup;
OBSWeakSource nonMatchingScene;
NoMatch switchIfNotMatching = NO_SWITCH;
double noMatchDelay;
double noMatchCount = 0;
Duration noMatchDelay;
StartupBehavior startupBehavior = PERSIST;
AutoStartEvent autoStartEvent = AutoStartEvent::NEVER;
double cooldown = 0.;
Duration cooldown;
std::chrono::high_resolution_clock::time_point lastMatchTime;
std::deque<Macro> macros;
// Allow only one macro scene change per scene switcher interval
// with the top macro having the highest priority
bool macroSceneSwitched = false;
std::deque<WindowSwitch> windowSwitches;
std::vector<std::string> ignoreIdleWindows;
std::string lastTitle;
@@ -114,6 +132,12 @@ struct SwitcherData {
std::deque<AudioSwitch> audioSwitches;
AudioSwitchFallback audioFallback;
WSServer server;
ServerStatus serverStatus = ServerStatus::NOT_RUNNING;
WSClient client;
ClientStatus clientStatus = ClientStatus::DISCONNECTED;
NetworkConfig networkConfig;
std::deque<VideoSwitch> videoSwitches;
std::deque<SceneGroup> sceneGroups;
@@ -122,7 +146,7 @@ struct SwitcherData {
default_priority_0, default_priority_1, default_priority_2,
default_priority_3, default_priority_4, default_priority_5,
default_priority_6, default_priority_7, default_priority_8,
default_priority_9};
default_priority_9, default_priority_10};
struct ThreadPrio {
std::string name;
@@ -151,13 +175,17 @@ struct SwitcherData {
uint32_t threadPriority = QThread::NormalPriority;
std::vector<int> tabOrder;
std::vector<int> tabOrder = std::vector<int>(tab_count);
bool hotkeysRegistered = false;
obs_hotkey_id startHotkey = 0;
obs_hotkey_id stopHotkey = 0;
obs_hotkey_id toggleHotkey = 0;
bool saveWindowGeo = false;
QPoint windowPos = {};
QSize windowSize = {};
bool versionChanged(obs_data_t *obj, std::string currentVersion);
void Thread();
@@ -175,33 +203,29 @@ struct SwitcherData {
void writeToStatusFile(QString status);
bool checkForMatch(OBSWeakSource &scene, OBSWeakSource &transition,
int &linger);
void checkSceneSequence(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition, int &linger);
void checkIdleSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkWindowTitleSwitch(bool &match, OBSWeakSource &scene,
int &linger, bool &setPreviousSceneAsMatch,
bool &macroMatch);
bool checkMacros();
bool runMacros();
bool checkSceneSequence(OBSWeakSource &scene, OBSWeakSource &transition,
int &linger, bool &setPrevSceneAfterLinger);
bool checkIdleSwitch(OBSWeakSource &scene, OBSWeakSource &transition);
bool checkWindowTitleSwitch(OBSWeakSource &scene,
OBSWeakSource &transition);
void checkExeSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkScreenRegionSwitch(bool &match, OBSWeakSource &scene,
bool checkExeSwitch(OBSWeakSource &scene, OBSWeakSource &transition);
bool checkScreenRegionSwitch(OBSWeakSource &scene,
OBSWeakSource &transition);
void checkSwitchInfoFromFile(bool &match, OBSWeakSource &scene,
bool checkSwitchInfoFromFile(OBSWeakSource &scene,
OBSWeakSource &transition);
void checkFileContent(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkRandom(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition, int &delay);
void checkMediaSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkTimeSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkAudioSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
bool checkFileContent(OBSWeakSource &scene, OBSWeakSource &transition);
bool checkRandom(OBSWeakSource &scene, OBSWeakSource &transition,
int &delay);
bool checkMediaSwitch(OBSWeakSource &scene, OBSWeakSource &transition);
bool checkTimeSwitch(OBSWeakSource &scene, OBSWeakSource &transition);
bool checkAudioSwitch(OBSWeakSource &scene, OBSWeakSource &transition);
void checkAudioSwitchFallback(OBSWeakSource &scene,
OBSWeakSource &transition);
void checkVideoSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
bool checkVideoSwitch(OBSWeakSource &scene, OBSWeakSource &transition);
void checkNoMatchSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition, int &sleep);
void checkSwitchCooldown(bool &match);
@@ -210,6 +234,7 @@ struct SwitcherData {
void checkDefaultSceneTransitions();
void saveSettings(obs_data_t *obj);
void saveMacros(obs_data_t *obj);
void saveWindowTitleSwitches(obs_data_t *obj);
void saveScreenRegionSwitches(obs_data_t *obj);
void savePauseSwitches(obs_data_t *obj);
@@ -225,15 +250,16 @@ struct SwitcherData {
void saveSceneGroups(obs_data_t *obj);
void saveSceneTriggers(obs_data_t *obj);
void saveVideoSwitches(obs_data_t *obj);
void saveNetworkSwitches(obs_data_t *obj);
void saveGeneralSettings(obs_data_t *obj);
void saveHotkeys(obs_data_t *obj);
void saveVersion(obs_data_t *obj, std::string currentVersion);
void loadSettings(obs_data_t *obj);
void loadMacros(obs_data_t *obj);
void loadWindowTitleSwitches(obs_data_t *obj);
void loadScreenRegionSwitches(obs_data_t *obj);
void loadPauseSwitches(obs_data_t *obj);
void loadOldPauseSwitches(obs_data_t *obj);
void loadSceneSequenceSwitches(obs_data_t *obj);
void loadSceneTransitions(obs_data_t *obj);
void loadIdleSwitches(obs_data_t *obj);
@@ -246,6 +272,7 @@ struct SwitcherData {
void loadSceneGroups(obs_data_t *obj);
void loadSceneTriggers(obs_data_t *obj);
void loadVideoSwitches(obs_data_t *obj);
void loadNetworkSettings(obs_data_t *obj);
void loadGeneralSettings(obs_data_t *obj);
void loadHotkeys(obs_data_t *obj);

View File

@@ -1,22 +1,16 @@
#pragma once
#include <QString>
#include <QTextStream>
#include <QLayout>
#include <QLabel>
#include <QMessageBox>
#include <unordered_map>
#include <obs.hpp>
#include <obs-module.h>
#include <obs-frontend-api.h>
static inline bool SceneGroupValid(SceneGroup *group)
{
if (group) {
return group->name != invalid_scene_group_name;
}
return false;
}
static inline bool WeakSourceValid(obs_weak_source_t *ws)
{
obs_source_t *source = obs_weak_source_get_source(ws);
@@ -39,26 +33,6 @@ static inline std::string GetWeakSourceName(obs_weak_source_t *weak_source)
return name;
}
static inline SceneGroup *GetSceneGroupByName(const char *name)
{
if (!switcher) {
return nullptr;
}
for (SceneGroup &sg : switcher->sceneGroups) {
if (sg.name == name) {
return &sg;
}
}
return nullptr;
}
static inline SceneGroup *GetSceneGroupByQString(const QString &name)
{
return GetSceneGroupByName(name.toUtf8().constData());
}
static inline OBSWeakSource GetWeakSourceByName(const char *name)
{
OBSWeakSource weak;
@@ -182,3 +156,61 @@ placeWidgets(std::string text, QBoxLayout *layout,
layout->addStretch();
}
}
static inline void clearLayout(QLayout *layout)
{
QLayoutItem *item;
while ((item = layout->takeAt(0))) {
if (item->layout()) {
clearLayout(item->layout());
delete item->layout();
}
if (item->widget()) {
delete item->widget();
}
delete item;
}
}
static inline bool compareIgnoringLineEnding(QString &s1, QString &s2)
{
// Let QT deal with different types of lineendings
QTextStream s1stream(&s1);
QTextStream s2stream(&s2);
while (!s1stream.atEnd() || !s2stream.atEnd()) {
QString s1s = s1stream.readLine();
QString s2s = s2stream.readLine();
if (s1s != s2s) {
return false;
}
}
if (!s1stream.atEnd() && !s2stream.atEnd()) {
return false;
}
return true;
}
static inline bool DisplayMessage(QString msg, bool question = false)
{
if (question) {
QMessageBox::StandardButton reply;
reply = QMessageBox::question(
nullptr, "Advanced Scene Switcher", msg,
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
return true;
} else {
return false;
}
} else {
QMessageBox Msgbox;
Msgbox.setWindowTitle("Advanced Scene Switcher");
Msgbox.setText(msg);
Msgbox.exec();
}
return false;
}

View File

@@ -68,41 +68,38 @@ void registerHotkeys()
void SwitcherData::saveHotkeys(obs_data_t *obj)
{
obs_data_array_t *startHotkeyArrray =
obs_hotkey_save(switcher->startHotkey);
obs_data_array_t *startHotkeyArrray = obs_hotkey_save(startHotkey);
obs_data_set_array(obj, "startHotkey", startHotkeyArrray);
obs_data_array_release(startHotkeyArrray);
obs_data_array_t *stopHotkeyArrray =
obs_hotkey_save(switcher->stopHotkey);
obs_data_array_t *stopHotkeyArrray = obs_hotkey_save(stopHotkey);
obs_data_set_array(obj, "stopHotkey", stopHotkeyArrray);
obs_data_array_release(stopHotkeyArrray);
obs_data_array_t *toggleHotkeyArrray =
obs_hotkey_save(switcher->toggleHotkey);
obs_data_array_t *toggleHotkeyArrray = obs_hotkey_save(toggleHotkey);
obs_data_set_array(obj, "toggleHotkey", toggleHotkeyArrray);
obs_data_array_release(toggleHotkeyArrray);
}
void SwitcherData::loadHotkeys(obs_data_t *obj)
{
if (!switcher->hotkeysRegistered) {
if (!hotkeysRegistered) {
registerHotkeys();
}
obs_data_array_t *startHotkeyArrray =
obs_data_get_array(obj, "startHotkey");
obs_hotkey_load(switcher->startHotkey, startHotkeyArrray);
obs_hotkey_load(startHotkey, startHotkeyArrray);
obs_data_array_release(startHotkeyArrray);
obs_data_array_t *stopHotkeyArrray =
obs_data_get_array(obj, "stopHotkey");
obs_hotkey_load(switcher->stopHotkey, stopHotkeyArrray);
obs_hotkey_load(stopHotkey, stopHotkeyArrray);
obs_data_array_release(stopHotkeyArrray);
obs_data_array_t *toggleHotkeyArrray =
obs_data_get_array(obj, "toggleHotkey");
obs_hotkey_load(switcher->toggleHotkey, toggleHotkeyArrray);
obs_hotkey_load(toggleHotkey, toggleHotkeyArrray);
obs_data_array_release(toggleHotkeyArrray);
}

View File

@@ -14,7 +14,9 @@
#undef Status
#undef Unsorted
#include <util/platform.h>
#include "../headers/advanced-scene-switcher.hpp"
#include <vector>
#include <QStringList>
#include <QRegularExpression>
static Display *xdisplay = 0;
@@ -203,6 +205,10 @@ void GetCurrentWindowTitle(std::string &title)
&format, &num, &bytes,
(uint8_t **)&data);
if (data == nullptr) {
return;
}
int status = 0;
XTextProperty text;
if (xstatus == Success) {
@@ -386,4 +392,4 @@ int secondsSinceLastInput()
XCloseDisplay(display);
return idle_time;
}
}

186
src/macro-action-audio.cpp Normal file
View File

@@ -0,0 +1,186 @@
#include "headers/macro-action-audio.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
const int MacroActionAudio::id = 2;
bool MacroActionAudio::_registered = MacroActionFactory::Register(
MacroActionAudio::id,
{MacroActionAudio::Create, MacroActionAudioEdit::Create,
"AdvSceneSwitcher.action.audio"});
const static std::map<AudioAction, std::string> actionTypes = {
{AudioAction::MUTE, "AdvSceneSwitcher.action.audio.type.mute"},
{AudioAction::UNMUTE, "AdvSceneSwitcher.action.audio.type.unmute"},
{AudioAction::SOURCE_VOLUME,
"AdvSceneSwitcher.action.audio.type.sourceVolume"},
{AudioAction::MASTER_VOLUME,
"AdvSceneSwitcher.action.audio.type.masterVolume"},
};
bool MacroActionAudio::PerformAction()
{
auto s = obs_weak_source_get_source(_audioSource);
switch (_action) {
case AudioAction::MUTE:
obs_source_set_muted(s, true);
break;
case AudioAction::UNMUTE:
obs_source_set_muted(s, false);
break;
case AudioAction::SOURCE_VOLUME:
obs_source_set_volume(s, (float)_volume / 100.0f);
break;
case AudioAction::MASTER_VOLUME:
obs_set_master_volume((float)_volume / 100.0f);
break;
default:
break;
}
obs_source_release(s);
return true;
}
void MacroActionAudio::LogAction()
{
auto it = actionTypes.find(_action);
if (it != actionTypes.end()) {
vblog(LOG_INFO,
"performed action \"%s\" for source \"%s\" with volume %d",
it->second.c_str(),
GetWeakSourceName(_audioSource).c_str(), _volume);
} else {
blog(LOG_WARNING, "ignored unknown audio action %d",
static_cast<int>(_action));
}
}
bool MacroActionAudio::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
obs_data_set_string(obj, "audioSource",
GetWeakSourceName(_audioSource).c_str());
obs_data_set_int(obj, "action", static_cast<int>(_action));
obs_data_set_int(obj, "volume", _volume);
return true;
}
bool MacroActionAudio::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
const char *audioSourceName = obs_data_get_string(obj, "audioSource");
_audioSource = GetWeakSourceByName(audioSourceName);
_action = static_cast<AudioAction>(obs_data_get_int(obj, "action"));
_volume = obs_data_get_int(obj, "volume");
return true;
}
static inline void populateActionSelection(QComboBox *list)
{
for (auto entry : actionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionAudioEdit::MacroActionAudioEdit(
QWidget *parent, std::shared_ptr<MacroActionAudio> entryData)
: QWidget(parent)
{
_audioSources = new QComboBox();
_actions = new QComboBox();
_volumePercent = new QSpinBox();
_volumePercent->setMinimum(0);
_volumePercent->setMaximum(2000);
_volumePercent->setSuffix("%");
populateActionSelection(_actions);
AdvSceneSwitcher::populateAudioSelection(_audioSources);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_audioSources,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(SourceChanged(const QString &)));
QWidget::connect(_volumePercent, SIGNAL(valueChanged(int)), this,
SLOT(VolumeChanged(int)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{audioSources}}", _audioSources},
{"{{actions}}", _actions},
{"{{volume}}", _volumePercent},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.audio.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
bool hasVolumeControl(AudioAction action)
{
return action == AudioAction::SOURCE_VOLUME ||
action == AudioAction::MASTER_VOLUME;
}
bool hasSourceControl(AudioAction action)
{
return action != AudioAction::MASTER_VOLUME;
}
void MacroActionAudioEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_audioSources->setCurrentText(
GetWeakSourceName(_entryData->_audioSource).c_str());
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_volumePercent->setValue(_entryData->_volume);
if (hasVolumeControl(_entryData->_action)) {
_volumePercent->show();
} else {
_volumePercent->hide();
}
if (hasSourceControl(_entryData->_action)) {
_audioSources->show();
} else {
_audioSources->hide();
}
}
void MacroActionAudioEdit::SourceChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_audioSource = GetWeakSourceByQString(text);
}
void MacroActionAudioEdit::ActionChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_action = static_cast<AudioAction>(value);
UpdateEntryData();
}
void MacroActionAudioEdit::VolumeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_volume = value;
}

135
src/macro-action-edit.cpp Normal file
View File

@@ -0,0 +1,135 @@
#include "headers/macro-action-edit.hpp"
#include "headers/macro-action-switch-scene.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
std::map<int, MacroActionInfo> MacroActionFactory::_methods;
bool MacroActionFactory::Register(int id, MacroActionInfo info)
{
if (auto it = _methods.find(id); it == _methods.end()) {
_methods[id] = info;
return true;
}
return false;
}
std::shared_ptr<MacroAction> MacroActionFactory::Create(const int id)
{
if (auto it = _methods.find(id); it != _methods.end())
return it->second._createFunc();
return nullptr;
}
QWidget *MacroActionFactory::CreateWidget(const int id, QWidget *parent,
std::shared_ptr<MacroAction> action)
{
if (auto it = _methods.find(id); it != _methods.end())
return it->second._createWidgetFunc(parent, action);
return nullptr;
}
static inline void populateActionSelection(QComboBox *list)
{
for (auto entry : MacroActionFactory::GetActionTypes()) {
list->addItem(obs_module_text(entry.second._name.c_str()));
}
}
MacroActionEdit::MacroActionEdit(QWidget *parent,
std::shared_ptr<MacroAction> *entryData,
int type, bool startCollapsed)
: QWidget(parent)
{
_actionSelection = new QComboBox();
_section = new Section(300);
QWidget::connect(_actionSelection, SIGNAL(currentIndexChanged(int)),
this, SLOT(ActionSelectionChanged(int)));
populateActionSelection(_actionSelection);
_section->AddHeaderWidget(_actionSelection);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(_section);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData(type);
_loading = false;
_section->Collapse(startCollapsed);
}
void MacroActionEdit::ActionSelectionChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->reset();
*_entryData = MacroActionFactory::Create(idx);
auto widget =
MacroActionFactory::CreateWidget(idx, window(), *_entryData);
_section->SetContent(widget);
_section->Collapse(false);
}
void MacroActionEdit::UpdateEntryData(int type)
{
_actionSelection->setCurrentIndex(type);
auto widget =
MacroActionFactory::CreateWidget(type, window(), *_entryData);
_section->SetContent(widget);
}
void MacroActionEdit::Collapse(bool collapsed)
{
_section->Collapse(collapsed);
}
void AdvSceneSwitcher::on_actionAdd_clicked()
{
auto macro = getSelectedMacro();
if (!macro) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
macro->Actions().emplace_back(MacroActionFactory::Create(0));
auto newEntry = new MacroActionEdit(this, &macro->Actions().back(), 0);
ui->macroEditActionLayout->addWidget(newEntry);
ui->macroEditActionHelp->setVisible(false);
newEntry->Collapse(false);
}
void AdvSceneSwitcher::on_actionRemove_clicked()
{
auto macro = getSelectedMacro();
if (!macro) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
if (macro->Actions().empty()) {
return;
}
macro->Actions().pop_back();
int count = ui->macroEditActionLayout->count();
auto item = ui->macroEditActionLayout->takeAt(count - 1);
if (item) {
auto widget = item->widget();
if (widget) {
widget->setVisible(false);
}
delete item;
}
if (count == 1) {
ui->macroEditActionHelp->setVisible(true);
}
}

View File

@@ -0,0 +1,133 @@
#include "headers/macro-action-recording.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
const int MacroActionRecord::id = 4;
bool MacroActionRecord::_registered = MacroActionFactory::Register(
MacroActionRecord::id,
{MacroActionRecord::Create, MacroActionRecordEdit::Create,
"AdvSceneSwitcher.action.recording"});
const static std::map<RecordAction, std::string> actionTypes = {
{RecordAction::STOP, "AdvSceneSwitcher.action.recording.type.stop"},
{RecordAction::START, "AdvSceneSwitcher.action.recording.type.start"},
{RecordAction::PAUSE, "AdvSceneSwitcher.action.recording.type.pause"},
{RecordAction::UNPAUSE,
"AdvSceneSwitcher.action.recording.type.unpause"},
};
bool MacroActionRecord::PerformAction()
{
switch (_action) {
case RecordAction::STOP:
if (obs_frontend_recording_active()) {
obs_frontend_recording_stop();
}
break;
case RecordAction::START:
if (!obs_frontend_recording_active()) {
obs_frontend_recording_start();
}
break;
case RecordAction::PAUSE:
if (obs_frontend_recording_active() &&
!obs_frontend_recording_paused()) {
obs_frontend_recording_pause(true);
}
break;
case RecordAction::UNPAUSE:
if (obs_frontend_recording_active() &&
obs_frontend_recording_paused()) {
obs_frontend_recording_pause(false);
}
break;
default:
break;
}
return true;
}
void MacroActionRecord::LogAction()
{
auto it = actionTypes.find(_action);
if (it != actionTypes.end()) {
vblog(LOG_INFO, "performed action \"%s\"", it->second.c_str());
} else {
blog(LOG_WARNING, "ignored unknown recording action %d",
static_cast<int>(_action));
}
}
bool MacroActionRecord::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
return true;
}
bool MacroActionRecord::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_action = static_cast<RecordAction>(obs_data_get_int(obj, "action"));
return true;
}
static inline void populateActionSelection(QComboBox *list)
{
for (auto entry : actionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionRecordEdit::MacroActionRecordEdit(
QWidget *parent, std::shared_ptr<MacroActionRecord> entryData)
: QWidget(parent)
{
_actions = new QComboBox();
_pauseHint = new QLabel(obs_module_text(
"AdvSceneSwitcher.action.recording.pause.hint"));
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{actions}}", _actions},
{"{{pauseHint}}", _pauseHint},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.recording.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
bool isPauseAction(RecordAction a)
{
return a == RecordAction::PAUSE || a == RecordAction::UNPAUSE;
}
void MacroActionRecordEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_pauseHint->setVisible(isPauseAction(_entryData->_action));
}
void MacroActionRecordEdit::ActionChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_action = static_cast<RecordAction>(value);
_pauseHint->setVisible(isPauseAction(_entryData->_action));
}

View File

@@ -0,0 +1,121 @@
#include "headers/macro-action-replay-buffer.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
const int MacroActionReplayBuffer::id = 5;
bool MacroActionReplayBuffer::_registered = MacroActionFactory::Register(
MacroActionReplayBuffer::id,
{MacroActionReplayBuffer::Create, MacroActionReplayBufferEdit::Create,
"AdvSceneSwitcher.action.replay"});
const static std::map<ReplayBufferAction, std::string> actionTypes = {
{ReplayBufferAction::STOP, "AdvSceneSwitcher.action.replay.type.stop"},
{ReplayBufferAction::START,
"AdvSceneSwitcher.action.replay.type.start"},
{ReplayBufferAction::SAVE, "AdvSceneSwitcher.action.replay.type.save"},
};
bool MacroActionReplayBuffer::PerformAction()
{
switch (_action) {
case ReplayBufferAction::STOP:
if (obs_frontend_replay_buffer_active()) {
obs_frontend_replay_buffer_stop();
}
break;
case ReplayBufferAction::START:
if (!obs_frontend_replay_buffer_active()) {
obs_frontend_replay_buffer_start();
}
break;
case ReplayBufferAction::SAVE:
if (obs_frontend_replay_buffer_active() &&
_duration.DurationReached()) {
obs_frontend_replay_buffer_save();
// Default buffer size is 20s so waiting for 10s before
// trying to save again seems reasonable
_duration.seconds = 10;
_duration.Reset();
}
break;
default:
break;
}
return true;
}
void MacroActionReplayBuffer::LogAction()
{
auto it = actionTypes.find(_action);
if (it != actionTypes.end()) {
vblog(LOG_INFO, "performed action \"%s\"", it->second.c_str());
} else {
blog(LOG_WARNING, "ignored unknown replay buffer action %d",
static_cast<int>(_action));
}
}
bool MacroActionReplayBuffer::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
return true;
}
bool MacroActionReplayBuffer::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_action = static_cast<ReplayBufferAction>(
obs_data_get_int(obj, "action"));
return true;
}
static inline void populateActionSelection(QComboBox *list)
{
for (auto entry : actionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionReplayBufferEdit::MacroActionReplayBufferEdit(
QWidget *parent, std::shared_ptr<MacroActionReplayBuffer> entryData)
: QWidget(parent)
{
_actions = new QComboBox();
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{actions}}", _actions},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.replay.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionReplayBufferEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
}
void MacroActionReplayBufferEdit::ActionChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_action = static_cast<ReplayBufferAction>(value);
}

98
src/macro-action-run.cpp Normal file
View File

@@ -0,0 +1,98 @@
#include "headers/macro-action-run.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
#include <QProcess>
#include <QFileDialog>
const int MacroActionRun::id = 6;
bool MacroActionRun::_registered = MacroActionFactory::Register(
MacroActionRun::id, {MacroActionRun::Create, MacroActionRunEdit::Create,
"AdvSceneSwitcher.action.run"});
bool MacroActionRun::PerformAction()
{
QProcess::startDetached(QString::fromStdString(_path), QStringList());
return true;
}
void MacroActionRun::LogAction()
{
vblog(LOG_INFO, "run \"%s\"", _path.c_str());
}
bool MacroActionRun::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
obs_data_set_string(obj, "path", _path.c_str());
return true;
}
bool MacroActionRun::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_path = obs_data_get_string(obj, "path");
return true;
}
MacroActionRunEdit::MacroActionRunEdit(
QWidget *parent, std::shared_ptr<MacroActionRun> entryData)
: QWidget(parent)
{
_filePath = new QLineEdit();
_browseButton =
new QPushButton(obs_module_text("AdvSceneSwitcher.browse"));
_browseButton->setStyleSheet("border:1px solid gray;");
QWidget::connect(_filePath, SIGNAL(editingFinished()), this,
SLOT(FilePathChanged()));
QWidget::connect(_browseButton, SIGNAL(clicked()), this,
SLOT(BrowseButtonClicked()));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{filePath}}", _filePath},
{"{{browseButton}}", _browseButton},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.run.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionRunEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_filePath->setText(QString::fromStdString(_entryData->_path));
}
void MacroActionRunEdit::FilePathChanged()
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_path = _filePath->text().toUtf8().constData();
}
void MacroActionRunEdit::BrowseButtonClicked()
{
if (_loading || !_entryData) {
return;
}
QString path = QFileDialog::getOpenFileName(this);
if (path.isEmpty()) {
return;
}
_filePath->setText(path);
FilePathChanged();
}

View File

@@ -0,0 +1,111 @@
#include "headers/macro-action-streaming.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
const int MacroActionStream::id = 3;
bool MacroActionStream::_registered = MacroActionFactory::Register(
MacroActionStream::id,
{MacroActionStream::Create, MacroActionStreamEdit::Create,
"AdvSceneSwitcher.action.streaming"});
const static std::map<StreamAction, std::string> actionTypes = {
{StreamAction::STOP, "AdvSceneSwitcher.action.streaming.type.stop"},
{StreamAction::START, "AdvSceneSwitcher.action.streaming.type.start"},
};
bool MacroActionStream::PerformAction()
{
switch (_action) {
case StreamAction::STOP:
if (obs_frontend_streaming_active()) {
obs_frontend_streaming_stop();
}
break;
case StreamAction::START:
if (!obs_frontend_streaming_active() &&
_retryCooldown.DurationReached()) {
obs_frontend_streaming_start();
_retryCooldown.seconds++;
_retryCooldown.Reset();
}
break;
default:
break;
}
return true;
}
void MacroActionStream::LogAction()
{
auto it = actionTypes.find(_action);
if (it != actionTypes.end()) {
vblog(LOG_INFO, "performed action \"%s\"", it->second.c_str());
} else {
blog(LOG_WARNING, "ignored unknown streaming action %d",
static_cast<int>(_action));
}
}
bool MacroActionStream::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
return true;
}
bool MacroActionStream::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_action = static_cast<StreamAction>(obs_data_get_int(obj, "action"));
return true;
}
static inline void populateActionSelection(QComboBox *list)
{
for (auto entry : actionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionStreamEdit::MacroActionStreamEdit(
QWidget *parent, std::shared_ptr<MacroActionStream> entryData)
: QWidget(parent)
{
_actions = new QComboBox();
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{actions}}", _actions},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.streaming.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionStreamEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
}
void MacroActionStreamEdit::ActionChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_action = static_cast<StreamAction>(value);
}

View File

@@ -0,0 +1,73 @@
#include "headers/macro-action-switch-scene.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
const int MacroActionSwitchScene::id = 0;
bool MacroActionSwitchScene::_registered = MacroActionFactory::Register(
MacroActionSwitchScene::id,
{MacroActionSwitchScene::Create, MacroActionSwitchSceneEdit::Create,
"AdvSceneSwitcher.action.switchScene"});
bool MacroActionSwitchScene::PerformAction()
{
OBSWeakSource scene = getScene();
switchScene({scene, transition, (int)(_duration.seconds * 1000)});
return true;
}
void MacroActionSwitchScene::LogAction()
{
if (switcher->verbose) {
logMatch();
}
}
bool MacroActionSwitchScene::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
SceneSwitcherEntry::save(obj);
_duration.Save(obj);
return true;
}
bool MacroActionSwitchScene::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
SceneSwitcherEntry::load(obj);
_duration.Load(obj);
return true;
}
MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
QWidget *parent, std::shared_ptr<MacroActionSwitchScene> entryData)
: SwitchWidget(parent, entryData.get(), true, true)
{
_duration = new DurationSelection(parent, false);
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", scenes},
{"{{transitions}}", transitions},
{"{{duration}}", _duration},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.scene.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
_duration->SetDuration(_entryData->_duration);
SwitchWidget::loading = false;
}
void MacroActionSwitchSceneEdit::DurationChanged(double seconds)
{
if (SwitchWidget::loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.seconds = seconds;
}

206
src/macro-action-wait.cpp Normal file
View File

@@ -0,0 +1,206 @@
#include "headers/macro-action-wait.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
#include <random>
const int MacroActionWait::id = 1;
bool MacroActionWait::_registered = MacroActionFactory::Register(
MacroActionWait::id,
{MacroActionWait::Create, MacroActionWaitEdit::Create,
"AdvSceneSwitcher.action.wait"});
static std::map<WaitType, std::string> waitTypes = {
{WaitType::FIXED, "AdvSceneSwitcher.action.wait.type.fixed"},
{WaitType::RANDOM, "AdvSceneSwitcher.action.wait.type.random"},
};
static std::random_device rd;
static std::default_random_engine re(rd());
bool MacroActionWait::PerformAction()
{
double sleep_duration;
if (_waitType == WaitType::FIXED) {
sleep_duration = _duration.seconds;
} else {
double min = (_duration.seconds < _duration2.seconds)
? _duration.seconds
: _duration2.seconds;
double max = (_duration.seconds < _duration2.seconds)
? _duration2.seconds
: _duration.seconds;
std::uniform_real_distribution<double> unif(min, max);
sleep_duration = unif(re);
}
vblog(LOG_INFO, "perform action wait with duration of %f",
sleep_duration);
std::unique_lock<std::mutex> lock(switcher->m);
switcher->cv.wait_for(lock, std::chrono::milliseconds((
long long)(sleep_duration * 1000)));
return !switcher->stop;
}
bool MacroActionWait::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
_duration.Save(obj);
_duration2.Save(obj, "seconds2", "displayUnit2");
obs_data_set_int(obj, "waitType", static_cast<int>(_waitType));
return true;
}
bool MacroActionWait::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_duration.Load(obj);
_duration2.Load(obj, "seconds2", "displayUnit2");
_waitType = static_cast<WaitType>(obs_data_get_int(obj, "waitType"));
return true;
}
static inline void populateTypeSelection(QComboBox *list)
{
for (auto entry : waitTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionWaitEdit::MacroActionWaitEdit(
QWidget *parent, std::shared_ptr<MacroActionWait> entryData)
: QWidget(parent)
{
_duration = new DurationSelection();
_duration2 = new DurationSelection();
_waitType = new QComboBox();
populateTypeSelection(_waitType);
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(DurationUnitChanged(DurationUnit)));
QWidget::connect(_duration2, SIGNAL(DurationChanged(double)), this,
SLOT(Duration2Changed(double)));
QWidget::connect(_duration2, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(Duration2UnitChanged(DurationUnit)));
QWidget::connect(_waitType, SIGNAL(currentIndexChanged(int)), this,
SLOT(TypeChanged(int)));
_mainLayout = new QHBoxLayout;
setLayout(_mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionWaitEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
if (_entryData->_waitType == WaitType::FIXED) {
SetupFixedDurationEdit();
} else {
SetupRandomDurationEdit();
}
_duration->SetDuration(_entryData->_duration);
_duration2->SetDuration(_entryData->_duration2);
_waitType->setCurrentIndex(static_cast<int>(_entryData->_waitType));
}
void MacroActionWaitEdit::SetupFixedDurationEdit()
{
_mainLayout->removeWidget(_duration);
_mainLayout->removeWidget(_duration2);
_mainLayout->removeWidget(_waitType);
clearLayout(_mainLayout);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{duration}}", _duration},
{"{{waitType}}", _waitType},
};
placeWidgets(
obs_module_text("AdvSceneSwitcher.action.wait.entry.fixed"),
_mainLayout, widgetPlaceholders);
_duration2->hide();
}
void MacroActionWaitEdit::SetupRandomDurationEdit()
{
_mainLayout->removeWidget(_duration);
_mainLayout->removeWidget(_duration2);
_mainLayout->removeWidget(_waitType);
clearLayout(_mainLayout);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{duration}}", _duration},
{"{{duration2}}", _duration2},
{"{{waitType}}", _waitType},
};
placeWidgets(
obs_module_text("AdvSceneSwitcher.action.wait.entry.random"),
_mainLayout, widgetPlaceholders);
_duration2->show();
}
void MacroActionWaitEdit::TypeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
WaitType type = static_cast<WaitType>(value);
if (type == WaitType::FIXED) {
SetupFixedDurationEdit();
} else {
SetupRandomDurationEdit();
}
_entryData->_waitType = type;
}
void MacroActionWaitEdit::DurationChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.seconds = seconds;
}
void MacroActionWaitEdit::DurationUnitChanged(DurationUnit unit)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.displayUnit = unit;
}
void MacroActionWaitEdit::Duration2Changed(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration2.seconds = seconds;
}
void MacroActionWaitEdit::Duration2UnitChanged(DurationUnit unit)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration2.displayUnit = unit;
}

View File

@@ -0,0 +1,239 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-audio.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
const int MacroConditionAudio::id = 3;
bool MacroConditionAudio::_registered = MacroConditionFactory::Register(
MacroConditionAudio::id,
{MacroConditionAudio::Create, MacroConditionAudioEdit::Create,
"AdvSceneSwitcher.condition.audio"});
static std::map<AudioCondition, std::string> audioConditionTypes = {
{AudioCondition::ABOVE, "AdvSceneSwitcher.ondition.audio.state.above"},
{AudioCondition::BELOW, "AdvSceneSwitcher.ondition.audio.state.below"},
};
MacroConditionAudio::~MacroConditionAudio()
{
obs_volmeter_remove_callback(_volmeter, SetVolumeLevel, this);
obs_volmeter_destroy(_volmeter);
}
bool MacroConditionAudio::CheckCondition()
{
// peak will have a value from -60 db to 0 db
bool volumeThresholdreached = false;
if (_condition == AudioCondition::ABOVE) {
volumeThresholdreached = ((double)_peak + 60) * 1.7 > _volume;
} else {
volumeThresholdreached = ((double)_peak + 60) * 1.7 < _volume;
}
// Reset for next check
_peak = -std::numeric_limits<float>::infinity();
if (!volumeThresholdreached) {
_duration.Reset();
return false;
}
return _duration.DurationReached();
}
bool MacroConditionAudio::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_string(obj, "audioSource",
GetWeakSourceName(_audioSource).c_str());
obs_data_set_int(obj, "volume", _volume);
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
_duration.Save(obj);
return true;
}
obs_volmeter_t *AddVolmeterToSource(MacroConditionAudio *entry,
obs_weak_source *source)
{
obs_volmeter_t *volmeter = obs_volmeter_create(OBS_FADER_LOG);
obs_volmeter_add_callback(volmeter, MacroConditionAudio::SetVolumeLevel,
entry);
obs_source_t *as = obs_weak_source_get_source(source);
if (!obs_volmeter_attach_source(volmeter, as)) {
const char *name = obs_source_get_name(as);
blog(LOG_WARNING, "failed to attach volmeter to source %s",
name);
}
obs_source_release(as);
return volmeter;
}
bool MacroConditionAudio::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
const char *audioSourceName = obs_data_get_string(obj, "audioSource");
_audioSource = GetWeakSourceByName(audioSourceName);
_volume = obs_data_get_int(obj, "volume");
_condition =
static_cast<AudioCondition>(obs_data_get_int(obj, "condition"));
_duration.Load(obj);
_volmeter = AddVolmeterToSource(this, _audioSource);
return true;
}
void MacroConditionAudio::SetVolumeLevel(
void *data, const float magnitude[MAX_AUDIO_CHANNELS],
const float peak[MAX_AUDIO_CHANNELS],
const float inputPeak[MAX_AUDIO_CHANNELS])
{
UNUSED_PARAMETER(magnitude);
UNUSED_PARAMETER(inputPeak);
MacroConditionAudio *c = static_cast<MacroConditionAudio *>(data);
for (int i = 0; i < MAX_AUDIO_CHANNELS; i++) {
if (peak[i] > c->_peak) {
c->_peak = peak[i];
}
}
}
void MacroConditionAudio::ResetVolmeter()
{
obs_volmeter_remove_callback(_volmeter, SetVolumeLevel, this);
obs_volmeter_destroy(_volmeter);
_volmeter = AddVolmeterToSource(this, _audioSource);
}
static inline void populateConditionSelection(QComboBox *list)
{
for (auto entry : audioConditionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroConditionAudioEdit::MacroConditionAudioEdit(
QWidget *parent, std::shared_ptr<MacroConditionAudio> entryData)
: QWidget(parent)
{
_audioSources = new QComboBox();
_condition = new QComboBox();
_volume = new QSpinBox();
_duration = new DurationSelection(parent, false);
_volume->setSuffix("%");
_volume->setMaximum(100);
_volume->setMinimum(0);
QWidget::connect(_volume, SIGNAL(valueChanged(int)), this,
SLOT(VolumeThresholdChanged(int)));
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(_audioSources,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(SourceChanged(const QString &)));
AdvSceneSwitcher::populateAudioSelection(_audioSources);
populateConditionSelection(_condition);
QHBoxLayout *switchLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{audioSources}}", _audioSources},
{"{{volume}}", _volume},
{"{{condition}}", _condition},
{"{{duration}}", _duration}};
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.audio.entry"),
switchLayout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(switchLayout);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionAudioEdit::UpdateVolmeterSource()
{
delete _volMeter;
obs_source_t *soruce =
obs_weak_source_get_source(_entryData->_audioSource);
_volMeter = new VolControl(soruce);
obs_source_release(soruce);
QLayout *layout = this->layout();
layout->addWidget(_volMeter);
QWidget::connect(_volMeter->GetSlider(), SIGNAL(valueChanged(int)),
_volume, SLOT(setValue(int)));
QWidget::connect(_volume, SIGNAL(valueChanged(int)),
_volMeter->GetSlider(), SLOT(setValue(int)));
// Slider will default to 0 so set it manually once
_volMeter->GetSlider()->setValue(_entryData->_volume);
}
void MacroConditionAudioEdit::SourceChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_audioSource = GetWeakSourceByQString(text);
_entryData->ResetVolmeter();
UpdateVolmeterSource();
}
void MacroConditionAudioEdit::VolumeThresholdChanged(int vol)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_volume = vol;
}
void MacroConditionAudioEdit::ConditionChanged(int cond)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_condition = static_cast<AudioCondition>(cond);
}
void MacroConditionAudioEdit::DurationChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.seconds = seconds;
}
void MacroConditionAudioEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_audioSources->setCurrentText(
GetWeakSourceName(_entryData->_audioSource).c_str());
_volume->setValue(_entryData->_volume);
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
_duration->SetDuration(_entryData->_duration);
UpdateVolmeterSource();
}

View File

@@ -0,0 +1,194 @@
#include "headers/macro-condition-edit.hpp"
std::map<int, MacroConditionInfo> MacroConditionFactory::_methods;
bool MacroConditionFactory::Register(int id, MacroConditionInfo info)
{
if (auto it = _methods.find(id); it == _methods.end()) {
_methods[id] = info;
return true;
}
return false;
}
std::shared_ptr<MacroCondition> MacroConditionFactory::Create(const int id)
{
if (auto it = _methods.find(id); it != _methods.end())
return it->second._createFunc();
return nullptr;
}
QWidget *
MacroConditionFactory::CreateWidget(const int id, QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
if (auto it = _methods.find(id); it != _methods.end())
return it->second._createWidgetFunc(parent, cond);
return nullptr;
}
static inline void populateLogicSelection(QComboBox *list, bool root = false)
{
if (root) {
for (auto entry : MacroCondition::logicTypes) {
if (static_cast<int>(entry.first) < logic_root_offset) {
list->addItem(obs_module_text(
entry.second._name.c_str()));
}
}
} else {
for (auto entry : MacroCondition::logicTypes) {
if (static_cast<int>(entry.first) >=
logic_root_offset) {
list->addItem(obs_module_text(
entry.second._name.c_str()));
}
}
}
}
static inline void populateConditionSelection(QComboBox *list)
{
for (auto entry : MacroConditionFactory::GetConditionTypes()) {
list->addItem(obs_module_text(entry.second._name.c_str()));
}
}
MacroConditionEdit::MacroConditionEdit(
QWidget *parent, std::shared_ptr<MacroCondition> *entryData, int type,
bool root, bool startCollapsed)
: QWidget(parent)
{
_logicSelection = new QComboBox();
_conditionSelection = new QComboBox();
_section = new Section(300);
QWidget::connect(_logicSelection, SIGNAL(currentIndexChanged(int)),
this, SLOT(LogicSelectionChanged(int)));
QWidget::connect(_conditionSelection, SIGNAL(currentIndexChanged(int)),
this, SLOT(ConditionSelectionChanged(int)));
populateLogicSelection(_logicSelection, root);
populateConditionSelection(_conditionSelection);
_section->AddHeaderWidget(_logicSelection);
_section->AddHeaderWidget(_conditionSelection);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(_section);
setLayout(mainLayout);
_entryData = entryData;
_isRoot = root;
UpdateEntryData(type);
_loading = false;
_section->Collapse(startCollapsed);
}
void MacroConditionEdit::LogicSelectionChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
LogicType type;
if (IsRootNode()) {
type = static_cast<LogicType>(idx);
} else {
type = static_cast<LogicType>(idx + logic_root_offset);
}
std::lock_guard<std::mutex> lock(switcher->m);
(*_entryData)->SetLogicType(type);
}
bool MacroConditionEdit::IsRootNode()
{
return _isRoot;
}
void MacroConditionEdit::UpdateEntryData(int type)
{
_conditionSelection->setCurrentIndex(type);
auto widget = MacroConditionFactory::CreateWidget(type, window(),
*_entryData);
auto logic = (*_entryData)->GetLogicType();
if (IsRootNode()) {
_logicSelection->setCurrentIndex(static_cast<int>(logic));
} else {
_logicSelection->setCurrentIndex(static_cast<int>(logic) -
logic_root_offset);
}
_section->SetContent(widget);
}
void MacroConditionEdit::Collapse(bool collapsed)
{
_section->Collapse(collapsed);
}
void MacroConditionEdit::ConditionSelectionChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
auto logic = (*_entryData)->GetLogicType();
_entryData->reset();
*_entryData = MacroConditionFactory::Create(idx);
(*_entryData)->SetLogicType(logic);
auto widget =
MacroConditionFactory::CreateWidget(idx, window(), *_entryData);
_section->SetContent(widget);
_section->Collapse(false);
}
void AdvSceneSwitcher::on_conditionAdd_clicked()
{
auto macro = getSelectedMacro();
if (!macro) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
bool root = macro->Conditions().size() == 0;
macro->Conditions().emplace_back(MacroConditionFactory::Create(0));
auto logic = root ? LogicType::ROOT_NONE : LogicType::NONE;
macro->Conditions().back()->SetLogicType(logic);
auto newEntry = new MacroConditionEdit(
this, &macro->Conditions().back(), 0, root);
ui->macroEditConditionLayout->addWidget(newEntry);
ui->macroEditConditionHelp->setVisible(false);
newEntry->Collapse(false);
}
void AdvSceneSwitcher::on_conditionRemove_clicked()
{
auto macro = getSelectedMacro();
if (!macro) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
if (macro->Conditions().empty()) {
return;
}
macro->Conditions().pop_back();
int count = ui->macroEditConditionLayout->count();
auto item = ui->macroEditConditionLayout->takeAt(count - 1);
if (item) {
auto widget = item->widget();
if (widget) {
widget->setVisible(false);
}
delete item;
}
if (count == 1) {
ui->macroEditConditionHelp->setVisible(true);
}
}

View File

@@ -0,0 +1,293 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-file.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/curl-helper.hpp"
#include <QTextStream>
#include <QFileDialog>
const int MacroConditionFile::id = 4;
bool MacroConditionFile::_registered = MacroConditionFactory::Register(
MacroConditionFile::id,
{MacroConditionFile::Create, MacroConditionFileEdit::Create,
"AdvSceneSwitcher.condition.file"});
static std::hash<std::string> strHash;
static size_t WriteCallback(void *contents, size_t size, size_t nmemb,
void *userp)
{
((std::string *)userp)->append((char *)contents, size * nmemb);
return size * nmemb;
}
static std::string getRemoteData(std::string &url)
{
std::string readBuffer;
if (switcher->curl && f_curl_setopt && f_curl_perform) {
f_curl_setopt(switcher->curl, CURLOPT_URL, url.c_str());
f_curl_setopt(switcher->curl, CURLOPT_WRITEFUNCTION,
WriteCallback);
f_curl_setopt(switcher->curl, CURLOPT_WRITEDATA, &readBuffer);
f_curl_perform(switcher->curl);
}
return readBuffer;
}
bool MacroConditionFile::matchFileContent(QString &filedata)
{
if (_onlyMatchIfChanged) {
size_t newHash = strHash(filedata.toUtf8().constData());
if (newHash == _lastHash) {
return false;
}
_lastHash = newHash;
}
if (_useRegex) {
QRegExp rx(QString::fromStdString(_text));
return rx.exactMatch(filedata);
}
QString text = QString::fromStdString(_text);
return compareIgnoringLineEnding(text, filedata);
}
bool MacroConditionFile::checkRemoteFileContent()
{
std::string data = getRemoteData(_file);
QString qdata = QString::fromStdString(data);
return matchFileContent(qdata);
}
bool MacroConditionFile::checkLocalFileContent()
{
QString t = QString::fromStdString(_text);
QFile file(QString::fromStdString(_file));
if (_file.empty() || !file.open(QIODevice::ReadOnly)) {
return false;
}
if (_useTime) {
QDateTime newLastMod = QFileInfo(file).lastModified();
if (_lastMod == newLastMod) {
return false;
}
_lastMod = newLastMod;
}
QString filedata = QTextStream(&file).readAll();
bool match = matchFileContent(filedata);
file.close();
return match;
}
bool MacroConditionFile::CheckCondition()
{
if (_fileType == FileType::REMOTE) {
return checkRemoteFileContent();
} else {
return checkLocalFileContent();
}
}
bool MacroConditionFile::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_string(obj, "file", _file.c_str());
obs_data_set_string(obj, "text", _text.c_str());
obs_data_set_int(obj, "fileType", static_cast<int>(_fileType));
obs_data_set_bool(obj, "useRegex", _useRegex);
obs_data_set_bool(obj, "useTime", _useTime);
obs_data_set_bool(obj, "onlyMatchIfChanged", _onlyMatchIfChanged);
return true;
}
bool MacroConditionFile::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_file = obs_data_get_string(obj, "file");
_text = obs_data_get_string(obj, "text");
_fileType = static_cast<FileType>(obs_data_get_int(obj, "fileType"));
_useRegex = obs_data_get_bool(obj, "useRegex");
_useTime = obs_data_get_bool(obj, "useTime");
_onlyMatchIfChanged = obs_data_get_bool(obj, "onlyMatchIfChanged");
return true;
}
MacroConditionFileEdit::MacroConditionFileEdit(
QWidget *parent, std::shared_ptr<MacroConditionFile> entryData)
: QWidget(parent)
{
_fileType = new QComboBox();
_filePath = new QLineEdit();
_browseButton =
new QPushButton(obs_module_text("AdvSceneSwitcher.browse"));
_browseButton->setStyleSheet("border:1px solid gray;");
_matchText = new QPlainTextEdit();
_useRegex = new QCheckBox(
obs_module_text("AdvSceneSwitcher.fileTab.useRegExp"));
_checkModificationDate = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.fileTab.checkfileContentTime"));
_checkFileContent = new QCheckBox(
obs_module_text("AdvSceneSwitcher.fileTab.checkfileContent"));
QWidget::connect(_fileType, SIGNAL(currentIndexChanged(int)), this,
SLOT(FileTypeChanged(int)));
QWidget::connect(_filePath, SIGNAL(editingFinished()), this,
SLOT(FilePathChanged()));
QWidget::connect(_browseButton, SIGNAL(clicked()), this,
SLOT(BrowseButtonClicked()));
QWidget::connect(_matchText, SIGNAL(textChanged()), this,
SLOT(MatchTextChanged()));
QWidget::connect(_useRegex, SIGNAL(stateChanged(int)), this,
SLOT(UseRegexChanged(int)));
QWidget::connect(_checkModificationDate, SIGNAL(stateChanged(int)),
this, SLOT(CheckModificationDateChanged(int)));
QWidget::connect(_checkFileContent, SIGNAL(stateChanged(int)), this,
SLOT(OnlyMatchIfChangedChanged(int)));
_fileType->addItem(obs_module_text("AdvSceneSwitcher.fileTab.local"));
_fileType->addItem(obs_module_text("AdvSceneSwitcher.fileTab.remote"));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{fileType}}", _fileType},
{"{{filePath}}", _filePath},
{"{{browseButton}}", _browseButton},
{"{{matchText}}", _matchText},
{"{{useRegex}}", _useRegex},
{"{{checkModificationDate}}", _checkModificationDate},
{"{{checkFileContent}}", _checkFileContent},
};
QVBoxLayout *mainLayout = new QVBoxLayout;
QHBoxLayout *line1Layout = new QHBoxLayout;
QHBoxLayout *line2Layout = new QHBoxLayout;
QHBoxLayout *line3Layout = new QHBoxLayout;
placeWidgets(
obs_module_text("AdvSceneSwitcher.condition.file.entry.line1"),
line1Layout, widgetPlaceholders);
placeWidgets(
obs_module_text("AdvSceneSwitcher.condition.file.entry.line2"),
line2Layout, widgetPlaceholders, false);
placeWidgets(
obs_module_text("AdvSceneSwitcher.condition.file.entry.line3"),
line3Layout, widgetPlaceholders);
mainLayout->addLayout(line1Layout);
mainLayout->addLayout(line2Layout);
mainLayout->addLayout(line3Layout);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionFileEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_fileType->setCurrentIndex(static_cast<int>(_entryData->_fileType));
_filePath->setText(QString::fromStdString(_entryData->_file));
_matchText->setPlainText(QString::fromStdString(_entryData->_text));
_useRegex->setChecked(_entryData->_useRegex);
_checkModificationDate->setChecked(_entryData->_useTime);
_checkFileContent->setChecked(_entryData->_onlyMatchIfChanged);
}
void MacroConditionFileEdit::FileTypeChanged(int index)
{
if (_loading || !_entryData) {
return;
}
FileType type = static_cast<FileType>(index);
if (type == FileType::LOCAL) {
_browseButton->setDisabled(false);
_checkModificationDate->setDisabled(false);
} else {
_browseButton->setDisabled(true);
_checkModificationDate->setDisabled(true);
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_fileType = type;
}
void MacroConditionFileEdit::FilePathChanged()
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_file = _filePath->text().toUtf8().constData();
}
void MacroConditionFileEdit::BrowseButtonClicked()
{
if (_loading || !_entryData) {
return;
}
QString path = QFileDialog::getOpenFileName(
this,
tr(obs_module_text("AdvSceneSwitcher.fileTab.selectRead")),
QDir::currentPath(),
tr(obs_module_text("AdvSceneSwitcher.fileTab.anyFileType")));
if (path.isEmpty()) {
return;
}
_filePath->setText(path);
FilePathChanged();
}
void MacroConditionFileEdit::MatchTextChanged()
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_text = _matchText->toPlainText().toUtf8().constData();
}
void MacroConditionFileEdit::UseRegexChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_useRegex = state;
}
void MacroConditionFileEdit::CheckModificationDateChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_useTime = state;
}
void MacroConditionFileEdit::OnlyMatchIfChangedChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_onlyMatchIfChanged = state;
}

View File

@@ -0,0 +1,83 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-idle.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
const int MacroConditionIdle::id = 10;
bool MacroConditionIdle::_registered = MacroConditionFactory::Register(
MacroConditionIdle::id,
{MacroConditionIdle::Create, MacroConditionIdleEdit::Create,
"AdvSceneSwitcher.condition.idle"});
bool MacroConditionIdle::CheckCondition()
{
return secondsSinceLastInput() >= _duration.seconds;
}
bool MacroConditionIdle::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
_duration.Save(obj);
return true;
}
bool MacroConditionIdle::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_duration.Load(obj);
return true;
}
MacroConditionIdleEdit::MacroConditionIdleEdit(
QWidget *parent, std::shared_ptr<MacroConditionIdle> entryData)
: QWidget(parent)
{
_duration = new DurationSelection();
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(DurationUnitChanged(DurationUnit)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{duration}}", _duration},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.idle.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionIdleEdit::DurationChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.seconds = seconds;
}
void MacroConditionIdleEdit::DurationUnitChanged(DurationUnit unit)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.displayUnit = unit;
}
void MacroConditionIdleEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_duration->SetDuration(_entryData->_duration);
}

View File

@@ -0,0 +1,376 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-media.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
const int MacroConditionMedia::id = 5;
bool MacroConditionMedia::_registered = MacroConditionFactory::Register(
MacroConditionMedia::id,
{MacroConditionMedia::Create, MacroConditionMediaEdit::Create,
"AdvSceneSwitcher.condition.media"});
static std::map<MediaTimeRestriction, std::string> mediaTimeRestrictions = {
{MediaTimeRestriction::TIME_RESTRICTION_NONE,
"AdvSceneSwitcher.mediaTab.timeRestriction.none"},
{MediaTimeRestriction::TIME_RESTRICTION_SHORTER,
"AdvSceneSwitcher.mediaTab.timeRestriction.shorter"},
{MediaTimeRestriction::TIME_RESTRICTION_LONGER,
"AdvSceneSwitcher.mediaTab.timeRestriction.longer"},
{MediaTimeRestriction::TIME_RESTRICTION_REMAINING_SHORTER,
"AdvSceneSwitcher.mediaTab.timeRestriction.remainShorter"},
{MediaTimeRestriction::TIME_RESTRICTION_REMAINING_LONGER,
"AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger"},
};
static std::map<MediaState, std::string> mediaStates = {
{MediaState::OBS_MEDIA_STATE_NONE,
"AdvSceneSwitcher.mediaTab.states.none"},
{MediaState::OBS_MEDIA_STATE_PLAYING,
"AdvSceneSwitcher.mediaTab.states.playing"},
{MediaState::OBS_MEDIA_STATE_OPENING,
"AdvSceneSwitcher.mediaTab.states.opening"},
{MediaState::OBS_MEDIA_STATE_BUFFERING,
"AdvSceneSwitcher.mediaTab.states.buffering"},
{MediaState::OBS_MEDIA_STATE_PAUSED,
"AdvSceneSwitcher.mediaTab.states.paused"},
{MediaState::OBS_MEDIA_STATE_STOPPED,
"AdvSceneSwitcher.mediaTab.states.stopped"},
{MediaState::OBS_MEDIA_STATE_ENDED,
"AdvSceneSwitcher.mediaTab.states.ended"},
{MediaState::OBS_MEDIA_STATE_ERROR,
"AdvSceneSwitcher.mediaTab.states.error"},
{MediaState::PLAYED_TO_END,
"AdvSceneSwitcher.mediaTab.states.playedToEnd"},
{MediaState::ANY, "AdvSceneSwitcher.mediaTab.states.any"},
};
MacroConditionMedia::~MacroConditionMedia()
{
obs_source_t *mediasource = obs_weak_source_get_source(_source);
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
signal_handler_disconnect(sh, "media_stopped", MediaStopped, this);
signal_handler_disconnect(sh, "media_ended", MediaEnded, this);
obs_source_release(mediasource);
}
bool matchTime(const int64_t currentTime, const int64_t duration,
const MediaTimeRestriction restriction, const int64_t time)
{
bool matchedTimeNone =
(restriction == MediaTimeRestriction::TIME_RESTRICTION_NONE);
bool matchedTimeLonger =
(restriction ==
MediaTimeRestriction::TIME_RESTRICTION_LONGER) &&
(currentTime > time);
bool matchedTimeShorter =
(restriction ==
MediaTimeRestriction::TIME_RESTRICTION_SHORTER) &&
(currentTime < time);
bool matchedTimeRemainLonger =
(restriction ==
MediaTimeRestriction::TIME_RESTRICTION_REMAINING_LONGER) &&
(duration > currentTime && duration - currentTime > time);
bool matchedTimeRemainShorter =
(restriction ==
MediaTimeRestriction::TIME_RESTRICTION_REMAINING_SHORTER) &&
(duration > currentTime && duration - currentTime < time);
return matchedTimeNone || matchedTimeLonger || matchedTimeShorter ||
matchedTimeRemainLonger || matchedTimeRemainShorter;
}
bool MacroConditionMedia::CheckCondition()
{
if (!_source) {
return false;
}
bool match = false;
obs_source_t *source = obs_weak_source_get_source(_source);
auto duration = obs_source_media_get_duration(source);
auto time = obs_source_media_get_time(source);
obs_media_state currentState = obs_source_media_get_state(source);
obs_source_release(source);
// To be able to compare to obs_media_state more easily
int expectedState = static_cast<int>(_state);
bool matchedStopped = expectedState == OBS_MEDIA_STATE_STOPPED &&
_stopped;
// The signal for the state ended is intentionally not used here
// so matchedEnded can be used to specify the end of a VLC playlist
// by two consequtive matches of OBS_MEDIA_STATE_ENDED
//
// This was done to reduce the likelyhood of interpreting a single
// OBS_MEDIA_STATE_ENDED caught by obs_source_media_get_state()
// as the end of the playlist of the VLC source, while actually being
// in the middle of switching to the next item of the playlist
//
// If there is a separate obs_media_sate in the future for the
// "end of playlist reached" signal the line below can be used
// and an additional check for this new singal can be introduced
//
// bool matchedEnded = _state == OBS_MEDIA_STATE_ENDED && _ended;
bool ended = false;
if (currentState == OBS_MEDIA_STATE_ENDED) {
ended = _previousStateEnded;
_previousStateEnded = true;
} else {
_previousStateEnded = false;
}
bool matchedEnded = ended && (expectedState == OBS_MEDIA_STATE_ENDED);
// match if playedToEnd was true in last interval
// and playback is currently ended
bool matchedPlayedToEnd = _state == MediaState::PLAYED_TO_END &&
_playedToEnd && ended;
// interval * 2 to make sure not to miss any state changes
// which happened during check of the conditions
_playedToEnd = _playedToEnd ||
(duration - time <= switcher->interval * 2);
// reset for next check
if (ended) {
_playedToEnd = false;
}
_stopped = false;
_ended = false;
bool matchedState =
(currentState == expectedState || _state == MediaState::ANY) ||
matchedStopped || matchedEnded || matchedPlayedToEnd;
bool matchedTime =
matchTime(time, duration, _restriction, _time.seconds * 1000);
bool matched = matchedState && matchedTime;
if (matched && !_alreadyMatched) {
match = true;
}
_alreadyMatched = matched;
return match;
}
bool MacroConditionMedia::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str());
obs_data_set_int(obj, "state", static_cast<int>(_state));
obs_data_set_int(obj, "restriction", static_cast<int>(_restriction));
_time.Save(obj);
return true;
}
bool MacroConditionMedia::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
const char *sourceName = obs_data_get_string(obj, "source");
_source = GetWeakSourceByName(sourceName);
_state = static_cast<MediaState>(obs_data_get_int(obj, "state"));
_restriction = static_cast<MediaTimeRestriction>(
obs_data_get_int(obj, "restriction"));
_time.Load(obj);
obs_source_t *mediasource = obs_weak_source_get_source(_source);
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
signal_handler_connect(sh, "media_stopped", MediaStopped, this);
signal_handler_connect(sh, "media_ended", MediaEnded, this);
obs_source_release(mediasource);
return true;
}
void MacroConditionMedia::ClearSignalHandler()
{
obs_source_t *mediasource = obs_weak_source_get_source(_source);
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
signal_handler_disconnect(sh, "media_stopped", MediaStopped, this);
signal_handler_disconnect(sh, "media_ended", MediaEnded, this);
obs_source_release(mediasource);
}
void MacroConditionMedia::ResetSignalHandler()
{
obs_source_t *mediasource = obs_weak_source_get_source(_source);
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
signal_handler_disconnect(sh, "media_stopped", MediaStopped, this);
signal_handler_disconnect(sh, "media_ended", MediaEnded, this);
signal_handler_connect(sh, "media_stopped", MediaStopped, this);
signal_handler_connect(sh, "media_ended", MediaEnded, this);
obs_source_release(mediasource);
}
void MacroConditionMedia::MediaStopped(void *data, calldata_t *)
{
MacroConditionMedia *media = static_cast<MacroConditionMedia *>(data);
media->_stopped = true;
}
void MacroConditionMedia::MediaEnded(void *data, calldata_t *)
{
MacroConditionMedia *media = static_cast<MacroConditionMedia *>(data);
media->_ended = true;
}
static void populateMediaTimeRestrictions(QComboBox *list)
{
for (auto entry : mediaTimeRestrictions) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
static void populateMediaStates(QComboBox *list)
{
for (auto entry : mediaStates) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroConditionMediaEdit::MacroConditionMediaEdit(
QWidget *parent, std::shared_ptr<MacroConditionMedia> entryData)
: QWidget(parent)
{
_mediaSources = new QComboBox();
_states = new QComboBox();
_timeRestrictions = new QComboBox();
_time = new DurationSelection();
QWidget::connect(_mediaSources,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(SourceChanged(const QString &)));
QWidget::connect(_states, SIGNAL(currentIndexChanged(int)), this,
SLOT(StateChanged(int)));
QWidget::connect(_timeRestrictions, SIGNAL(currentIndexChanged(int)),
this, SLOT(TimeRestrictionChanged(int)));
QWidget::connect(_time, SIGNAL(DurationChanged(double)), this,
SLOT(TimeChanged(double)));
QWidget::connect(_time, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(TimeUnitChanged(DurationUnit)));
AdvSceneSwitcher::populateMediaSelection(_mediaSources);
populateMediaStates(_states);
populateMediaTimeRestrictions(_timeRestrictions);
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{mediaSources}}", _mediaSources},
{"{{states}}", _states},
{"{{timeRestrictions}}", _timeRestrictions},
{"{{time}}", _time},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.media.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionMediaEdit::SourceChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->ClearSignalHandler();
_entryData->_source = GetWeakSourceByQString(text);
_entryData->ResetSignalHandler();
}
MediaState getMediaStateFromIdx(int idx)
{
if (idx < static_cast<int>(MediaState::LAST_OBS_MEDIA_STATE)) {
return static_cast<MediaState>(idx);
} else {
return static_cast<MediaState>(
idx -
static_cast<int>(MediaState::LAST_OBS_MEDIA_STATE) +
custom_media_states_offset);
}
}
void MacroConditionMediaEdit::StateChanged(int index)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_state = getMediaStateFromIdx(index);
}
void MacroConditionMediaEdit::TimeRestrictionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
if (static_cast<MediaTimeRestriction>(index) ==
MediaTimeRestriction::TIME_RESTRICTION_NONE) {
_time->setDisabled(true);
} else {
_time->setDisabled(false);
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_restriction = static_cast<MediaTimeRestriction>(index);
}
void MacroConditionMediaEdit::TimeChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_time.seconds = seconds;
}
void MacroConditionMediaEdit::TimeUnitChanged(DurationUnit unit)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_time.displayUnit = unit;
}
int getIdxFromMediaState(MediaState state)
{
if (state < MediaState::LAST_OBS_MEDIA_STATE) {
return static_cast<int>(state);
} else {
return static_cast<int>(state) - custom_media_states_offset +
static_cast<int>(MediaState::LAST_OBS_MEDIA_STATE);
}
}
void MacroConditionMediaEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_mediaSources->setCurrentText(
GetWeakSourceName(_entryData->_source).c_str());
_states->setCurrentIndex(getIdxFromMediaState(_entryData->_state));
_timeRestrictions->setCurrentIndex(
static_cast<int>(_entryData->_restriction));
_time->SetDuration(_entryData->_time);
if (_entryData->_restriction ==
MediaTimeRestriction::TIME_RESTRICTION_NONE) {
_time->setDisabled(true);
}
}

View File

@@ -0,0 +1,92 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-plugin-state.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
const int MacroConditionPluginState::id = 11;
bool MacroConditionPluginState::_registered = MacroConditionFactory::Register(
MacroConditionPluginState::id,
{MacroConditionPluginState::Create,
MacroConditionPluginStateEdit::Create,
"AdvSceneSwitcher.condition.pluginState"});
static std::map<PluginStateCondition, std::string> pluginStateConditionTypes = {
{PluginStateCondition::SCENESWITCHED,
"AdvSceneSwitcher.condition.pluginState.state.sceneSwitched"},
};
bool MacroConditionPluginState::CheckCondition()
{
return switcher->macroSceneSwitched;
}
bool MacroConditionPluginState::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
return true;
}
bool MacroConditionPluginState::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_condition = static_cast<PluginStateCondition>(
obs_data_get_int(obj, "condition"));
return true;
}
static inline void populateConditionSelection(QComboBox *list)
{
for (auto entry : pluginStateConditionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroConditionPluginStateEdit::MacroConditionPluginStateEdit(
QWidget *parent, std::shared_ptr<MacroConditionPluginState> entryData)
: QWidget(parent)
{
_condition = new QComboBox();
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
populateConditionSelection(_condition);
QHBoxLayout *switchLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{condition}}", _condition},
};
placeWidgets(
obs_module_text("AdvSceneSwitcher.condition.pluginState.entry"),
switchLayout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(switchLayout);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionPluginStateEdit::ConditionChanged(int cond)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_condition = static_cast<PluginStateCondition>(cond);
}
void MacroConditionPluginStateEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
}

View File

@@ -0,0 +1,110 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-process.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include <regex>
const int MacroConditionProcess::id = 9;
bool MacroConditionProcess::_registered = MacroConditionFactory::Register(
MacroConditionProcess::id,
{MacroConditionProcess::Create, MacroConditionProcessEdit::Create,
"AdvSceneSwitcher.condition.process"});
bool MacroConditionProcess::CheckCondition()
{
std::string title;
QStringList runningProcesses;
QString proc = QString::fromStdString(_process);
GetCurrentWindowTitle(title);
GetProcessList(runningProcesses);
bool equals = runningProcesses.contains(proc);
bool matches = runningProcesses.indexOf(QRegularExpression(proc)) != -1;
bool focus = !_focus || isInFocus(proc);
return (equals || matches) && focus;
}
bool MacroConditionProcess::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_string(obj, "process", _process.c_str());
obs_data_set_bool(obj, "focus", _focus);
return true;
}
bool MacroConditionProcess::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_process = obs_data_get_string(obj, "process");
_focus = obs_data_get_bool(obj, "focus");
return true;
}
MacroConditionProcessEdit::MacroConditionProcessEdit(
QWidget *parent, std::shared_ptr<MacroConditionProcess> entryData)
: QWidget(parent)
{
_processSelection = new QComboBox();
_processSelection->setEditable(true);
_processSelection->setMaxVisibleItems(20);
_focused = new QCheckBox();
QWidget::connect(_processSelection,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(ProcessChanged(const QString &)));
QWidget::connect(_focused, SIGNAL(stateChanged(int)), this,
SLOT(FocusChanged(int)));
AdvSceneSwitcher::populateProcessSelection(_processSelection);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{processes}}", _processSelection},
{"{{focused}}", _focused},
};
QHBoxLayout *mainLayout = new QHBoxLayout;
placeWidgets(
obs_module_text("AdvSceneSwitcher.condition.process.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionProcessEdit::ProcessChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_process = text.toStdString();
}
void MacroConditionProcessEdit::FocusChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_focus = state;
}
void MacroConditionProcessEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_processSelection->setCurrentText(_entryData->_process.c_str());
_focused->setChecked(_entryData->_focus);
}

View File

@@ -0,0 +1,138 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-recording.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
const int MacroConditionRecord::id = 8;
bool MacroConditionRecord::_registered = MacroConditionFactory::Register(
MacroConditionRecord::id,
{MacroConditionRecord::Create, MacroConditionRecordEdit::Create,
"AdvSceneSwitcher.condition.record"});
static std::map<RecordState, std::string> recordStates = {
{RecordState::STOP, "AdvSceneSwitcher.condition.record.state.stop"},
{RecordState::PAUSE, "AdvSceneSwitcher.condition.record.state.pause"},
{RecordState::START, "AdvSceneSwitcher.condition.record.state.start"},
};
bool MacroConditionRecord::CheckCondition()
{
bool stateMatch = false;
switch (_recordState) {
case RecordState::STOP:
stateMatch = !obs_frontend_recording_active();
break;
case RecordState::PAUSE:
stateMatch = obs_frontend_recording_paused();
break;
case RecordState::START:
stateMatch = obs_frontend_recording_active();
break;
default:
break;
}
if (!stateMatch) {
_duration.Reset();
return false;
}
return _duration.DurationReached();
}
bool MacroConditionRecord::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_int(obj, "state", static_cast<int>(_recordState));
_duration.Save(obj);
return true;
}
bool MacroConditionRecord::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_recordState = static_cast<RecordState>(obs_data_get_int(obj, "state"));
_duration.Load(obj);
return true;
}
static inline void populateStateSelection(QComboBox *list)
{
for (auto entry : recordStates) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroConditionRecordEdit::MacroConditionRecordEdit(
QWidget *parent, std::shared_ptr<MacroConditionRecord> entryData)
: QWidget(parent)
{
_recordState = new QComboBox();
_duration = new DurationSelection();
QWidget::connect(_recordState, SIGNAL(currentIndexChanged(int)), this,
SLOT(StateChanged(int)));
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(DurationUnitChanged(DurationUnit)));
populateStateSelection(_recordState);
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{recordState}}", _recordState},
{"{{duration}}", _duration},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.record.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionRecordEdit::StateChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_recordState = static_cast<RecordState>(value);
}
void MacroConditionRecordEdit::DurationChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.seconds = seconds;
}
void MacroConditionRecordEdit::DurationUnitChanged(DurationUnit unit)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.displayUnit = unit;
}
void MacroConditionRecordEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_recordState->setCurrentIndex(
static_cast<int>(_entryData->_recordState));
_duration->SetDuration(_entryData->_duration);
}

View File

@@ -0,0 +1,140 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-region.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
const int MacroConditionRegion::id = 2;
bool MacroConditionRegion::_registered = MacroConditionFactory::Register(
MacroConditionRegion::id,
{MacroConditionRegion::Create, MacroConditionRegionEdit::Create,
"AdvSceneSwitcher.condition.region"});
bool MacroConditionRegion::CheckCondition()
{
std::pair<int, int> cursorPos = getCursorPos();
return cursorPos.first >= _minX && cursorPos.second >= _minY &&
cursorPos.first <= _maxX && cursorPos.second <= _maxY;
}
bool MacroConditionRegion::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_int(obj, "minX", _minX);
obs_data_set_int(obj, "minY", _minY);
obs_data_set_int(obj, "maxX", _maxX);
obs_data_set_int(obj, "maxY", _maxY);
return true;
}
bool MacroConditionRegion::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_minX = obs_data_get_int(obj, "minX");
_minY = obs_data_get_int(obj, "minY");
_maxX = obs_data_get_int(obj, "maxX");
_maxY = obs_data_get_int(obj, "maxY");
return true;
}
MacroConditionRegionEdit::MacroConditionRegionEdit(
QWidget *parent, std::shared_ptr<MacroConditionRegion> entryData)
: QWidget(parent)
{
_minX = new QSpinBox();
_minY = new QSpinBox();
_maxX = new QSpinBox();
_maxY = new QSpinBox();
_minX->setPrefix("Min X: ");
_minY->setPrefix("Min Y: ");
_maxX->setPrefix("Max X: ");
_maxY->setPrefix("Max Y: ");
_minX->setMinimum(-1000000);
_minY->setMinimum(-1000000);
_maxX->setMinimum(-1000000);
_maxY->setMinimum(-1000000);
_minX->setMaximum(1000000);
_minY->setMaximum(1000000);
_maxX->setMaximum(1000000);
_maxY->setMaximum(1000000);
QWidget::connect(_minX, SIGNAL(valueChanged(int)), this,
SLOT(MinXChanged(int)));
QWidget::connect(_minY, SIGNAL(valueChanged(int)), this,
SLOT(MinYChanged(int)));
QWidget::connect(_maxX, SIGNAL(valueChanged(int)), this,
SLOT(MaxXChanged(int)));
QWidget::connect(_maxY, SIGNAL(valueChanged(int)), this,
SLOT(MaxYChanged(int)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{minX}}", _minX},
{"{{minY}}", _minY},
{"{{maxX}}", _maxX},
{"{{maxY}}", _maxY}};
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.region.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionRegionEdit::MinXChanged(int pos)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_minX = pos;
}
void MacroConditionRegionEdit::MinYChanged(int pos)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_minY = pos;
}
void MacroConditionRegionEdit::MaxXChanged(int pos)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_maxX = pos;
}
void MacroConditionRegionEdit::MaxYChanged(int pos)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_maxY = pos;
}
void MacroConditionRegionEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_minX->setValue(_entryData->_minX);
_minY->setValue(_entryData->_minY);
_maxX->setValue(_entryData->_maxX);
_maxY->setValue(_entryData->_maxY);
}

View File

@@ -0,0 +1,150 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-scene.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
const int MacroConditionScene::id = 0;
bool MacroConditionScene::_registered = MacroConditionFactory::Register(
MacroConditionScene::id,
{MacroConditionScene::Create, MacroConditionSceneEdit::Create,
"AdvSceneSwitcher.condition.scene"});
static std::map<SceneType, std::string> sceneTypes = {
{SceneType::CURRENT, "AdvSceneSwitcher.condition.scene.type.current"},
{SceneType::PREVIOUS, "AdvSceneSwitcher.condition.scene.type.previous"},
};
bool MacroConditionScene::CheckCondition()
{
bool sceneMatch = false;
if (_type == SceneType::CURRENT) {
obs_source_t *rawScene = obs_frontend_get_current_scene();
OBSWeakSource currentScene =
obs_source_get_weak_source(rawScene);
sceneMatch = currentScene == _scene;
obs_weak_source_release(currentScene);
obs_source_release(rawScene);
} else {
sceneMatch = switcher->previousScene == _scene;
}
if (!sceneMatch) {
_duration.Reset();
return false;
}
return _duration.DurationReached();
}
bool MacroConditionScene::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_string(obj, "scene", GetWeakSourceName(_scene).c_str());
obs_data_set_int(obj, "type", static_cast<int>(_type));
_duration.Save(obj);
return true;
}
bool MacroConditionScene::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_scene = GetWeakSourceByName(obs_data_get_string(obj, "scene"));
_type = static_cast<SceneType>(obs_data_get_int(obj, "type"));
_duration.Load(obj);
return true;
}
static inline void populateTypeSelection(QComboBox *list)
{
for (auto entry : sceneTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroConditionSceneEdit::MacroConditionSceneEdit(
QWidget *parent, std::shared_ptr<MacroConditionScene> entryData)
: QWidget(parent)
{
_sceneSelection = new QComboBox();
_sceneType = new QComboBox();
_duration = new DurationSelection();
QWidget::connect(_sceneSelection,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(SceneChanged(const QString &)));
QWidget::connect(_sceneType, SIGNAL(currentIndexChanged(int)), this,
SLOT(TypeChanged(int)));
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(DurationUnitChanged(DurationUnit)));
AdvSceneSwitcher::populateSceneSelection(_sceneSelection);
populateTypeSelection(_sceneType);
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _sceneSelection},
{"{{sceneType}}", _sceneType},
{"{{duration}}", _duration},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.scene.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionSceneEdit::SceneChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_scene = GetWeakSourceByQString(text);
}
void MacroConditionSceneEdit::TypeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_type = static_cast<SceneType>(value);
}
void MacroConditionSceneEdit::DurationChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.seconds = seconds;
}
void MacroConditionSceneEdit::DurationUnitChanged(DurationUnit unit)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.displayUnit = unit;
}
void MacroConditionSceneEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_sceneSelection->setCurrentText(
GetWeakSourceName(_entryData->_scene).c_str());
_sceneType->setCurrentIndex(static_cast<int>(_entryData->_type));
_duration->SetDuration(_entryData->_duration);
}

View File

@@ -0,0 +1,134 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-streaming.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
const int MacroConditionStream::id = 7;
bool MacroConditionStream::_registered = MacroConditionFactory::Register(
MacroConditionStream::id,
{MacroConditionStream::Create, MacroConditionStreamEdit::Create,
"AdvSceneSwitcher.condition.stream"});
static std::map<StreamState, std::string> streamStates = {
{StreamState::STOP, "AdvSceneSwitcher.condition.stream.state.stop"},
{StreamState::START, "AdvSceneSwitcher.condition.stream.state.start"},
};
bool MacroConditionStream::CheckCondition()
{
bool stateMatch = false;
switch (_streamState) {
case StreamState::STOP:
stateMatch = !obs_frontend_streaming_active();
break;
case StreamState::START:
stateMatch = obs_frontend_streaming_active();
break;
default:
break;
}
if (!stateMatch) {
_duration.Reset();
return false;
}
return _duration.DurationReached();
}
bool MacroConditionStream::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_int(obj, "state", static_cast<int>(_streamState));
_duration.Save(obj);
return true;
}
bool MacroConditionStream::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_streamState = static_cast<StreamState>(obs_data_get_int(obj, "state"));
_duration.Load(obj);
return true;
}
static inline void populateStateSelection(QComboBox *list)
{
for (auto entry : streamStates) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroConditionStreamEdit::MacroConditionStreamEdit(
QWidget *parent, std::shared_ptr<MacroConditionStream> entryData)
: QWidget(parent)
{
_streamState = new QComboBox();
_duration = new DurationSelection();
QWidget::connect(_streamState, SIGNAL(currentIndexChanged(int)), this,
SLOT(StateChanged(int)));
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(DurationUnitChanged(DurationUnit)));
populateStateSelection(_streamState);
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{streamState}}", _streamState},
{"{{duration}}", _duration},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.stream.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionStreamEdit::StateChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_streamState = static_cast<StreamState>(value);
}
void MacroConditionStreamEdit::DurationChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.seconds = seconds;
}
void MacroConditionStreamEdit::DurationUnitChanged(DurationUnit unit)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.displayUnit = unit;
}
void MacroConditionStreamEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_streamState->setCurrentIndex(
static_cast<int>(_entryData->_streamState));
_duration->SetDuration(_entryData->_duration);
}

View File

@@ -0,0 +1,363 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-video.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include <QFileDialog>
#include <QBuffer>
#include <QToolTip>
#include <QMessageBox>
const int MacroConditionVideo::id = 6;
bool MacroConditionVideo::_registered = MacroConditionFactory::Register(
MacroConditionVideo::id,
{MacroConditionVideo::Create, MacroConditionVideoEdit::Create,
"AdvSceneSwitcher.condition.video"});
static std::map<VideoCondition, std::string> conditionTypes = {
{VideoCondition::MATCH,
"AdvSceneSwitcher.condition.video.condition.match"},
{VideoCondition::DIFFER,
"AdvSceneSwitcher.condition.video.condition.differ"},
{VideoCondition::HAS_NOT_CHANGED,
"AdvSceneSwitcher.condition.video.condition.hasNotChanged"},
{VideoCondition::HAS_CHANGED,
"AdvSceneSwitcher.condition.video.condition.hasChanged"},
{VideoCondition::NO_IMAGE,
"AdvSceneSwitcher.condition.video.condition.noImage"},
};
bool requiresFileInput(VideoCondition t)
{
return t == VideoCondition::MATCH || t == VideoCondition::DIFFER;
}
bool MacroConditionVideo::CheckCondition()
{
bool match = false;
if (_screenshotData) {
if (_screenshotData->done) {
bool imageMatch = Compare();
if (!imageMatch) {
_duration.Reset();
}
if (imageMatch && _duration.DurationReached()) {
match = true;
}
if (!requiresFileInput(_condition)) {
_matchImage = std::move(_screenshotData->image);
}
_screenshotData.reset(nullptr);
}
}
GetScreenshot();
return match;
}
bool MacroConditionVideo::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_string(obj, "videoSource",
GetWeakSourceName(_videoSource).c_str());
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
_duration.Save(obj);
obs_data_set_string(obj, "filePath", _file.c_str());
return true;
}
bool MacroConditionVideo::Load(obs_data_t *obj)
{
const char *videoSourceName = obs_data_get_string(obj, "videoSource");
_videoSource = GetWeakSourceByName(videoSourceName);
_condition =
static_cast<VideoCondition>(obs_data_get_int(obj, "condition"));
_duration.Load(obj);
_file = obs_data_get_string(obj, "filePath");
if (requiresFileInput(_condition)) {
(void)LoadImageFromFile();
}
return true;
}
void MacroConditionVideo::GetScreenshot()
{
auto source = obs_weak_source_get_source(_videoSource);
_screenshotData = std::make_unique<AdvSSScreenshotObj>(source);
obs_source_release(source);
}
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_RGBX8888);
return true;
}
bool MacroConditionVideo::Compare()
{
switch (_condition) {
case VideoCondition::MATCH:
return _screenshotData->image == _matchImage;
case VideoCondition::DIFFER:
return _screenshotData->image != _matchImage;
case VideoCondition::HAS_CHANGED:
return _screenshotData->image != _matchImage;
case VideoCondition::HAS_NOT_CHANGED:
return _screenshotData->image == _matchImage;
case VideoCondition::NO_IMAGE:
return _screenshotData->image.isNull();
default:
break;
}
return false;
}
static inline void populateConditionSelection(QComboBox *list)
{
for (auto entry : conditionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroConditionVideoEdit::MacroConditionVideoEdit(
QWidget *parent, std::shared_ptr<MacroConditionVideo> entryData)
: QWidget(parent)
{
_videoSelection = new QComboBox();
_condition = new QComboBox();
_duration = new DurationSelection();
_filePath = new QLineEdit();
_browseButton =
new QPushButton(obs_module_text("AdvSceneSwitcher.browse"));
_filePath->setFixedWidth(100);
_browseButton->setStyleSheet("border:1px solid gray;");
QWidget::connect(_videoSelection,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(SourceChanged(const QString &)));
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(DurationUnitChanged(DurationUnit)));
QWidget::connect(_filePath, SIGNAL(editingFinished()), this,
SLOT(FilePathChanged()));
QWidget::connect(_browseButton, SIGNAL(clicked()), this,
SLOT(BrowseButtonClicked()));
AdvSceneSwitcher::populateVideoSelection(_videoSelection, false);
populateConditionSelection(_condition);
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{videoSources}}", _videoSelection},
{"{{condition}}", _condition},
{"{{duration}}", _duration},
{"{{filePath}}", _filePath},
{"{{browseButton}}", _browseButton},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.video.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionVideoEdit::UpdatePreviewTooltip()
{
if (!_entryData) {
return;
}
if (!requiresFileInput(_entryData->_condition)) {
this->setToolTip("");
return;
}
QImage preview = _entryData->GetMatchImage().scaled(
{300, 300}, Qt::KeepAspectRatio);
QByteArray data;
QBuffer buffer(&data);
if (!preview.save(&buffer, "PNG")) {
return;
}
QString html =
QString("<html><img src='data:image/png;base64, %0'/></html>")
.arg(QString(data.toBase64()));
this->setToolTip(html);
}
void MacroConditionVideoEdit::SetFilePath(const QString &text)
{
_filePath->setText(text);
FilePathChanged();
}
void MacroConditionVideoEdit::SourceChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_videoSource = GetWeakSourceByQString(text);
}
void MacroConditionVideoEdit::ConditionChanged(int cond)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_condition = static_cast<VideoCondition>(cond);
if (requiresFileInput(_entryData->_condition)) {
_filePath->show();
_browseButton->show();
} else {
_filePath->hide();
_browseButton->hide();
}
// Reload image data to avoid incorrect matches.
//
// Condition type HAS_NOT_CHANGED will use matchImage to store previous
// frame of video source, which will differ from the image stored at
// specified file location.
if (_entryData->LoadImageFromFile()) {
UpdatePreviewTooltip();
}
}
void MacroConditionVideoEdit::FilePathChanged()
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_file = _filePath->text().toUtf8().constData();
if (_entryData->LoadImageFromFile()) {
UpdatePreviewTooltip();
}
}
void MacroConditionVideoEdit::BrowseButtonClicked()
{
if (_loading || !_entryData) {
return;
}
QString path;
bool useExistingFile = false;
// Ask whether to create screenshot or to select existing file
if (_entryData->_videoSource) {
QMessageBox msgBox(
QMessageBox::Question,
obs_module_text("AdvSceneSwitcher.windowTitle"),
obs_module_text(
"AdvSceneSwitcher.condition.video.askFileAction"),
QMessageBox::Yes | QMessageBox::No);
msgBox.setWindowFlags(Qt::Window | Qt::WindowTitleHint |
Qt::CustomizeWindowHint);
msgBox.setButtonText(
QMessageBox::Yes,
obs_module_text(
"AdvSceneSwitcher.condition.video.askFileAction.file"));
msgBox.setButtonText(
QMessageBox::No,
obs_module_text(
"AdvSceneSwitcher.condition.video.askFileAction.screenshot"));
useExistingFile = msgBox.exec() == QMessageBox::Yes;
}
if (useExistingFile) {
path = QFileDialog::getOpenFileName(this);
if (path.isEmpty()) {
return;
}
} else {
auto source =
obs_weak_source_get_source(_entryData->_videoSource);
auto screenshot = std::make_unique<AdvSSScreenshotObj>(source);
obs_source_release(source);
path = QFileDialog::getSaveFileName(this);
if (path.isEmpty()) {
return;
}
QFile file(path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return;
}
if (!screenshot->done) { // Screenshot usually completed by now
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (!screenshot->done) {
DisplayMessage("Failed to get screenshot of source!");
return;
}
screenshot->image.save(path);
}
SetFilePath(path);
}
void MacroConditionVideoEdit::DurationChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.seconds = seconds;
}
void MacroConditionVideoEdit::DurationUnitChanged(DurationUnit unit)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.displayUnit = unit;
}
void MacroConditionVideoEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_videoSelection->setCurrentText(
GetWeakSourceName(_entryData->_videoSource).c_str());
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
_duration->SetDuration(_entryData->_duration);
_filePath->setText(QString::fromStdString(_entryData->_file));
if (!requiresFileInput(_entryData->_condition)) {
_filePath->hide();
_browseButton->hide();
}
}

View File

@@ -0,0 +1,195 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-window.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include <regex>
const int MacroConditionWindow::id = 1;
bool MacroConditionWindow::_registered = MacroConditionFactory::Register(
MacroConditionWindow::id,
{MacroConditionWindow::Create, MacroConditionWindowEdit::Create,
"AdvSceneSwitcher.condition.window"});
bool MacroConditionWindow::CheckWindowTitleSwitchDirect(
std::string &currentWindowTitle)
{
bool focus = (!_focus || _window == currentWindowTitle);
bool fullscreen = (!_fullscreen || isFullscreen(_window));
bool max = (!_maximized || isMaximized(_window));
return focus && fullscreen && max;
}
bool MacroConditionWindow::CheckWindowTitleSwitchRegex(
std::string &currentWindowTitle, std::vector<std::string> &windowList)
{
bool match = false;
for (auto &window : windowList) {
try {
std::regex expr(_window);
if (!std::regex_match(window, expr)) {
continue;
}
} catch (const std::regex_error &) {
}
bool focus = (!_focus || window == currentWindowTitle);
bool fullscreen = (!_fullscreen || isFullscreen(window));
bool max = (!_maximized || isMaximized(window));
if (focus && fullscreen && max) {
match = true;
break;
}
}
return match;
}
bool MacroConditionWindow::CheckCondition()
{
std::string currentWindowTitle;
GetCurrentWindowTitle(currentWindowTitle);
std::vector<std::string> windowList;
GetWindowList(windowList);
bool match = false;
if (std::find(windowList.begin(), windowList.end(), _window) !=
windowList.end()) {
match = CheckWindowTitleSwitchDirect(currentWindowTitle);
} else {
match = CheckWindowTitleSwitchRegex(currentWindowTitle,
windowList);
}
return match;
}
bool MacroConditionWindow::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_string(obj, "window", _window.c_str());
obs_data_set_bool(obj, "fullscreen", _fullscreen);
obs_data_set_bool(obj, "maximized", _maximized);
obs_data_set_bool(obj, "focus", _focus);
return true;
}
bool MacroConditionWindow::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_window = obs_data_get_string(obj, "window");
_fullscreen = obs_data_get_bool(obj, "fullscreen");
#if __APPLE__
// TODO: Implement maximized check on MacOS
_maximized = false;
#else
_maximized = obs_data_get_bool(obj, "maximized");
#endif
_focus = obs_data_get_bool(obj, "focus");
return true;
}
MacroConditionWindowEdit::MacroConditionWindowEdit(
QWidget *parent, std::shared_ptr<MacroConditionWindow> entryData)
: QWidget(parent)
{
_windowSelection = new QComboBox();
_windowSelection->setEditable(true);
_windowSelection->setMaxVisibleItems(20);
_fullscreen = new QCheckBox();
_maximized = new QCheckBox();
_focused = new QCheckBox();
QWidget::connect(_windowSelection,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(WindowChanged(const QString &)));
QWidget::connect(_fullscreen, SIGNAL(stateChanged(int)), this,
SLOT(FullscreenChanged(int)));
QWidget::connect(_maximized, SIGNAL(stateChanged(int)), this,
SLOT(MaximizedChanged(int)));
QWidget::connect(_focused, SIGNAL(stateChanged(int)), this,
SLOT(FocusChanged(int)));
AdvSceneSwitcher::populateWindowSelection(_windowSelection);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{windows}}", _windowSelection},
{"{{fullscreen}}", _fullscreen},
{"{{maximized}}", _maximized},
{"{{focused}}", _focused},
};
QVBoxLayout *mainLayout = new QVBoxLayout;
QHBoxLayout *line1Layout = new QHBoxLayout;
QHBoxLayout *line2Layout = new QHBoxLayout;
placeWidgets(obs_module_text(
"AdvSceneSwitcher.condition.window.entry.line1"),
line1Layout, widgetPlaceholders);
placeWidgets(obs_module_text(
"AdvSceneSwitcher.condition.window.entry.line2"),
line2Layout, widgetPlaceholders);
mainLayout->addLayout(line1Layout);
mainLayout->addLayout(line2Layout);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionWindowEdit::WindowChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_window = text.toStdString();
}
void MacroConditionWindowEdit::FullscreenChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_fullscreen = state;
}
void MacroConditionWindowEdit::MaximizedChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_maximized = state;
}
void MacroConditionWindowEdit::FocusChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_focus = state;
}
void MacroConditionWindowEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_windowSelection->setCurrentText(_entryData->_window.c_str());
_fullscreen->setChecked(_entryData->_fullscreen);
_maximized->setChecked(_entryData->_maximized);
_focused->setChecked(_entryData->_focus);
}

290
src/macro-tab.cpp Normal file
View File

@@ -0,0 +1,290 @@
#include "headers/macro.hpp"
#include "headers/macro-action-edit.hpp"
#include "headers/macro-condition-edit.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/name-dialog.hpp"
#include <QMenu>
static QMetaObject::Connection addPulse;
const auto conditionsCollapseThreshold = 4;
const auto actionsCollapseThreshold = 2;
bool macroNameExists(std::string name)
{
for (auto &m : switcher->macros) {
if (m.Name() == name) {
return true;
}
}
return false;
}
bool AdvSceneSwitcher::addNewMacro(std::string &name)
{
QString format{
obs_module_text("AdvSceneSwitcher.macroTab.defaultname")};
int i = 1;
QString placeHolderText = format.arg(i);
while ((macroNameExists(placeHolderText.toUtf8().constData()))) {
placeHolderText = format.arg(++i);
}
bool accepted = AdvSSNameDialog::AskForName(
this, obs_module_text("AdvSceneSwitcher.macroTab.add"),
obs_module_text("AdvSceneSwitcher.macroTab.name"), name,
placeHolderText);
if (!accepted) {
return false;
}
if (name.empty()) {
return false;
}
if (macroNameExists(name)) {
DisplayMessage(
obs_module_text("AdvSceneSwitcher.macroTab.exists"));
return false;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->macros.emplace_back(name);
}
return true;
}
void AdvSceneSwitcher::on_macroAdd_clicked()
{
std::string name;
if (!addNewMacro(name)) {
return;
}
QString text = QString::fromStdString(name);
QListWidgetItem *item = new QListWidgetItem(text, ui->macros);
item->setData(Qt::UserRole, text);
ui->macros->setCurrentItem(item);
ui->macroAdd->disconnect(addPulse);
ui->macroHelp->setVisible(false);
}
void AdvSceneSwitcher::on_macroRemove_clicked()
{
QListWidgetItem *item = ui->macros->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->macros->currentRow();
switcher->macros.erase(switcher->macros.begin() + idx);
}
delete item;
if (ui->macros->count() == 0) {
ui->macroHelp->setVisible(true);
}
}
void AdvSceneSwitcher::on_macroUp_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
int index = ui->macros->currentRow();
if (index != -1 && index != 0) {
ui->macros->insertItem(index - 1, ui->macros->takeItem(index));
ui->macros->setCurrentRow(index - 1);
iter_swap(switcher->macros.begin() + index,
switcher->macros.begin() + index - 1);
}
}
void AdvSceneSwitcher::on_macroDown_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
int index = ui->macros->currentRow();
if (index != -1 && index != ui->macros->count() - 1) {
ui->macros->insertItem(index + 1, ui->macros->takeItem(index));
ui->macros->setCurrentRow(index + 1);
iter_swap(switcher->macros.begin() + index,
switcher->macros.begin() + index + 1);
}
}
void AdvSceneSwitcher::on_macroName_editingFinished()
{
bool nameValid = true;
Macro *macro = getSelectedMacro();
if (!macro) {
return;
}
QString newName = ui->macroName->text();
QString oldName = QString::fromStdString(macro->Name());
if (newName.isEmpty() || newName == oldName) {
nameValid = false;
}
if (nameValid && macroNameExists(newName.toUtf8().constData())) {
DisplayMessage(
obs_module_text("AdvSceneSwitcher.macroTab.exists"));
nameValid = false;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
if (nameValid) {
macro->SetName(newName.toUtf8().constData());
QListWidgetItem *item = ui->macros->currentItem();
item->setData(Qt::UserRole, newName);
item->setText(newName);
} else {
ui->macroName->setText(oldName);
}
}
}
void AdvSceneSwitcher::SetEditMacro(Macro &m)
{
ui->macroName->setText(m.Name().c_str());
clearLayout(ui->macroEditConditionLayout);
clearLayout(ui->macroEditActionLayout);
bool collapse = m.Conditions().size() > conditionsCollapseThreshold;
bool root = true;
for (auto &c : m.Conditions()) {
auto newEntry = new MacroConditionEdit(this, &c, c->GetId(),
root, collapse);
ui->macroEditConditionLayout->addWidget(newEntry);
ui->macroEditConditionHelp->setVisible(false);
root = false;
}
collapse = m.Actions().size() > actionsCollapseThreshold;
for (auto &a : m.Actions()) {
auto newEntry =
new MacroActionEdit(this, &a, a->GetId(), collapse);
ui->macroEditActionLayout->addWidget(newEntry);
ui->macroEditActionHelp->setVisible(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);
}
}
Macro *AdvSceneSwitcher::getSelectedMacro()
{
Macro *macro = nullptr;
QListWidgetItem *item = ui->macros->currentItem();
if (!item) {
return macro;
}
QString name = item->data(Qt::UserRole).toString();
for (auto &m : switcher->macros) {
if (name.compare(m.Name().c_str()) == 0) {
macro = &m;
break;
}
}
return macro;
}
void AdvSceneSwitcher::on_macros_currentRowChanged(int idx)
{
if (loading) {
return;
}
if (idx == -1) {
ui->macroEdit->setDisabled(true);
return;
}
QListWidgetItem *item = ui->macros->item(idx);
QString macroName = item->data(Qt::UserRole).toString();
for (auto &m : switcher->macros) {
if (macroName.compare(m.Name().c_str()) == 0) {
SetEditMacro(m);
break;
}
}
}
void AdvSceneSwitcher::setupMacroTab()
{
for (auto &m : switcher->macros) {
QString text = QString::fromStdString(m.Name());
QListWidgetItem *item = new QListWidgetItem(text, ui->macros);
item->setData(Qt::UserRole, text);
}
if (switcher->macros.size() == 0) {
addPulse = PulseWidget(ui->macroAdd, QColor(Qt::green));
ui->macroHelp->setVisible(true);
} else {
ui->macroHelp->setVisible(false);
}
ui->macros->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->macros, SIGNAL(customContextMenuRequested(QPoint)), this,
SLOT(showMacroContextMenu(QPoint)));
ui->macroEdit->setDisabled(true);
}
void AdvSceneSwitcher::showMacroContextMenu(const QPoint &pos)
{
QPoint globalPos = ui->macros->mapToGlobal(pos);
QMenu myMenu;
myMenu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.copy"),
this, SLOT(copyMacro()));
myMenu.exec(globalPos);
}
void AdvSceneSwitcher::copyMacro()
{
obs_data_t *data = obs_data_create();
getSelectedMacro()->Save(data);
std::string name;
if (!addNewMacro(name)) {
obs_data_release(data);
return;
}
switcher->macros.back().Load(data);
switcher->macros.back().SetName(name);
obs_data_release(data);
QString text = QString::fromStdString(name);
QListWidgetItem *item = new QListWidgetItem(text, ui->macros);
item->setData(Qt::UserRole, text);
ui->macros->setCurrentItem(item);
}

298
src/macro.cpp Normal file
View File

@@ -0,0 +1,298 @@
#include "headers/macro.hpp"
#include "headers/macro-action-edit.hpp"
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-action-switch-scene.hpp"
const std::map<LogicType, LogicTypeInfo> MacroCondition::logicTypes = {
{LogicType::NONE, {"AdvSceneSwitcher.logic.none"}},
{LogicType::AND, {"AdvSceneSwitcher.logic.and"}},
{LogicType::OR, {"AdvSceneSwitcher.logic.or"}},
{LogicType::AND_NOT, {"AdvSceneSwitcher.logic.andNot"}},
{LogicType::OR_NOT, {"AdvSceneSwitcher.logic.orNot"}},
{LogicType::ROOT_NONE, {"AdvSceneSwitcher.logic.rootNone"}},
{LogicType::ROOT_NOT, {"AdvSceneSwitcher.logic.not"}},
};
Macro::Macro(std::string name) : _name(name) {}
Macro::~Macro() {}
bool Macro::CeckMatch()
{
_matched = false;
for (auto &c : _conditions) {
bool cond = c->CheckCondition();
switch (c->GetLogicType()) {
case LogicType::NONE:
vblog(LOG_INFO,
"ignoring condition check 'none' for '%s'",
_name.c_str());
continue;
break;
case LogicType::AND:
_matched = _matched && cond;
break;
case LogicType::OR:
_matched = _matched || cond;
break;
case LogicType::AND_NOT:
_matched = _matched && !cond;
break;
case LogicType::OR_NOT:
_matched = _matched || !cond;
break;
case LogicType::ROOT_NONE:
_matched = cond;
break;
case LogicType::ROOT_NOT:
_matched = !cond;
break;
default:
blog(LOG_WARNING,
"ignoring unkown condition check for '%s'",
_name.c_str());
break;
}
}
return _matched;
}
bool Macro::PerformAction()
{
bool ret = true;
for (auto &a : _actions) {
ret = ret && a->PerformAction();
a->LogAction();
if (!ret) {
return false;
}
}
return ret;
}
bool Macro::Save(obs_data_t *obj)
{
obs_data_set_string(obj, "name", _name.c_str());
obs_data_array_t *conditions = obs_data_array_create();
for (auto &c : _conditions) {
obs_data_t *array_obj = obs_data_create();
c->Save(array_obj);
obs_data_array_push_back(conditions, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "conditions", conditions);
obs_data_array_release(conditions);
obs_data_array_t *actions = obs_data_array_create();
for (auto &a : _actions) {
obs_data_t *array_obj = obs_data_create();
a->Save(array_obj);
obs_data_array_push_back(actions, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "actions", actions);
obs_data_array_release(actions);
return true;
}
bool isValidLogic(LogicType t, bool root)
{
bool isRoot = isRootLogicType(t);
if (!isRoot == root) {
return false;
}
if (isRoot) {
if (t >= LogicType::ROOT_LAST) {
return false;
}
} else if (t >= LogicType::LAST) {
return false;
}
return true;
}
void setValidLogic(MacroCondition *c, bool root, std::string name)
{
if (isValidLogic(c->GetLogicType(), root)) {
return;
}
if (root) {
c->SetLogicType(LogicType::ROOT_NONE);
blog(LOG_WARNING,
"setting invalid logic selection to 'if' for macro %s",
name.c_str());
} else {
c->SetLogicType(LogicType::NONE);
blog(LOG_WARNING,
"setting invalid logic selection to 'ignore' for macro %s",
name.c_str());
}
}
bool Macro::Load(obs_data_t *obj)
{
_name = obs_data_get_string(obj, "name");
bool root = true;
obs_data_array_t *conditions = obs_data_get_array(obj, "conditions");
size_t count = obs_data_array_count(conditions);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(conditions, i);
int id = obs_data_get_int(array_obj, "id");
auto newEntry = MacroConditionFactory::Create(id);
if (newEntry) {
_conditions.emplace_back(newEntry);
auto c = _conditions.back().get();
c->Load(array_obj);
setValidLogic(c, root, _name);
} else {
blog(LOG_WARNING,
"discarding condition entry with unkown id (%d) for macro %s",
id, _name.c_str());
}
obs_data_release(array_obj);
root = false;
}
obs_data_array_release(conditions);
obs_data_array_t *actions = obs_data_get_array(obj, "actions");
count = obs_data_array_count(actions);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(actions, i);
int id = obs_data_get_int(array_obj, "id");
auto newEntry = MacroActionFactory::Create(id);
if (newEntry) {
_actions.emplace_back(newEntry);
_actions.back()->Load(array_obj);
} else {
blog(LOG_WARNING,
"discarding action entry with unkown id (%d) for macro %s",
id, _name.c_str());
}
obs_data_release(array_obj);
}
obs_data_array_release(actions);
return true;
}
bool Macro::SwitchesScene()
{
MacroActionSwitchScene temp;
for (auto &a : _actions) {
if (a->GetId() == temp.GetId()) {
return true;
}
}
return false;
}
bool MacroCondition::Save(obs_data_t *obj)
{
obs_data_set_int(obj, "id", GetId());
obs_data_set_int(obj, "logic", static_cast<int>(_logic));
return true;
}
bool MacroCondition::Load(obs_data_t *obj)
{
_logic = static_cast<LogicType>(obs_data_get_int(obj, "logic"));
return true;
}
bool MacroAction::Save(obs_data_t *obj)
{
obs_data_set_int(obj, "id", GetId());
return true;
}
bool MacroAction::Load(obs_data_t *obj)
{
UNUSED_PARAMETER(obj);
return true;
}
void MacroAction::LogAction()
{
blog(LOG_INFO, "performed action %d", GetId());
}
void SwitcherData::saveMacros(obs_data_t *obj)
{
obs_data_array_t *macroArray = 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(macroArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "macros", macroArray);
obs_data_array_release(macroArray);
}
void SwitcherData::loadMacros(obs_data_t *obj)
{
macros.clear();
obs_data_array_t *macroArray = obs_data_get_array(obj, "macros");
size_t count = obs_data_array_count(macroArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(macroArray, i);
macros.emplace_back();
macros.back().Load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(macroArray);
}
bool SwitcherData::checkMacros()
{
bool ret = false;
for (auto &m : macros) {
if (m.CeckMatch()) {
ret = true;
// This has to be performed here for now as actions are
// not performed immediately after checking conditions.
if (m.SwitchesScene()) {
switcher->macroSceneSwitched = true;
}
}
}
return ret;
}
bool SwitcherData::runMacros()
{
for (auto &m : macros) {
if (m.Matched()) {
blog(LOG_INFO, "running macro: %s", m.Name().c_str());
if (!m.PerformAction()) {
blog(LOG_WARNING, "abort macro: %s",
m.Name().c_str());
return false;
}
}
}
return true;
}

66
src/name-dialog.cpp Normal file
View File

@@ -0,0 +1,66 @@
#include <QVBoxLayout>
#include <QDialogButtonBox>
#include "headers/name-dialog.hpp"
AdvSSNameDialog::AdvSSNameDialog(QWidget *parent) : QDialog(parent)
{
setModal(true);
setWindowModality(Qt::WindowModality::WindowModal);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setFixedWidth(555);
setMinimumHeight(100);
QVBoxLayout *layout = new QVBoxLayout;
setLayout(layout);
label = new QLabel(this);
layout->addWidget(label);
label->setText("Set Text");
userText = new QLineEdit(this);
layout->addWidget(userText);
QDialogButtonBox *buttonbox = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
layout->addWidget(buttonbox);
buttonbox->setCenterButtons(true);
connect(buttonbox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonbox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
static bool IsWhitespace(char ch)
{
return ch == ' ' || ch == '\t';
}
static void CleanWhitespace(std::string &str)
{
while (str.size() && IsWhitespace(str.back()))
str.erase(str.end() - 1);
while (str.size() && IsWhitespace(str.front()))
str.erase(str.begin());
}
bool AdvSSNameDialog::AskForName(QWidget *parent, const QString &title,
const QString &text,
std::string &userTextInput,
const QString &placeHolder, int maxSize)
{
if (maxSize <= 0 || maxSize > 32767) {
maxSize = 170;
}
AdvSSNameDialog dialog(parent);
dialog.setWindowTitle(title);
dialog.label->setText(text);
dialog.userText->setMaxLength(maxSize);
dialog.userText->setText(placeHolder);
dialog.userText->selectAll();
if (dialog.exec() != DialogCode::Accepted) {
return false;
}
userTextInput = dialog.userText->text().toUtf8().constData();
CleanWhitespace(userTextInput);
return true;
}

View File

@@ -4,7 +4,9 @@
#import <Carbon/Carbon.h>
#include <Carbon/Carbon.h>
#include <util/platform.h>
#include "../headers/advanced-scene-switcher.hpp"
#include <vector>
#include <QStringList>
#include <QRegularExpression>
void GetWindowList(std::vector<std::string> &windows)
{

View File

@@ -1,7 +1,9 @@
#include <QVBoxLayout>
#include <QDialogButtonBox>
#include <random>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/name-dialog.hpp"
#include "headers/utility.hpp"
static QMetaObject::Connection addPulse;
@@ -127,7 +129,7 @@ void AdvSceneSwitcher::on_sceneGroupAdd_clicked()
placeHolderText = format.arg(++i);
}
bool accepted = SGNameDialog::AskForName(
bool accepted = AdvSSNameDialog::AskForName(
this, obs_module_text("AdvSceneSwitcher.sceneGroupTab.add"),
obs_module_text("AdvSceneSwitcher.sceneGroupTab.add"), name,
placeHolderText);
@@ -406,7 +408,7 @@ void AdvSceneSwitcher::on_sceneGroupSceneDown_clicked()
void SwitcherData::saveSceneGroups(obs_data_t *obj)
{
obs_data_array_t *sceneGroupArray = obs_data_array_create();
for (SceneGroup &sg : switcher->sceneGroups) {
for (SceneGroup &sg : sceneGroups) {
obs_data_t *array_obj = obs_data_create();
obs_data_set_string(array_obj, "name", sg.name.c_str());
@@ -445,7 +447,7 @@ void SwitcherData::saveSceneGroups(obs_data_t *obj)
void SwitcherData::loadSceneGroups(obs_data_t *obj)
{
switcher->sceneGroups.clear();
sceneGroups.clear();
obs_data_array_t *sceneGroupArray =
obs_data_get_array(obj, "sceneGroups");
@@ -476,8 +478,8 @@ void SwitcherData::loadSceneGroups(obs_data_t *obj)
double time = obs_data_get_double(array_obj, "time");
bool repeat = obs_data_get_bool(array_obj, "repeat");
switcher->sceneGroups.emplace_back(name, type, scenes, count,
time, repeat);
sceneGroups.emplace_back(name, type, scenes, count, time,
repeat);
obs_data_release(array_obj);
}
@@ -509,68 +511,6 @@ void AdvSceneSwitcher::setupSceneGroupTab()
ui->sceneGroupEdit->setDisabled(true);
}
SGNameDialog::SGNameDialog(QWidget *parent) : QDialog(parent)
{
setModal(true);
setWindowModality(Qt::WindowModality::WindowModal);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setFixedWidth(555);
setMinimumHeight(100);
QVBoxLayout *layout = new QVBoxLayout;
setLayout(layout);
label = new QLabel(this);
layout->addWidget(label);
label->setText("Set Text");
userText = new QLineEdit(this);
layout->addWidget(userText);
QDialogButtonBox *buttonbox = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
layout->addWidget(buttonbox);
buttonbox->setCenterButtons(true);
connect(buttonbox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonbox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
static bool IsWhitespace(char ch)
{
return ch == ' ' || ch == '\t';
}
static void CleanWhitespace(std::string &str)
{
while (str.size() && IsWhitespace(str.back()))
str.erase(str.end() - 1);
while (str.size() && IsWhitespace(str.front()))
str.erase(str.begin());
}
bool SGNameDialog::AskForName(QWidget *parent, const QString &title,
const QString &text, std::string &userTextInput,
const QString &placeHolder, int maxSize)
{
if (maxSize <= 0 || maxSize > 32767) {
maxSize = 170;
}
SGNameDialog dialog(parent);
dialog.setWindowTitle(title);
dialog.label->setText(text);
dialog.userText->setMaxLength(maxSize);
dialog.userText->setText(placeHolder);
dialog.userText->selectAll();
if (dialog.exec() != DialogCode::Accepted) {
return false;
}
userTextInput = dialog.userText->text().toUtf8().constData();
CleanWhitespace(userTextInput);
return true;
}
void populateTypeSelection(QComboBox *list)
{
list->addItem(

View File

@@ -305,7 +305,7 @@ void SwitcherData::checkTriggers()
void SwitcherData::saveSceneTriggers(obs_data_t *obj)
{
obs_data_array_t *triggerArray = obs_data_array_create();
for (auto &s : switcher->sceneTriggers) {
for (auto &s : sceneTriggers) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
@@ -317,93 +317,9 @@ void SwitcherData::saveSceneTriggers(obs_data_t *obj)
obs_data_array_release(triggerArray);
}
// To be removed in future version
void loadOldAutoStopStart(obs_data_t *obj)
{
typedef enum {
RECORDING = 0,
STREAMING = 1,
RECORINDGSTREAMING = 2
} AutoStartStopType;
if (obs_data_get_bool(obj, "autoStopEnable")) {
std::string autoStopScene =
obs_data_get_string(obj, "autoStopSceneName");
int action = obs_data_get_int(obj, "autoStopType");
if (action == RECORDING) {
switcher->sceneTriggers.emplace_back();
auto &s = switcher->sceneTriggers.back();
s.scene = GetWeakSourceByName(autoStopScene.c_str());
s.triggerType = sceneTriggerType::SCENE_ACTIVE;
s.triggerAction = sceneTriggerAction::STOP_RECORDING;
}
if (action == STREAMING) {
switcher->sceneTriggers.emplace_back();
auto &s = switcher->sceneTriggers.back();
s.scene = GetWeakSourceByName(autoStopScene.c_str());
s.triggerType = sceneTriggerType::SCENE_ACTIVE;
s.triggerAction = sceneTriggerAction::STOP_STREAMING;
}
if (action == RECORINDGSTREAMING) {
switcher->sceneTriggers.emplace_back();
auto &s = switcher->sceneTriggers.back();
s.scene = GetWeakSourceByName(autoStopScene.c_str());
s.triggerType = sceneTriggerType::SCENE_ACTIVE;
s.triggerAction = sceneTriggerAction::STOP_RECORDING;
switcher->sceneTriggers.emplace_back();
auto &s2 = switcher->sceneTriggers.back();
s2.scene = GetWeakSourceByName(autoStopScene.c_str());
s2.triggerType = sceneTriggerType::SCENE_ACTIVE;
s2.triggerAction = sceneTriggerAction::STOP_STREAMING;
}
}
if (obs_data_get_bool(obj, "autoStartEnable")) {
std::string autoStartScene =
obs_data_get_string(obj, "autoStartSceneName");
int action = obs_data_get_int(obj, "autoStartType");
if (action == RECORDING) {
switcher->sceneTriggers.emplace_back();
auto &s = switcher->sceneTriggers.back();
s.scene = GetWeakSourceByName(autoStartScene.c_str());
s.triggerType = sceneTriggerType::SCENE_ACTIVE;
s.triggerAction = sceneTriggerAction::START_RECORDING;
}
if (action == STREAMING) {
switcher->sceneTriggers.emplace_back();
auto &s = switcher->sceneTriggers.back();
s.scene = GetWeakSourceByName(autoStartScene.c_str());
s.triggerType = sceneTriggerType::SCENE_ACTIVE;
s.triggerAction = sceneTriggerAction::START_STREAMING;
}
if (action == RECORINDGSTREAMING) {
switcher->sceneTriggers.emplace_back();
auto &s = switcher->sceneTriggers.back();
s.scene = GetWeakSourceByName(autoStartScene.c_str());
s.triggerType = sceneTriggerType::SCENE_ACTIVE;
s.triggerAction = sceneTriggerAction::START_RECORDING;
switcher->sceneTriggers.emplace_back();
auto &s2 = switcher->sceneTriggers.back();
s2.scene = GetWeakSourceByName(autoStartScene.c_str());
s2.triggerType = sceneTriggerType::SCENE_ACTIVE;
s2.triggerAction = sceneTriggerAction::START_STREAMING;
}
}
}
void SwitcherData::loadSceneTriggers(obs_data_t *obj)
{
switcher->sceneTriggers.clear();
loadOldAutoStopStart(obj);
sceneTriggers.clear();
obs_data_array_t *triggerArray = obs_data_get_array(obj, "triggers");
size_t count = obs_data_array_count(triggerArray);
@@ -411,7 +327,7 @@ void SwitcherData::loadSceneTriggers(obs_data_t *obj)
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(triggerArray, i);
switcher->sceneTriggers.emplace_back();
sceneTriggers.emplace_back();
sceneTriggers.back().load(array_obj);
obs_data_release(array_obj);

131
src/screenshot-helper.cpp Normal file
View File

@@ -0,0 +1,131 @@
#include "headers/screenshot-helper.hpp"
static void ScreenshotTick(void *param, float);
AdvSSScreenshotObj::AdvSSScreenshotObj(obs_source_t *source)
: weakSource(OBSGetWeakRef(source))
{
obs_add_tick_callback(ScreenshotTick, this);
}
AdvSSScreenshotObj::~AdvSSScreenshotObj()
{
obs_enter_graphics();
gs_stagesurface_destroy(stagesurf);
gs_texrender_destroy(texrender);
obs_leave_graphics();
obs_remove_tick_callback(ScreenshotTick, this);
}
void AdvSSScreenshotObj::Screenshot()
{
OBSSource source = OBSGetStrongRef(weakSource);
if (source) {
cx = obs_source_get_base_width(source);
cy = obs_source_get_base_height(source);
} else {
obs_video_info ovi;
obs_get_video_info(&ovi);
cx = ovi.base_width;
cy = ovi.base_height;
}
if (!cx || !cy) {
blog(LOG_WARNING, "Cannot screenshot, invalid target size");
obs_remove_tick_callback(ScreenshotTick, this);
done = true;
return;
}
texrender = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
stagesurf = gs_stagesurface_create(cx, cy, GS_RGBA);
gs_texrender_reset(texrender);
if (gs_texrender_begin(texrender, cx, cy)) {
vec4 zero;
vec4_zero(&zero);
gs_clear(GS_CLEAR_COLOR, &zero, 0.0f, 0);
gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f);
gs_blend_state_push();
gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
if (source) {
obs_source_inc_showing(source);
obs_source_video_render(source);
obs_source_dec_showing(source);
} else {
obs_render_main_texture();
}
gs_blend_state_pop();
gs_texrender_end(texrender);
}
}
void AdvSSScreenshotObj::Download()
{
gs_stage_texture(stagesurf, gs_texrender_get_texture(texrender));
}
void AdvSSScreenshotObj::Copy()
{
uint8_t *videoData = nullptr;
uint32_t videoLinesize = 0;
image = QImage(cx, cy, QImage::Format::Format_RGBX8888);
if (gs_stagesurface_map(stagesurf, &videoData, &videoLinesize)) {
int linesize = image.bytesPerLine();
for (int y = 0; y < (int)cy; y++)
memcpy(image.scanLine(y),
videoData + (y * videoLinesize), linesize);
gs_stagesurface_unmap(stagesurf);
}
}
void AdvSSScreenshotObj::MarkDone()
{
time = std::chrono::high_resolution_clock::now();
done = true;
}
#define STAGE_SCREENSHOT 0
#define STAGE_DOWNLOAD 1
#define STAGE_COPY_AND_SAVE 2
#define STAGE_FINISH 3
static void ScreenshotTick(void *param, float)
{
AdvSSScreenshotObj *data =
reinterpret_cast<AdvSSScreenshotObj *>(param);
if (data->stage == STAGE_FINISH) {
return;
}
obs_enter_graphics();
switch (data->stage) {
case STAGE_SCREENSHOT:
data->Screenshot();
break;
case STAGE_DOWNLOAD:
data->Download();
break;
case STAGE_COPY_AND_SAVE:
data->Copy();
data->MarkDone();
obs_remove_tick_callback(ScreenshotTick, data);
break;
}
obs_leave_graphics();
data->stage++;
}

112
src/section.cpp Normal file
View File

@@ -0,0 +1,112 @@
#include <QPropertyAnimation>
#include "headers/section.hpp"
#include "headers/utility.hpp"
Section::Section(const int animationDuration, QWidget *parent)
: QWidget(parent), _animationDuration(animationDuration)
{
_toggleButton = new QToolButton(this);
_headerLine = new QFrame(this);
_mainLayout = new QGridLayout(this);
_headerWidgetLayout = new QHBoxLayout();
_toggleButton->setStyleSheet("QToolButton {border: none;}");
_toggleButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
_toggleButton->setArrowType(Qt::ArrowType::RightArrow);
_toggleButton->setCheckable(true);
_toggleButton->setChecked(true);
_headerLine->setFrameShape(QFrame::HLine);
_headerLine->setFrameShadow(QFrame::Sunken);
_headerLine->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Maximum);
// Don't waste space
_mainLayout->setVerticalSpacing(0);
_mainLayout->setContentsMargins(0, 0, 0, 0);
// But add some spacing for widgets in header
_headerWidgetLayout->setSpacing(11);
_headerWidgetLayout->addWidget(_toggleButton);
int row = 0;
_mainLayout->addLayout(_headerWidgetLayout, row, 0, 1, 1,
Qt::AlignLeft);
_mainLayout->addWidget(_headerLine, row++, 2, 1, 1);
setLayout(_mainLayout);
connect(_toggleButton, &QToolButton::toggled, this, &Section::Collapse);
}
void Section::Collapse(bool collapsed)
{
_toggleButton->setChecked(collapsed);
_toggleButton->setArrowType(!collapsed ? Qt::ArrowType::DownArrow
: Qt::ArrowType::RightArrow);
_toggleAnimation->setDirection(!collapsed
? QAbstractAnimation::Forward
: QAbstractAnimation::Backward);
_toggleAnimation->start();
}
void Section::SetContent(QWidget *w)
{
// Clean up previous content
if (_contentArea) {
auto oldLayout = _contentArea->layout();
if (oldLayout) {
clearLayout(oldLayout);
delete oldLayout;
}
}
delete _contentArea;
delete _toggleAnimation;
// Setup contentArea
_contentArea = new QScrollArea(this);
_contentArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
// Start out collapsed
_contentArea->setMaximumHeight(0);
_contentArea->setMinimumHeight(0);
auto newLayout = new QVBoxLayout();
newLayout->addWidget(w);
_contentArea->setLayout(newLayout);
_mainLayout->addWidget(_contentArea, 1, 0, 1, 3);
// Animation Setup
_toggleAnimation = new QParallelAnimationGroup(this);
_toggleAnimation->addAnimation(
new QPropertyAnimation(this, "minimumHeight"));
_toggleAnimation->addAnimation(
new QPropertyAnimation(this, "maximumHeight"));
_toggleAnimation->addAnimation(
new QPropertyAnimation(_contentArea, "maximumHeight"));
const auto collapsedHeight =
sizeHint().height() - _contentArea->maximumHeight();
auto contentHeight = newLayout->sizeHint().height();
for (int i = 0; i < _toggleAnimation->animationCount() - 1; ++i) {
QPropertyAnimation *SectionAnimation =
static_cast<QPropertyAnimation *>(
_toggleAnimation->animationAt(i));
SectionAnimation->setDuration(_animationDuration);
SectionAnimation->setStartValue(collapsedHeight);
SectionAnimation->setEndValue(collapsedHeight + contentHeight);
}
QPropertyAnimation *contentAnimation =
static_cast<QPropertyAnimation *>(_toggleAnimation->animationAt(
_toggleAnimation->animationCount() - 1));
contentAnimation->setDuration(_animationDuration);
contentAnimation->setStartValue(0);
contentAnimation->setEndValue(contentHeight);
}
void Section::AddHeaderWidget(QWidget *w)
{
_headerWidgetLayout->addWidget(w);
}

View File

@@ -93,13 +93,7 @@ void AdvSceneSwitcher::on_audioFallback_toggled(bool on)
void SwitcherData::checkAudioSwitchFallback(OBSWeakSource &scene,
OBSWeakSource &transition)
{
bool durationReached =
((unsigned long long)audioFallback.matchCount * interval) /
1000.0 >=
audioFallback.duration;
if (durationReached) {
if (audioFallback.duration.DurationReached()) {
scene = audioFallback.getScene();
transition = audioFallback.transition;
@@ -107,17 +101,16 @@ void SwitcherData::checkAudioSwitchFallback(OBSWeakSource &scene,
audioFallback.logMatch();
}
}
audioFallback.matchCount++;
}
void SwitcherData::checkAudioSwitch(bool &match, OBSWeakSource &scene,
bool SwitcherData::checkAudioSwitch(OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (AudioSwitch::pause) {
return;
return false;
}
bool match = false;
bool fallbackChecked = false; // false if only one or no match
for (AudioSwitch &s : audioSwitches) {
@@ -125,9 +118,16 @@ void SwitcherData::checkAudioSwitch(bool &match, OBSWeakSource &scene,
continue;
}
obs_source_t *as = obs_weak_source_get_source(s.audioSource);
bool audioActive = obs_source_active(as);
obs_source_release(as);
if (s.ignoreInactiveSource) {
obs_source_t *as =
obs_weak_source_get_source(s.audioSource);
bool audioActive = obs_source_active(as);
obs_source_release(as);
if (!audioActive) {
continue;
}
}
// peak will have a value from -60 db to 0 db
bool volumeThresholdreached = false;
@@ -143,18 +143,11 @@ void SwitcherData::checkAudioSwitch(bool &match, OBSWeakSource &scene,
// Reset for next check
s.peak = -FLT_MAX;
if (volumeThresholdreached) {
s.matchCount++;
} else {
s.matchCount = 0;
if (!volumeThresholdreached) {
s.duration.Reset();
}
bool durationReached =
((unsigned long long)s.matchCount * interval) /
1000.0 >=
s.duration;
if (volumeThresholdreached && durationReached && audioActive) {
if (volumeThresholdreached && s.duration.DurationReached()) {
if (match) {
checkAudioSwitchFallback(scene, transition);
fallbackChecked = true;
@@ -176,14 +169,16 @@ void SwitcherData::checkAudioSwitch(bool &match, OBSWeakSource &scene,
}
if (!fallbackChecked) {
audioFallback.matchCount = 0;
audioFallback.duration.Reset();
}
return match;
}
void SwitcherData::saveAudioSwitches(obs_data_t *obj)
{
obs_data_array_t *audioArray = obs_data_array_create();
for (AudioSwitch &s : switcher->audioSwitches) {
for (AudioSwitch &s : audioSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
@@ -199,7 +194,7 @@ void SwitcherData::saveAudioSwitches(obs_data_t *obj)
void SwitcherData::loadAudioSwitches(obs_data_t *obj)
{
switcher->audioSwitches.clear();
audioSwitches.clear();
obs_data_array_t *audioArray = obs_data_get_array(obj, "audioSwitches");
size_t count = obs_data_array_count(audioArray);
@@ -207,7 +202,7 @@ void SwitcherData::loadAudioSwitches(obs_data_t *obj)
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(audioArray, i);
switcher->audioSwitches.emplace_back();
audioSwitches.emplace_back();
audioSwitches.back().load(array_obj);
obs_data_release(array_obj);
@@ -250,7 +245,7 @@ void AudioSwitch::setVolumeLevel(void *data,
UNUSED_PARAMETER(inputPeak);
AudioSwitch *s = static_cast<AudioSwitch *>(data);
for (int i = 1; i < MAX_AUDIO_CHANNELS; i++) {
for (int i = 0; i < MAX_AUDIO_CHANNELS; i++) {
if (peak[i] > s->peak) {
s->peak = peak[i];
}
@@ -300,44 +295,12 @@ void AudioSwitch::save(obs_data_t *obj)
obs_data_set_int(obj, "volume", volumeThreshold);
obs_data_set_int(obj, "condition", condition);
obs_data_set_double(obj, "duration", duration);
}
// To be removed in future version
bool loadOldAudio(obs_data_t *obj, AudioSwitch *s)
{
if (!s) {
return false;
}
const char *scene = obs_data_get_string(obj, "scene");
if (strcmp(scene, "") == 0) {
return false;
}
s->scene = GetWeakSourceByName(scene);
const char *transition = obs_data_get_string(obj, "transition");
s->transition = GetWeakTransitionByName(transition);
const char *audioSource = obs_data_get_string(obj, "audioSource");
s->audioSource = GetWeakSourceByName(audioSource);
s->volumeThreshold = obs_data_get_int(obj, "volume");
s->condition = (audioCondition)obs_data_get_int(obj, "condition");
s->duration = obs_data_get_double(obj, "duration");
s->usePreviousScene = strcmp(scene, previous_scene_name) == 0;
return true;
duration.Save(obj, "duration");
obs_data_set_bool(obj, "ignoreInactiveSource", ignoreInactiveSource);
}
void AudioSwitch::load(obs_data_t *obj)
{
if (loadOldAudio(obj, this)) {
return;
}
SceneSwitcherEntry::load(obj);
const char *audioSourceName = obs_data_get_string(obj, "audioSource");
@@ -345,7 +308,8 @@ void AudioSwitch::load(obs_data_t *obj)
volumeThreshold = obs_data_get_int(obj, "volume");
condition = (audioCondition)obs_data_get_int(obj, "condition");
duration = obs_data_get_double(obj, "duration");
duration.Load(obj, "duration");
ignoreInactiveSource = obs_data_get_bool(obj, "ignoreInactiveSource");
volmeter = AddVolmeterToSource(this, audioSource);
}
@@ -357,7 +321,7 @@ void AudioSwitchFallback::save(obs_data_t *obj)
"audioFallbackTransition");
obs_data_set_bool(obj, "audioFallbackEnable", enable);
obs_data_set_double(obj, "audioFallbackDuration", duration);
duration.Save(obj, "audioFallbackDuration");
}
void AudioSwitchFallback::load(obs_data_t *obj)
@@ -367,7 +331,7 @@ void AudioSwitchFallback::load(obs_data_t *obj)
"audioFallbackTransition");
enable = obs_data_get_bool(obj, "audioFallbackEnable");
duration = obs_data_get_double(obj, "audioFallbackDuration");
duration.Load(obj, "audioFallbackDuration");
}
AudioSwitch::AudioSwitch(const AudioSwitch &other)
@@ -452,7 +416,9 @@ AudioSwitchWidget::AudioSwitchWidget(QWidget *parent, AudioSwitch *s)
audioSources = new QComboBox();
condition = new QComboBox();
audioVolumeThreshold = new QSpinBox();
duration = new QDoubleSpinBox();
duration = new DurationSelection(this, false);
ignoreInactiveSource = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.audioTab.ignoreInactiveSource"));
obs_source_t *source = nullptr;
if (s) {
@@ -465,10 +431,6 @@ AudioSwitchWidget::AudioSwitchWidget(QWidget *parent, AudioSwitch *s)
audioVolumeThreshold->setMaximum(100);
audioVolumeThreshold->setMinimum(0);
duration->setMinimum(0.0);
duration->setMaximum(99.000000);
duration->setSuffix("s");
QWidget::connect(volMeter->GetSlider(), SIGNAL(valueChanged(int)),
audioVolumeThreshold, SLOT(setValue(int)));
QWidget::connect(audioVolumeThreshold, SIGNAL(valueChanged(int)),
@@ -477,11 +439,13 @@ AudioSwitchWidget::AudioSwitchWidget(QWidget *parent, AudioSwitch *s)
SLOT(VolumeThresholdChanged(int)));
QWidget::connect(condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(duration, SIGNAL(valueChanged(double)), this,
QWidget::connect(duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(audioSources,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(SourceChanged(const QString &)));
QWidget::connect(ignoreInactiveSource, SIGNAL(stateChanged(int)), this,
SLOT(IgnoreInactiveChanged(int)));
AdvSceneSwitcher::populateAudioSelection(audioSources);
populateConditionSelection(condition);
@@ -491,7 +455,8 @@ AudioSwitchWidget::AudioSwitchWidget(QWidget *parent, AudioSwitch *s)
GetWeakSourceName(s->audioSource).c_str());
audioVolumeThreshold->setValue(s->volumeThreshold);
condition->setCurrentIndex(s->condition);
duration->setValue(s->duration);
duration->SetDuration(s->duration);
ignoreInactiveSource->setChecked(s->ignoreInactiveSource);
}
QHBoxLayout *switchLayout = new QHBoxLayout;
@@ -500,6 +465,7 @@ AudioSwitchWidget::AudioSwitchWidget(QWidget *parent, AudioSwitch *s)
{"{{volumeWidget}}", audioVolumeThreshold},
{"{{condition}}", condition},
{"{{duration}}", duration},
{"{{ignoreInactiveSource}}", ignoreInactiveSource},
{"{{scenes}}", scenes},
{"{{transitions}}", transitions}};
placeWidgets(obs_module_text("AdvSceneSwitcher.audioTab.entry"),
@@ -589,31 +555,37 @@ void AudioSwitchWidget::ConditionChanged(int cond)
switchData->condition = (audioCondition)cond;
}
void AudioSwitchWidget::DurationChanged(double dur)
void AudioSwitchWidget::DurationChanged(double sec)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->duration = dur;
switchData->duration.seconds = sec;
}
void AudioSwitchWidget::IgnoreInactiveChanged(int state)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->ignoreInactiveSource = state;
}
AudioSwitchFallbackWidget::AudioSwitchFallbackWidget(QWidget *parent,
AudioSwitchFallback *s)
: SwitchWidget(parent, s, true, true)
{
duration = new QDoubleSpinBox();
duration = new DurationSelection(this, false);
duration->setMinimum(0.0);
duration->setMaximum(99.000000);
duration->setSuffix("s");
QWidget::connect(duration, SIGNAL(valueChanged(double)), this,
QWidget::connect(duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
if (s) {
duration->setValue(s->duration);
duration->SetDuration(s->duration);
}
QHBoxLayout *mainLayout = new QHBoxLayout;
@@ -631,12 +603,12 @@ AudioSwitchFallbackWidget::AudioSwitchFallbackWidget(QWidget *parent,
loading = false;
}
void AudioSwitchFallbackWidget::DurationChanged(double dur)
void AudioSwitchFallbackWidget::DurationChanged(double sec)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->duration = dur;
switchData->duration.seconds = sec;
}

View File

@@ -77,16 +77,17 @@ void AdvSceneSwitcher::on_executableDown_clicked()
switcher->executableSwitches[index + 1]);
}
void SwitcherData::checkExeSwitch(bool &match, OBSWeakSource &scene,
bool SwitcherData::checkExeSwitch(OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (executableSwitches.size() == 0 || ExecutableSwitch::pause) {
return;
return false;
}
std::string title;
QStringList runningProcesses;
bool ignored = false;
bool match = false;
// Check if current window is ignored
GetCurrentWindowTitle(title);
@@ -135,12 +136,14 @@ void SwitcherData::checkExeSwitch(bool &match, OBSWeakSource &scene,
break;
}
}
return match;
}
void SwitcherData::saveExecutableSwitches(obs_data_t *obj)
{
obs_data_array_t *executableArray = obs_data_array_create();
for (ExecutableSwitch &s : switcher->executableSwitches) {
for (ExecutableSwitch &s : executableSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
@@ -154,7 +157,7 @@ void SwitcherData::saveExecutableSwitches(obs_data_t *obj)
void SwitcherData::loadExecutableSwitches(obs_data_t *obj)
{
switcher->executableSwitches.clear();
executableSwitches.clear();
obs_data_array_t *executableArray =
obs_data_get_array(obj, "executableSwitches");
@@ -163,7 +166,7 @@ void SwitcherData::loadExecutableSwitches(obs_data_t *obj)
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(executableArray, i);
switcher->executableSwitches.emplace_back();
executableSwitches.emplace_back();
executableSwitches.back().load(array_obj);
obs_data_release(array_obj);
@@ -199,37 +202,8 @@ void ExecutableSwitch::save(obs_data_t *obj)
obs_data_set_bool(obj, "infocus", inFocus);
}
// To be removed in future version
bool loadOldExe(obs_data_t *obj, ExecutableSwitch *s)
{
if (!s) {
return false;
}
const char *scene = obs_data_get_string(obj, "scene");
if (strcmp(scene, "") == 0) {
return false;
}
s->scene = GetWeakSourceByName(scene);
const char *transition = obs_data_get_string(obj, "transition");
s->transition = GetWeakTransitionByName(transition);
s->exe = obs_data_get_string(obj, "exefile");
s->inFocus = obs_data_get_bool(obj, "infocus");
s->usePreviousScene = strcmp(scene, previous_scene_name) == 0;
return true;
}
void ExecutableSwitch::load(obs_data_t *obj)
{
if (loadOldExe(obj, this)) {
return;
}
SceneSwitcherEntry::load(obj);
exe = obs_data_get_string(obj, "exefile");

View File

@@ -115,19 +115,20 @@ void SwitcherData::writeToStatusFile(QString msg)
file.close();
}
void SwitcherData::checkSwitchInfoFromFile(bool &match, OBSWeakSource &scene,
bool SwitcherData::checkSwitchInfoFromFile(OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (!fileIO.readEnabled || fileIO.readPath.empty() ||
FileSwitch::pause) {
return;
return false;
}
QFile file(QString::fromStdString(fileIO.readPath));
if (!file.open(QIODevice::ReadOnly)) {
return;
return false;
}
bool match = false;
QTextStream in(&file);
QString sceneStr = in.readLine();
@@ -149,6 +150,8 @@ void SwitcherData::checkSwitchInfoFromFile(bool &match, OBSWeakSource &scene,
fileIO.readPath.c_str());
}
file.close();
return match;
}
static size_t WriteCallback(void *contents, size_t size, size_t nmemb,
@@ -158,7 +161,7 @@ static size_t WriteCallback(void *contents, size_t size, size_t nmemb,
return size * nmemb;
}
std::string getRemoteData(std::string &url)
static std::string getRemoteData(std::string &url)
{
std::string readBuffer;
@@ -172,32 +175,6 @@ std::string getRemoteData(std::string &url)
return readBuffer;
}
bool compareIgnoringLineEnding(QString &s1, QString &s2)
{
/*
Im using QTextStream here so the conversion between different lineendings is done by QT.
QT itself uses only the linefeed internally so the input by the user is always using that,
but the files selected by the user might use different line endings.
If you are reading this and know of a cleaner way to do this, please let me know :)
*/
QTextStream s1stream(&s1);
QTextStream s2stream(&s2);
while (!s1stream.atEnd() || !s2stream.atEnd()) {
QString s1s = s1stream.readLine();
QString s2s = s2stream.readLine();
if (s1s != s2s) {
return false;
}
}
if (!s1stream.atEnd() && !s2stream.atEnd()) {
return false;
}
return true;
}
bool matchFileContent(QString &filedata, FileSwitch &s)
{
if (s.onlyMatchIfChanged) {
@@ -247,13 +224,14 @@ bool checkLocalFileContent(FileSwitch &s)
return match;
}
void SwitcherData::checkFileContent(bool &match, OBSWeakSource &scene,
bool SwitcherData::checkFileContent(OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (FileSwitch::pause) {
return;
return false;
}
bool match = false;
for (FileSwitch &s : fileSwitches) {
if (!s.initialized()) {
continue;
@@ -277,6 +255,7 @@ void SwitcherData::checkFileContent(bool &match, OBSWeakSource &scene,
break;
}
}
return match;
}
void AdvSceneSwitcher::on_fileAdd_clicked()
@@ -373,7 +352,7 @@ void AdvSceneSwitcher::on_fileDown_clicked()
void SwitcherData::saveFileSwitches(obs_data_t *obj)
{
obs_data_array_t *fileArray = obs_data_array_create();
for (FileSwitch &s : switcher->fileSwitches) {
for (FileSwitch &s : fileSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
@@ -384,23 +363,22 @@ void SwitcherData::saveFileSwitches(obs_data_t *obj)
obs_data_set_array(obj, "fileSwitches", fileArray);
obs_data_array_release(fileArray);
obs_data_set_bool(obj, "readEnabled", switcher->fileIO.readEnabled);
obs_data_set_string(obj, "readPath", switcher->fileIO.readPath.c_str());
obs_data_set_bool(obj, "writeEnabled", switcher->fileIO.writeEnabled);
obs_data_set_string(obj, "writePath",
switcher->fileIO.writePath.c_str());
obs_data_set_bool(obj, "readEnabled", fileIO.readEnabled);
obs_data_set_string(obj, "readPath", fileIO.readPath.c_str());
obs_data_set_bool(obj, "writeEnabled", fileIO.writeEnabled);
obs_data_set_string(obj, "writePath", fileIO.writePath.c_str());
}
void SwitcherData::loadFileSwitches(obs_data_t *obj)
{
switcher->fileSwitches.clear();
fileSwitches.clear();
obs_data_array_t *fileArray = obs_data_get_array(obj, "fileSwitches");
size_t count = obs_data_array_count(fileArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(fileArray, i);
switcher->fileSwitches.emplace_back();
fileSwitches.emplace_back();
fileSwitches.back().load(array_obj);
obs_data_release(array_obj);
@@ -408,11 +386,11 @@ void SwitcherData::loadFileSwitches(obs_data_t *obj)
obs_data_array_release(fileArray);
obs_data_set_default_bool(obj, "readEnabled", false);
switcher->fileIO.readEnabled = obs_data_get_bool(obj, "readEnabled");
switcher->fileIO.readPath = obs_data_get_string(obj, "readPath");
fileIO.readEnabled = obs_data_get_bool(obj, "readEnabled");
fileIO.readPath = obs_data_get_string(obj, "readPath");
obs_data_set_default_bool(obj, "writeEnabled", false);
switcher->fileIO.writeEnabled = obs_data_get_bool(obj, "writeEnabled");
switcher->fileIO.writePath = obs_data_get_string(obj, "writePath");
fileIO.writeEnabled = obs_data_get_bool(obj, "writeEnabled");
fileIO.writePath = obs_data_get_string(obj, "writePath");
}
void AdvSceneSwitcher::setupFileTab()
@@ -470,41 +448,8 @@ void FileSwitch::save(obs_data_t *obj)
obs_data_set_bool(obj, "onlyMatchIfChanged", onlyMatchIfChanged);
}
// To be removed in future version
bool loadOldFile(obs_data_t *obj, FileSwitch *s)
{
if (!s) {
return false;
}
const char *scene = obs_data_get_string(obj, "scene");
if (strcmp(scene, "") == 0) {
return false;
}
s->scene = GetWeakSourceByName(scene);
const char *transition = obs_data_get_string(obj, "transition");
s->transition = GetWeakTransitionByName(transition);
s->file = obs_data_get_string(obj, "file");
s->text = obs_data_get_string(obj, "text");
s->remote = obs_data_get_bool(obj, "remote");
s->useRegex = obs_data_get_bool(obj, "useRegex");
s->useTime = obs_data_get_bool(obj, "useTime");
s->onlyMatchIfChanged = obs_data_get_bool(obj, "onlyMatchIfChanged");
s->usePreviousScene = strcmp(scene, previous_scene_name) == 0;
return true;
}
void FileSwitch::load(obs_data_t *obj)
{
if (loadOldFile(obj, this)) {
return;
}
SceneSwitcherEntry::load(obj);
file = obs_data_get_string(obj, "file");

View File

@@ -1,6 +1,34 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
static inline bool SceneGroupValid(SceneGroup *group)
{
if (group) {
return group->name != invalid_scene_group_name;
}
return false;
}
static inline SceneGroup *GetSceneGroupByName(const char *name)
{
if (!switcher) {
return nullptr;
}
for (SceneGroup &sg : switcher->sceneGroups) {
if (sg.name == name) {
return &sg;
}
}
return nullptr;
}
static inline SceneGroup *GetSceneGroupByQString(const QString &name)
{
return GetSceneGroupByName(name.toUtf8().constData());
}
bool SceneSwitcherEntry::initialized()
{
return (usePreviousScene || WeakSourceValid(scene) ||

View File

@@ -6,16 +6,17 @@
bool IdleData::pause = false;
IdleWidget *idleWidget = nullptr;
void SwitcherData::checkIdleSwitch(bool &match, OBSWeakSource &scene,
bool SwitcherData::checkIdleSwitch(OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (!idleData.idleEnable || IdleData::pause) {
return;
return false;
}
std::string title;
bool ignoreIdle = false;
GetCurrentWindowTitle(title);
bool match = false;
for (std::string &window : ignoreIdleWindows) {
if (window == title) {
@@ -40,7 +41,7 @@ void SwitcherData::checkIdleSwitch(bool &match, OBSWeakSource &scene,
if (!ignoreIdle && secondsSinceLastInput() > idleData.time) {
if (idleData.alreadySwitched) {
return;
return false;
}
scene = idleData.getScene();
transition = idleData.transition;
@@ -50,8 +51,11 @@ void SwitcherData::checkIdleSwitch(bool &match, OBSWeakSource &scene,
if (verbose) {
idleData.logMatch();
}
} else
} else {
idleData.alreadySwitched = false;
}
return match;
}
void AdvSceneSwitcher::on_idleCheckBox_stateChanged(int state)
@@ -164,7 +168,7 @@ int AdvSceneSwitcher::IgnoreIdleWindowsFindByData(const QString &window)
void SwitcherData::saveIdleSwitches(obs_data_t *obj)
{
obs_data_array_t *ignoreIdleWindowsArray = obs_data_array_create();
for (std::string &window : switcher->ignoreIdleWindows) {
for (std::string &window : ignoreIdleWindows) {
obs_data_t *array_obj = obs_data_create();
obs_data_set_string(array_obj, "window", window.c_str());
obs_data_array_push_back(ignoreIdleWindowsArray, array_obj);
@@ -178,7 +182,7 @@ void SwitcherData::saveIdleSwitches(obs_data_t *obj)
void SwitcherData::loadIdleSwitches(obs_data_t *obj)
{
switcher->ignoreIdleWindows.clear();
ignoreIdleWindows.clear();
obs_data_array_t *ignoreIdleWindowsArray =
obs_data_get_array(obj, "ignoreIdleWindows");
@@ -190,7 +194,7 @@ void SwitcherData::loadIdleSwitches(obs_data_t *obj)
const char *window = obs_data_get_string(array_obj, "window");
switcher->ignoreIdleWindows.emplace_back(window);
ignoreIdleWindows.emplace_back(window);
obs_data_release(array_obj);
}

View File

@@ -80,13 +80,14 @@ void AdvSceneSwitcher::on_mediaDown_clicked()
switcher->mediaSwitches[index + 1]);
}
void SwitcherData::checkMediaSwitch(bool &match, OBSWeakSource &scene,
bool SwitcherData::checkMediaSwitch(OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (MediaSwitch::pause) {
return;
return false;
}
bool match = false;
for (MediaSwitch &mediaSwitch : mediaSwitches) {
if (!mediaSwitch.initialized()) {
continue;
@@ -196,12 +197,13 @@ void SwitcherData::checkMediaSwitch(bool &match, OBSWeakSource &scene,
break;
}
}
return match;
}
void SwitcherData::saveMediaSwitches(obs_data_t *obj)
{
obs_data_array_t *mediaArray = obs_data_array_create();
for (MediaSwitch &s : switcher->mediaSwitches) {
for (MediaSwitch &s : mediaSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
@@ -216,13 +218,13 @@ void SwitcherData::saveMediaSwitches(obs_data_t *obj)
void SwitcherData::loadMediaSwitches(obs_data_t *obj)
{
obs_data_array_t *mediaArray = obs_data_get_array(obj, "mediaSwitches");
switcher->mediaSwitches.clear();
mediaSwitches.clear();
size_t count = obs_data_array_count(mediaArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(mediaArray, i);
switcher->mediaSwitches.emplace_back();
mediaSwitches.emplace_back();
mediaSwitches.back().load(array_obj);
obs_data_release(array_obj);
@@ -271,42 +273,8 @@ void MediaSwitch::save(obs_data_t *obj)
obs_data_set_int(obj, "time", time);
}
// To be removed in future version
bool loadOldMedia(obs_data_t *obj, MediaSwitch *s)
{
if (!s) {
return false;
}
const char *scene = obs_data_get_string(obj, "scene");
if (strcmp(scene, "") == 0) {
return false;
}
s->scene = GetWeakSourceByName(scene);
const char *transition = obs_data_get_string(obj, "transition");
s->transition = GetWeakTransitionByName(transition);
const char *source = obs_data_get_string(obj, "source");
s->source = GetWeakSourceByName(source);
s->state = (obs_media_state)obs_data_get_int(obj, "state");
s->restriction = (time_restriction)obs_data_get_int(obj, "restriction");
s->time = obs_data_get_int(obj, "time");
s->usePreviousScene = strcmp(scene, previous_scene_name) == 0;
return true;
}
void MediaSwitch::load(obs_data_t *obj)
{
if (loadOldMedia(obj, this)) {
return;
}
SceneSwitcherEntry::load(obj);
const char *sourceName = obs_data_get_string(obj, "source");
@@ -432,7 +400,7 @@ void swap(MediaSwitch &first, MediaSwitch &second)
second.resetSignalHandler();
}
void populateMediaStates(QComboBox *list)
static void populateMediaStates(QComboBox *list)
{
list->addItem(obs_module_text("AdvSceneSwitcher.mediaTab.states.none"));
list->addItem(
@@ -454,7 +422,7 @@ void populateMediaStates(QComboBox *list)
list->addItem(obs_module_text("AdvSceneSwitcher.mediaTab.states.any"));
}
void populateTimeRestrictions(QComboBox *list)
static void populateTimeRestrictions(QComboBox *list)
{
list->addItem(obs_module_text(
"AdvSceneSwitcher.mediaTab.timeRestriction.none"));

637
src/switch-network.cpp Normal file
View File

@@ -0,0 +1,637 @@
/*
Most of this code is based on https://github.com/Palakis/obs-websocket
*/
#include <QtWidgets/QMainWindow>
#include <QtConcurrent/QtConcurrent>
#include <QTime>
#include <QMessageBox>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
#define PARAM_SERVER_ENABLE "ServerEnabled"
#define PARAM_SERVER_PORT "ServerPort"
#define PARAM_LOCKTOIPV4 "LockToIPv4"
#define PARAM_CLIENT_ENABLE "ClientEnabled"
#define PARAM_CLIENT_PORT "ClientPort"
#define PARAM_ADDRESS "Address"
#define PARAM_CLIENT_SENDALL "SendAll"
#define RECONNECT_DELAY 10
#define SCENE_ENTRY "scene"
#define TRANSITION_ENTRY "transition"
#define TRANSITION_DURATION "duration"
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
NetworkConfig::NetworkConfig()
: ServerEnabled(false),
ServerPort(55555),
LockToIPv4(false),
ClientEnabled(false),
Address(""),
ClientPort(55555),
SendAll(true)
{
}
void NetworkConfig::Load(obs_data_t *obj)
{
SetDefaults(obj);
ServerEnabled = obs_data_get_bool(obj, PARAM_SERVER_ENABLE);
ServerPort = obs_data_get_int(obj, PARAM_SERVER_PORT);
LockToIPv4 = obs_data_get_bool(obj, PARAM_LOCKTOIPV4);
ClientEnabled = obs_data_get_bool(obj, PARAM_CLIENT_ENABLE);
Address = obs_data_get_string(obj, PARAM_ADDRESS);
ClientPort = obs_data_get_int(obj, PARAM_CLIENT_PORT);
SendAll = obs_data_get_bool(obj, PARAM_CLIENT_SENDALL);
}
void NetworkConfig::Save(obs_data_t *obj)
{
obs_data_set_bool(obj, PARAM_SERVER_ENABLE, ServerEnabled);
obs_data_set_int(obj, PARAM_SERVER_PORT, ServerPort);
obs_data_set_bool(obj, PARAM_LOCKTOIPV4, LockToIPv4);
obs_data_set_bool(obj, PARAM_CLIENT_ENABLE, ClientEnabled);
obs_data_set_string(obj, PARAM_ADDRESS, Address.c_str());
obs_data_set_int(obj, PARAM_CLIENT_PORT, ClientPort);
obs_data_set_bool(obj, PARAM_CLIENT_SENDALL, SendAll);
}
void NetworkConfig::SetDefaults(obs_data_t *obj)
{
obs_data_set_default_bool(obj, PARAM_SERVER_ENABLE, ServerEnabled);
obs_data_set_default_int(obj, PARAM_SERVER_PORT, ServerPort);
obs_data_set_default_bool(obj, PARAM_LOCKTOIPV4, LockToIPv4);
obs_data_set_default_bool(obj, PARAM_CLIENT_ENABLE, ClientEnabled);
obs_data_set_default_string(obj, PARAM_ADDRESS, Address.c_str());
obs_data_set_default_int(obj, PARAM_CLIENT_PORT, ClientPort);
obs_data_set_default_bool(obj, PARAM_CLIENT_SENDALL, SendAll);
}
std::string NetworkConfig::GetClientUri()
{
return "ws://" + Address + ":" + std::to_string(ClientPort);
}
WSServer::WSServer()
: QObject(nullptr), _connections(), _clMutex(QMutex::Recursive)
{
_server.get_alog().clear_channels(
websocketpp::log::alevel::frame_header |
websocketpp::log::alevel::frame_payload |
websocketpp::log::alevel::control);
_server.init_asio();
#ifndef _WIN32
_server.set_reuse_addr(true);
#endif
_server.set_open_handler(bind(&WSServer::onOpen, this, ::_1));
_server.set_close_handler(bind(&WSServer::onClose, this, ::_1));
_server.set_message_handler(
bind(&WSServer::onMessage, this, ::_1, ::_2));
}
WSServer::~WSServer()
{
stop();
}
void WSServer::start(quint16 port, bool lockToIPv4)
{
if (_server.is_listening() &&
(port == _serverPort && _lockToIPv4 == lockToIPv4)) {
blog(LOG_INFO,
"WSServer::start: server already on this port and protocol mode. no restart needed");
return;
}
if (_server.is_listening()) {
stop();
}
_server.reset();
_serverPort = port;
_lockToIPv4 = lockToIPv4;
websocketpp::lib::error_code errorCode;
if (lockToIPv4) {
blog(LOG_INFO, "WSServer::start: Locked to IPv4 bindings");
_server.listen(websocketpp::lib::asio::ip::tcp::v4(),
_serverPort, errorCode);
} else {
blog(LOG_INFO, "WSServer::start: Not locked to IPv4 bindings");
_server.listen(_serverPort, errorCode);
}
if (errorCode) {
std::string errorCodeMessage = errorCode.message();
blog(LOG_INFO, "server: listen failed: %s",
errorCodeMessage.c_str());
obs_frontend_push_ui_translation(obs_module_get_string);
QString errorTitle = tr("AdvSceneSwitcher.windowTitle");
QString errorMessage =
tr("AdvSceneSwitcher.networkTab.startFailed.message")
.arg(_serverPort)
.arg(errorCodeMessage.c_str());
obs_frontend_pop_ui_translation();
QMainWindow *mainWindow = reinterpret_cast<QMainWindow *>(
obs_frontend_get_main_window());
QMessageBox::warning(mainWindow, errorTitle, errorMessage);
return;
}
switcher->serverStatus = ServerStatus::STARTING;
_server.start_accept();
QtConcurrent::run([=]() {
blog(LOG_INFO, "WSServer::start: io thread started");
_server.run();
blog(LOG_INFO, "WSServer::start: io thread exited");
});
switcher->serverStatus = ServerStatus::RUNNING;
blog(LOG_INFO,
"WSServer::start: server started successfully on port %d",
_serverPort);
}
void WSServer::stop()
{
if (!_server.is_listening()) {
return;
}
_server.stop_listening();
for (connection_hdl hdl : _connections) {
websocketpp::lib::error_code ec;
_server.close(hdl, websocketpp::close::status::going_away,
"Server stopping", ec);
}
_threadPool.waitForDone();
while (_connections.size() > 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
switcher->serverStatus = ServerStatus::NOT_RUNNING;
blog(LOG_INFO, "server stopped successfully");
}
void WSServer::sendMessage(sceneSwitchInfo sceneSwitch)
{
if (!sceneSwitch.scene) {
return;
}
OBSData data = obs_data_create();
obs_data_set_string(data, SCENE_ENTRY,
GetWeakSourceName(sceneSwitch.scene).c_str());
obs_data_set_string(data, TRANSITION_ENTRY,
GetWeakSourceName(sceneSwitch.transition).c_str());
obs_data_set_int(data, TRANSITION_DURATION, sceneSwitch.duration);
std::string message = obs_data_get_json(data);
obs_data_release(data);
for (connection_hdl hdl : _connections) {
websocketpp::lib::error_code ec;
_server.send(hdl, message, websocketpp::frame::opcode::text,
ec);
if (ec) {
std::string errorCodeMessage = ec.message();
blog(LOG_INFO, "server: send failed: %s",
errorCodeMessage.c_str());
}
}
if (switcher->verbose) {
blog(LOG_INFO, "server sent message:\n%s", message.c_str());
}
}
void WSServer::onOpen(connection_hdl hdl)
{
QMutexLocker locker(&_clMutex);
_connections.insert(hdl);
locker.unlock();
QString clientIp = getRemoteEndpoint(hdl);
blog(LOG_INFO, "new client connection from %s",
clientIp.toUtf8().constData());
}
std::string processMessage(std::string payload)
{
auto config = switcher->networkConfig;
std::string msgContainer(payload);
const char *msg = msgContainer.c_str();
OBSData data = obs_data_create_from_json(msg);
if (!data) {
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
return "invalid JSON payload";
}
if (!obs_data_has_user_value(data, SCENE_ENTRY) ||
!obs_data_has_user_value(data, TRANSITION_ENTRY) ||
!obs_data_has_user_value(data, TRANSITION_DURATION)) {
return "missing request parameters";
}
std::string sceneName = obs_data_get_string(data, SCENE_ENTRY);
std::string transitionName =
obs_data_get_string(data, TRANSITION_ENTRY);
int duration = obs_data_get_int(data, TRANSITION_DURATION);
obs_data_release(data);
auto scene = GetWeakSourceByName(sceneName.c_str());
if (!scene) {
return "ignoring request - unknown scene '" + sceneName + "'";
}
std::string ret = "message ok";
auto transition = GetWeakTransitionByName(transitionName.c_str());
if (switcher->verbose && !transition) {
ret += " - ignoring invalid transition: '" + transitionName +
"'";
}
switchScene({scene, transition, duration});
return ret;
}
void WSServer::onMessage(connection_hdl hdl, server::message_ptr message)
{
UNUSED_PARAMETER(hdl);
auto opcode = message->get_opcode();
if (opcode != websocketpp::frame::opcode::text) {
return;
}
QtConcurrent::run(&_threadPool, [=]() {
if (message->get_payload() != "message ok") {
blog(LOG_WARNING, "received response: %s",
message->get_payload().c_str());
}
});
}
void WSServer::onClose(connection_hdl hdl)
{
QMutexLocker locker(&_clMutex);
_connections.erase(hdl);
locker.unlock();
auto conn = _server.get_con_from_hdl(hdl);
auto localCloseCode = conn->get_local_close_code();
if (localCloseCode != websocketpp::close::status::going_away) {
QString clientIp = getRemoteEndpoint(hdl);
blog(LOG_INFO, "client %s disconnected",
clientIp.toUtf8().constData());
}
}
QString WSServer::getRemoteEndpoint(connection_hdl hdl)
{
auto conn = _server.get_con_from_hdl(hdl);
return QString::fromStdString(conn->get_remote_endpoint());
}
WSClient::WSClient() : QObject(nullptr)
{
_client.get_alog().clear_channels(
websocketpp::log::alevel::frame_header |
websocketpp::log::alevel::frame_payload |
websocketpp::log::alevel::control);
_client.init_asio();
#ifndef _WIN32
_client.set_reuse_addr(true);
#endif
_client.set_open_handler(bind(&WSClient::onOpen, this, ::_1));
_client.set_fail_handler(bind(&WSClient::onFail, this, ::_1));
_client.set_message_handler(
bind(&WSClient::onMessage, this, ::_1, ::_2));
_client.set_close_handler(bind(&WSClient::onClose, this, ::_1));
}
WSClient::~WSClient()
{
disconnect();
}
void WSClient::connectThread()
{
while (_retry) {
_client.reset();
switcher->clientStatus = ClientStatus::CONNECTING;
// Create a connection to the given URI and queue it for connection once
// the event loop starts
websocketpp::lib::error_code ec;
client::connection_ptr con = _client.get_connection(_uri, ec);
if (ec) {
_failMsg = ec.message();
blog(LOG_INFO, "client: connect failed: %s",
_failMsg.c_str());
switcher->clientStatus = ClientStatus::FAIL;
} else {
_client.connect(con);
_connection = connection_hdl(con);
// Start the ASIO io_service run loop
blog(LOG_INFO, "WSClient::connect: io thread started");
_connected = true;
_client.run();
_connected = false;
blog(LOG_INFO, "WSClient::connect: io thread exited");
}
if (_retry) {
std::unique_lock<std::mutex> lck(_waitMtx);
blog(LOG_INFO,
"trying to reconnect to %s in %d seconds.",
_uri.c_str(), RECONNECT_DELAY);
_cv.wait_for(lck,
std::chrono::seconds(RECONNECT_DELAY));
}
}
}
void WSClient::connect(std::string uri)
{
disconnect();
_uri = uri;
_retry = true;
_thread = std::thread(&WSClient::connectThread, this);
switcher->clientStatus = ClientStatus::DISCONNECTED;
blog(LOG_INFO, "WSClient::connect: exited");
}
void WSClient::disconnect()
{
_retry = false;
websocketpp::lib::error_code ec;
_client.close(_connection, websocketpp::close::status::normal,
"Client stopping", ec);
{
std::unique_lock<std::mutex> waitLck(_waitMtx);
blog(LOG_INFO, "trying to reconnect to %s in %d seconds.",
_uri.c_str(), RECONNECT_DELAY);
_cv.notify_all();
}
while (_connected) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
_client.close(_connection, websocketpp::close::status::normal,
"Client stopping", ec);
}
if (_thread.joinable()) {
_thread.join();
}
}
void WSClient::onOpen(connection_hdl hdl)
{
UNUSED_PARAMETER(hdl);
blog(LOG_INFO, "connection to %s opened", _uri.c_str());
switcher->clientStatus = ClientStatus::CONNECTED;
}
void WSClient::onFail(connection_hdl hdl)
{
UNUSED_PARAMETER(hdl);
blog(LOG_INFO, "connection to %s failed", _uri.c_str());
}
void WSClient::onMessage(connection_hdl hdl, client::message_ptr message)
{
auto opcode = message->get_opcode();
if (opcode != websocketpp::frame::opcode::text) {
return;
}
std::string payload = message->get_payload();
std::string response = processMessage(payload);
websocketpp::lib::error_code errorCode;
_client.send(hdl, response, websocketpp::frame::opcode::text,
errorCode);
if (errorCode) {
std::string errorCodeMessage = errorCode.message();
blog(LOG_INFO, "client(response): send failed: %s",
errorCodeMessage.c_str());
}
if (switcher->verbose) {
blog(LOG_INFO, "client sent message:\n%s", response.c_str());
}
}
void WSClient::onClose(connection_hdl hdl)
{
UNUSED_PARAMETER(hdl);
blog(LOG_INFO, "client-connection to %s closed.", _uri.c_str());
switcher->clientStatus = ClientStatus::DISCONNECTED;
}
void SwitcherData::loadNetworkSettings(obs_data_t *obj)
{
networkConfig.Load(obj);
}
void SwitcherData::saveNetworkSwitches(obs_data_t *obj)
{
networkConfig.Save(obj);
if (!networkConfig.ServerEnabled) {
server.stop();
}
}
void AdvSceneSwitcher::setupNetworkTab()
{
ui->serverSettings->setChecked(switcher->networkConfig.ServerEnabled);
ui->serverPort->setValue(switcher->networkConfig.ServerPort);
ui->lockToIPv4->setChecked(switcher->networkConfig.LockToIPv4);
ui->clientSettings->setChecked(switcher->networkConfig.ClientEnabled);
ui->clientHostname->setText(switcher->networkConfig.Address.c_str());
ui->clientPort->setValue(switcher->networkConfig.ClientPort);
ui->restrictSend->setChecked(!switcher->networkConfig.SendAll);
QTimer *statusTimer = new QTimer(this);
connect(statusTimer, SIGNAL(timeout()), this,
SLOT(updateClientStatus()));
connect(statusTimer, SIGNAL(timeout()), this,
SLOT(updateServerStatus()));
statusTimer->start(500);
}
void AdvSceneSwitcher::on_serverSettings_toggled(bool on)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->networkConfig.ServerEnabled = on;
if (on) {
switcher->server.start(switcher->networkConfig.ServerPort,
switcher->networkConfig.LockToIPv4);
} else {
switcher->server.stop();
}
}
void AdvSceneSwitcher::on_serverPort_valueChanged(int value)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->networkConfig.ServerPort = value;
}
void AdvSceneSwitcher::on_lockToIPv4_stateChanged(int state)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->networkConfig.LockToIPv4 = state;
}
void AdvSceneSwitcher::on_serverRestart_clicked()
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->server.start(switcher->networkConfig.ServerPort,
switcher->networkConfig.LockToIPv4);
}
void AdvSceneSwitcher::updateServerStatus()
{
switch (switcher->serverStatus) {
case ServerStatus::NOT_RUNNING:
ui->serverStatus->setText(obs_module_text(
"AdvSceneSwitcher.networkTab.server.status.notRunning"));
break;
case ServerStatus::STARTING:
ui->serverStatus->setText(obs_module_text(
"AdvSceneSwitcher.networkTab.server.status.starting"));
break;
case ServerStatus::RUNNING:
ui->serverStatus->setText(obs_module_text(
"AdvSceneSwitcher.networkTab.server.status.running"));
break;
default:
break;
}
}
void AdvSceneSwitcher::on_clientSettings_toggled(bool on)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->networkConfig.ClientEnabled = on;
if (on) {
switcher->client.connect(
switcher->networkConfig.GetClientUri());
} else {
switcher->client.disconnect();
}
}
void AdvSceneSwitcher::on_clientHostname_textChanged(const QString &text)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->networkConfig.Address = text.toUtf8().constData();
}
void AdvSceneSwitcher::on_clientPort_valueChanged(int value)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->networkConfig.ClientPort = value;
}
void AdvSceneSwitcher::on_restrictSend_stateChanged(int state)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->networkConfig.SendAll = !state;
}
void AdvSceneSwitcher::on_clientReconnect_clicked()
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->client.connect(switcher->networkConfig.GetClientUri());
}
void AdvSceneSwitcher::updateClientStatus()
{
switch (switcher->clientStatus) {
case ClientStatus::DISCONNECTED:
ui->clientStatus->setText(obs_module_text(
"AdvSceneSwitcher.networkTab.client.status.disconnected"));
break;
case ClientStatus::CONNECTING:
ui->clientStatus->setText(obs_module_text(
"AdvSceneSwitcher.networkTab.client.status.connecting"));
break;
case ClientStatus::CONNECTED:
ui->clientStatus->setText(obs_module_text(
"AdvSceneSwitcher.networkTab.client.status.connected"));
break;
case ClientStatus::FAIL:
ui->clientStatus->setText(QString("Error: ") +
switcher->client.getFail().c_str());
break;
default:
break;
}
}

View File

@@ -90,7 +90,7 @@ void resetPause()
VideoSwitch::pause = false;
}
void setPauseTarget(PauseTarget &target, bool &verbose)
void setPauseTarget(PauseTarget &target)
{
switch (target) {
case PauseTarget::All:
@@ -148,23 +148,23 @@ void setPauseTarget(PauseTarget &target, bool &verbose)
}
bool checkPauseScene(obs_weak_source_t *currentScene, obs_weak_source_t *scene,
PauseTarget &target, bool &verbose)
PauseTarget &target)
{
if (currentScene != scene) {
return false;
}
setPauseTarget(target, verbose);
setPauseTarget(target);
return (target == PauseTarget::All);
}
bool checkPauseWindow(std::string &currentTitle, std::string &title,
PauseTarget &target, bool &verbose)
PauseTarget &target)
{
if (currentTitle != title) {
return false;
}
setPauseTarget(target, verbose);
setPauseTarget(target);
return (target == PauseTarget::All);
}
@@ -182,11 +182,10 @@ bool SwitcherData::checkPause()
for (PauseEntry &s : pauseEntries) {
if (s.pauseType == PauseType::Scene) {
pauseAll = checkPauseScene(ws, s.scene, s.pauseTarget,
verbose);
pauseAll = checkPauseScene(ws, s.scene, s.pauseTarget);
} else {
pauseAll = checkPauseWindow(title, s.window,
s.pauseTarget, verbose);
s.pauseTarget);
}
if (pauseAll) {
break;
@@ -202,7 +201,7 @@ bool SwitcherData::checkPause()
void SwitcherData::savePauseSwitches(obs_data_t *obj)
{
obs_data_array_t *pauseScenesArray = obs_data_array_create();
for (PauseEntry &s : switcher->pauseEntries) {
for (PauseEntry &s : pauseEntries) {
obs_data_t *array_obj = obs_data_create();
obs_data_set_int(array_obj, "pauseType",
@@ -229,10 +228,7 @@ void SwitcherData::savePauseSwitches(obs_data_t *obj)
void SwitcherData::loadPauseSwitches(obs_data_t *obj)
{
switcher->pauseEntries.clear();
// To be removed in future builds once most users have migrated
loadOldPauseSwitches(obj);
pauseEntries.clear();
obs_data_array_t *pauseArray = obs_data_get_array(obj, "pauseEntries");
size_t count = obs_data_array_count(pauseArray);
@@ -249,58 +245,14 @@ void SwitcherData::loadPauseSwitches(obs_data_t *obj)
const char *window =
obs_data_get_string(array_obj, "pauseWindow");
switcher->pauseEntries.emplace_back(GetWeakSourceByName(scene),
type, target, window);
pauseEntries.emplace_back(GetWeakSourceByName(scene), type,
target, window);
obs_data_release(array_obj);
}
obs_data_array_release(pauseArray);
}
void SwitcherData::loadOldPauseSwitches(obs_data_t *obj)
{
if (obs_data_get_int(obj, "oldPauseValuesImported")) {
return;
}
obs_data_array_t *pauseScenesArray =
obs_data_get_array(obj, "pauseScenes");
size_t count = obs_data_array_count(pauseScenesArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj =
obs_data_array_item(pauseScenesArray, i);
const char *scene =
obs_data_get_string(array_obj, "pauseScene");
switcher->pauseEntries.emplace_back(GetWeakSourceByName(scene),
PauseType::Scene,
PauseTarget::All, "");
obs_data_release(array_obj);
}
obs_data_array_release(pauseScenesArray);
obs_data_array_t *pauseWindowsArray =
obs_data_get_array(obj, "pauseWindows");
count = obs_data_array_count(pauseWindowsArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj =
obs_data_array_item(pauseWindowsArray, i);
const char *window =
obs_data_get_string(array_obj, "pauseWindow");
switcher->pauseEntries.emplace_back(nullptr, PauseType::Window,
PauseTarget::All, window);
obs_data_release(array_obj);
}
obs_data_array_release(pauseWindowsArray);
}
void AdvSceneSwitcher::setupPauseTab()
{
for (auto &s : switcher->pauseEntries) {

View File

@@ -66,7 +66,7 @@ bool SwitcherData::prioFuncsValid()
}
for (int p : functionNamesByPriority) {
if (p < 0 || p > 9) {
if (p < 0 || p > 10) {
return false;
}
}

View File

@@ -36,24 +36,32 @@ void AdvSceneSwitcher::on_randomRemove_clicked()
delete item;
}
void SwitcherData::checkRandom(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition, int &delay)
bool SwitcherData::checkRandom(OBSWeakSource &scene, OBSWeakSource &transition,
int &delay)
{
if (randomSwitches.size() == 0 || RandomSwitch::pause) {
return;
return false;
}
std::deque<RandomSwitch> rs(randomSwitches);
std::random_device rng;
std::mt19937 urng(rng());
std::shuffle(rs.begin(), rs.end(), urng);
bool match = false;
for (RandomSwitch &r : rs) {
if (!r.initialized()) {
continue;
}
if (r.scene == lastRandomScene && randomSwitches.size() != 1) {
continue;
if (randomSwitches.size() != 1) {
if (r.targetType == SwitchTargetType::Scene &&
r.scene == lastRandomScene) {
continue;
} else if (r.targetType ==
SwitchTargetType::SceneGroup &&
r.group && r.group == lastRandomSceneGroup) {
continue;
}
}
scene = r.getScene();
@@ -61,18 +69,20 @@ void SwitcherData::checkRandom(bool &match, OBSWeakSource &scene,
delay = (int)r.delay * 1000;
match = true;
lastRandomScene = r.scene;
lastRandomSceneGroup = r.group;
if (verbose) {
r.logMatch();
}
break;
}
return match;
}
void SwitcherData::saveRandomSwitches(obs_data_t *obj)
{
obs_data_array_t *randomArray = obs_data_array_create();
for (RandomSwitch &s : switcher->randomSwitches) {
for (RandomSwitch &s : randomSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
@@ -86,7 +96,7 @@ void SwitcherData::saveRandomSwitches(obs_data_t *obj)
void SwitcherData::loadRandomSwitches(obs_data_t *obj)
{
switcher->randomSwitches.clear();
randomSwitches.clear();
obs_data_array_t *randomArray =
obs_data_get_array(obj, "randomSwitches");
@@ -95,7 +105,7 @@ void SwitcherData::loadRandomSwitches(obs_data_t *obj)
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(randomArray, i);
switcher->randomSwitches.emplace_back();
randomSwitches.emplace_back();
randomSwitches.back().load(array_obj);
obs_data_release(array_obj);
@@ -142,7 +152,7 @@ void RandomSwitch::load(obs_data_t *obj)
}
RandomSwitchWidget::RandomSwitchWidget(QWidget *parent, RandomSwitch *s)
: SwitchWidget(parent, s, false, false)
: SwitchWidget(parent, s, false, true)
{
delay = new QDoubleSpinBox();

View File

@@ -151,15 +151,16 @@ bool shouldIgnoreSceneSwitch(ScreenRegionSwitch &matchingRegion)
return matchingRegion.excludeScene == ws;
}
void SwitcherData::checkScreenRegionSwitch(bool &match, OBSWeakSource &scene,
bool SwitcherData::checkScreenRegionSwitch(OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (ScreenRegionSwitch::pause) {
return;
return false;
}
std::pair<int, int> cursorPos = getCursorPos();
int minRegionSize = 99999;
bool match = false;
for (auto &s : screenRegionSwitches) {
if (!s.initialized()) {
@@ -173,7 +174,7 @@ void SwitcherData::checkScreenRegionSwitch(bool &match, OBSWeakSource &scene,
if (shouldIgnoreSceneSwitch(s)) {
// We technically have a match.
// But just ignore it.
return;
return false;
}
match = true;
scene = s.getScene();
@@ -187,6 +188,7 @@ void SwitcherData::checkScreenRegionSwitch(bool &match, OBSWeakSource &scene,
}
}
}
return match;
}
void AdvSceneSwitcher::updateScreenRegionCursorPos()
@@ -199,7 +201,7 @@ void AdvSceneSwitcher::updateScreenRegionCursorPos()
void SwitcherData::saveScreenRegionSwitches(obs_data_t *obj)
{
obs_data_array_t *screenRegionArray = obs_data_array_create();
for (ScreenRegionSwitch &s : switcher->screenRegionSwitches) {
for (ScreenRegionSwitch &s : screenRegionSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
@@ -213,7 +215,7 @@ void SwitcherData::saveScreenRegionSwitches(obs_data_t *obj)
void SwitcherData::loadScreenRegionSwitches(obs_data_t *obj)
{
switcher->screenRegionSwitches.clear();
screenRegionSwitches.clear();
obs_data_array_t *screenRegionArray =
obs_data_get_array(obj, "screenRegion");
@@ -223,7 +225,7 @@ void SwitcherData::loadScreenRegionSwitches(obs_data_t *obj)
obs_data_t *array_obj =
obs_data_array_item(screenRegionArray, i);
switcher->screenRegionSwitches.emplace_back();
screenRegionSwitches.emplace_back();
screenRegionSwitches.back().load(array_obj);
obs_data_release(array_obj);
@@ -269,40 +271,8 @@ void ScreenRegionSwitch::save(obs_data_t *obj)
obs_data_set_int(obj, "maxY", maxY);
}
// To be removed in future version
bool loadOldRegion(obs_data_t *obj, ScreenRegionSwitch *s)
{
if (!s) {
return false;
}
const char *scene = obs_data_get_string(obj, "screenRegionScene");
if (strcmp(scene, "") == 0) {
return false;
}
s->scene = GetWeakSourceByName(scene);
const char *transition = obs_data_get_string(obj, "transition");
s->transition = GetWeakTransitionByName(transition);
s->minX = obs_data_get_int(obj, "minX");
s->minY = obs_data_get_int(obj, "minY");
s->maxX = obs_data_get_int(obj, "maxX");
s->maxY = obs_data_get_int(obj, "maxY");
s->usePreviousScene = strcmp(scene, previous_scene_name) == 0;
return true;
}
void ScreenRegionSwitch::load(obs_data_t *obj)
{
if (loadOldRegion(obj, this)) {
return;
}
SceneSwitcherEntry::load(obj);
const char *excludeSceneName = obs_data_get_string(obj, "excludeScene");

View File

@@ -179,16 +179,18 @@ void AdvSceneSwitcher::on_sceneSequenceSwitches_itemDoubleClicked(
OpenSequenceExtendEdit(currentWidget);
}
void SwitcherData::checkSceneSequence(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition, int &linger)
bool SwitcherData::checkSceneSequence(OBSWeakSource &scene,
OBSWeakSource &transition, int &linger,
bool &setPrevSceneAfterLinger)
{
if (SceneSequenceSwitch::pause) {
return;
return false;
}
obs_source_t *currentSceneSource = obs_frontend_get_current_scene();
obs_weak_source_t *currentScene =
obs_source_get_weak_source(currentSceneSource);
bool match = false;
for (SceneSequenceSwitch &s : sceneSequenceSwitches) {
// Continue the active uninterruptible sequence and skip others
@@ -205,9 +207,12 @@ void SwitcherData::checkSceneSequence(bool &match, OBSWeakSource &scene,
if (s.activeSequence) {
scene = s.activeSequence->getScene();
transition = s.activeSequence->transition;
setPrevSceneAfterLinger =
s.activeSequence->usePreviousScene;
} else {
scene = s.getScene();
transition = s.transition;
setPrevSceneAfterLinger = s.usePreviousScene;
if (verbose) {
s.logMatch();
}
@@ -233,12 +238,14 @@ void SwitcherData::checkSceneSequence(bool &match, OBSWeakSource &scene,
obs_source_release(currentSceneSource);
obs_weak_source_release(currentScene);
return match;
}
void SwitcherData::saveSceneSequenceSwitches(obs_data_t *obj)
{
obs_data_array_t *sceneSequenceArray = obs_data_array_create();
for (SceneSequenceSwitch &s : switcher->sceneSequenceSwitches) {
for (SceneSequenceSwitch &s : sceneSequenceSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
@@ -252,7 +259,7 @@ void SwitcherData::saveSceneSequenceSwitches(obs_data_t *obj)
void SwitcherData::loadSceneSequenceSwitches(obs_data_t *obj)
{
switcher->sceneSequenceSwitches.clear();
sceneSequenceSwitches.clear();
obs_data_array_t *sceneSequenceArray =
obs_data_get_array(obj, "sceneRoundTrip");
@@ -262,7 +269,7 @@ void SwitcherData::loadSceneSequenceSwitches(obs_data_t *obj)
obs_data_t *array_obj =
obs_data_array_item(sceneSequenceArray, i);
switcher->sceneSequenceSwitches.emplace_back();
sceneSequenceSwitches.emplace_back();
sceneSequenceSwitches.back().load(array_obj);
obs_data_release(array_obj);
@@ -308,8 +315,7 @@ void SceneSequenceSwitch::save(obs_data_t *obj, bool saveExt)
static_cast<int>(startTargetType));
obs_data_set_string(obj, "startScene",
GetWeakSourceName(startScene).c_str());
obs_data_set_double(obj, "delay", delay);
obs_data_set_int(obj, "delayMultiplier", delayMultiplier);
delay.Save(obj, "delay");
obs_data_set_bool(obj, "interruptible", interruptible);
if (saveExt) {
@@ -328,60 +334,14 @@ void SceneSequenceSwitch::save(obs_data_t *obj, bool saveExt)
}
}
// To be removed in future version
bool loadOldScequence(obs_data_t *obj, SceneSequenceSwitch *s)
{
if (!s) {
return false;
}
const char *scene1 = obs_data_get_string(obj, "sceneRoundTripScene1");
if (strcmp(scene1, "") == 0) {
return false;
}
s->startScene = GetWeakSourceByName(scene1);
const char *scene2 = obs_data_get_string(obj, "sceneRoundTripScene2");
s->scene = GetWeakSourceByName(scene2);
const char *transition = obs_data_get_string(obj, "transition");
s->transition = GetWeakTransitionByName(transition);
s->delay = obs_data_get_double(obj, "delay");
int delayMultiplier = obs_data_get_int(obj, "delayMultiplier");
if (delayMultiplier == 0 ||
(delayMultiplier != 1 && delayMultiplier % 60 != 0))
delayMultiplier = 1;
s->delayMultiplier = delayMultiplier;
s->interruptible = obs_data_get_bool(obj, "interruptible");
s->usePreviousScene = strcmp(scene2, previous_scene_name) == 0;
return true;
}
void SceneSequenceSwitch::load(obs_data_t *obj, bool saveExt)
{
if (loadOldScequence(obj, this)) {
return;
}
SceneSwitcherEntry::load(obj);
startTargetType = static_cast<SwitchTargetType>(
obs_data_get_int(obj, "startTargetType"));
const char *scene = obs_data_get_string(obj, "startScene");
startScene = GetWeakSourceByName(scene);
delay = obs_data_get_double(obj, "delay");
delayMultiplier = obs_data_get_int(obj, "delayMultiplier");
if (delayMultiplier == 0 ||
(delayMultiplier != 1 && delayMultiplier % 60 != 0))
delayMultiplier = 1;
delay.Load(obj, "delay");
interruptible = obs_data_get_bool(obj, "interruptible");
@@ -455,7 +415,7 @@ bool SceneSequenceSwitch::checkMatch(OBSWeakSource currentScene, int &linger,
prepareUninterruptibleMatch(currentScene, linger);
}
} else {
matchCount = 0;
delay.Reset();
if (root) {
root->activeSequence = nullptr;
@@ -468,11 +428,8 @@ bool SceneSequenceSwitch::checkMatch(OBSWeakSource currentScene, int &linger,
bool SceneSequenceSwitch::checkDurationMatchInterruptible()
{
bool durationReached = matchCount * (switcher->interval / 1000.0) >=
delay;
matchCount++;
if (durationReached) {
matchCount = 0;
if (delay.DurationReached()) {
delay.Reset();
return true;
}
return false;
@@ -481,7 +438,7 @@ bool SceneSequenceSwitch::checkDurationMatchInterruptible()
void SceneSequenceSwitch::prepareUninterruptibleMatch(
OBSWeakSource currentScene, int &linger)
{
int dur = delay * 1000;
int dur = delay.seconds * 1000;
if (dur > 0) {
switcher->waitScene = obs_weak_source_get_source(currentScene);
obs_source_release(switcher->waitScene);
@@ -526,8 +483,8 @@ void SceneSequenceSwitch::advanceActiveSequence()
return;
}
// Reinit old matchCount value in case it was previously set
activeSequence->matchCount = 0;
// Reinit delay in case it was previously set
activeSequence->delay.Reset();
}
}
@@ -554,19 +511,6 @@ void SceneSequenceSwitch::logSequenceCanceled()
blog(LOG_INFO, "unexpected scene change - cancel sequence");
}
QString delayMultiplierToString(int delayMultiplier)
{
switch (delayMultiplier) {
case 1:
return obs_module_text("AdvSceneSwitcher.unit.secends");
case 60:
return obs_module_text("AdvSceneSwitcher.unit.minutes");
case 60 * 60:
return obs_module_text("AdvSceneSwitcher.unit.hours");
}
return "???";
}
QString makeExtendText(SceneSequenceSwitch *s, int curLen = 0)
{
if (!s) {
@@ -575,8 +519,7 @@ QString makeExtendText(SceneSequenceSwitch *s, int curLen = 0)
QString ext = "";
ext = QString::number(s->delay / s->delayMultiplier) + " ";
ext += delayMultiplierToString(s->delayMultiplier);
ext = QString::fromStdString(s->delay.ToString()) + " ";
QString sceneName = GetWeakSourceName(s->scene).c_str();
if (s->targetType == SwitchTargetType::SceneGroup && s->group) {
@@ -600,13 +543,6 @@ QString makeExtendText(SceneSequenceSwitch *s, int curLen = 0)
}
}
void populateDelayUnits(QComboBox *list)
{
list->addItem(obs_module_text("AdvSceneSwitcher.unit.secends"));
list->addItem(obs_module_text("AdvSceneSwitcher.unit.minutes"));
list->addItem(obs_module_text("AdvSceneSwitcher.unit.hours"));
}
SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
bool extendSequence, bool editExtendMode,
bool showExtendText)
@@ -614,8 +550,7 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
{
this->setParent(parent);
delay = new QDoubleSpinBox();
delayUnits = new QComboBox();
delay = new DurationSelection();
startScenes = new QComboBox();
interruptible = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.interruptible"));
@@ -638,10 +573,10 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
QWidget::connect(scenes, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(SceneChanged(const QString &)));
QWidget::connect(delay, SIGNAL(valueChanged(double)), this,
QWidget::connect(delay, SIGNAL(DurationChanged(double)), this,
SLOT(DelayChanged(double)));
QWidget::connect(delayUnits, SIGNAL(currentIndexChanged(int)), this,
SLOT(DelayUnitsChanged(int)));
QWidget::connect(delay, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(DelayUnitsChanged(DurationUnit)));
QWidget::connect(startScenes,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(StartSceneChanged(const QString &)));
@@ -653,9 +588,7 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
QWidget::connect(reduce, SIGNAL(clicked()), this,
SLOT(ReduceClicked()));
delay->setMaximum(99999.000000);
AdvSceneSwitcher::populateSceneSelection(startScenes, false);
populateDelayUnits(delayUnits);
interruptible->setToolTip(obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.interruptibleHint"));
@@ -666,7 +599,6 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", scenes},
{"{{delay}}", delay},
{"{{delayUnits}}", delayUnits},
{"{{transitions}}", transitions}};
placeWidgets(
obs_module_text(
@@ -679,7 +611,6 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
{"{{startScenes}}", startScenes},
{"{{scenes}}", scenes},
{"{{delay}}", delay},
{"{{delayUnits}}", delayUnits},
{"{{transitions}}", transitions},
{"{{interruptible}}", interruptible}};
placeWidgets(obs_module_text(
@@ -743,46 +674,24 @@ void SequenceWidget::swapSwitchData(SequenceWidget *s1, SequenceWidget *s2)
s2->setSwitchData(t);
}
void SequenceWidget::UpdateDelay()
{
if (switchData) {
delay->setValue(switchData->delay /
switchData->delayMultiplier);
}
}
void SequenceWidget::DelayChanged(double delay)
void SequenceWidget::DelayChanged(double sec)
{
if (loading || !switchData) {
return;
}
switchData->delay = delay * switchData->delayMultiplier;
std::lock_guard<std::mutex> lock(switcher->m);
switchData->delay.seconds = sec;
}
void SequenceWidget::DelayUnitsChanged(int idx)
void SequenceWidget::DelayUnitsChanged(DurationUnit unit)
{
if (loading || !switchData) {
return;
}
delay_units unit = (delay_units)idx;
switch (unit) {
case SECONDS:
switchData->delayMultiplier = 1;
break;
case MINUTES:
switchData->delayMultiplier = 60;
break;
case HOURS:
switchData->delayMultiplier = 60 * 60;
break;
default:
break;
}
UpdateDelay();
std::lock_guard<std::mutex> lock(switcher->m);
switchData->delay.displayUnit = unit;
}
void SequenceWidget::setExtendedSequenceStartScene()
@@ -844,21 +753,7 @@ void SequenceWidget::UpdateWidgetStatus(bool showExtendText)
makeExtendText(switchData->extendedSequence.get()));
}
switch (switchData->delayMultiplier) {
case 1:
delayUnits->setCurrentIndex(0);
break;
case 60:
delayUnits->setCurrentIndex(1);
break;
case 3600:
delayUnits->setCurrentIndex(2);
break;
default:
delayUnits->setCurrentIndex(0);
}
UpdateDelay();
delay->SetDuration(switchData->delay);
startScenes->setCurrentText(
GetWeakSourceName(switchData->startScene).c_str());
interruptible->setChecked(switchData->interruptible);

View File

@@ -113,13 +113,14 @@ bool checkRegularTime(TimeSwitch &s, int &interval)
return timesAreInInterval(s.time, now, interval);
}
void SwitcherData::checkTimeSwitch(bool &match, OBSWeakSource &scene,
bool SwitcherData::checkTimeSwitch(OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (TimeSwitch::pause) {
return;
return false;
}
bool match = false;
for (TimeSwitch &s : timeSwitches) {
if (!s.initialized()) {
continue;
@@ -142,12 +143,13 @@ void SwitcherData::checkTimeSwitch(bool &match, OBSWeakSource &scene,
break;
}
}
return match;
}
void SwitcherData::saveTimeSwitches(obs_data_t *obj)
{
obs_data_array_t *timeArray = obs_data_array_create();
for (TimeSwitch &s : switcher->timeSwitches) {
for (TimeSwitch &s : timeSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
@@ -161,7 +163,7 @@ void SwitcherData::saveTimeSwitches(obs_data_t *obj)
void SwitcherData::loadTimeSwitches(obs_data_t *obj)
{
switcher->timeSwitches.clear();
timeSwitches.clear();
obs_data_array_t *timeArray = obs_data_get_array(obj, "timeSwitches");
size_t count = obs_data_array_count(timeArray);
@@ -169,7 +171,7 @@ void SwitcherData::loadTimeSwitches(obs_data_t *obj)
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(timeArray, i);
switcher->timeSwitches.emplace_back();
timeSwitches.emplace_back();
timeSwitches.back().load(array_obj);
obs_data_release(array_obj);
@@ -204,37 +206,8 @@ void TimeSwitch::save(obs_data_t *obj)
obs_data_set_string(obj, "time", time.toString().toStdString().c_str());
}
// To be removed in future version
bool loadOldTime(obs_data_t *obj, TimeSwitch *s)
{
if (!s) {
return false;
}
const char *scene = obs_data_get_string(obj, "scene");
if (strcmp(scene, "") == 0) {
return false;
}
s->scene = GetWeakSourceByName(scene);
const char *transition = obs_data_get_string(obj, "transition");
s->transition = GetWeakTransitionByName(transition);
s->trigger = (timeTrigger)obs_data_get_int(obj, "trigger");
s->time = QTime::fromString(obs_data_get_string(obj, "time"));
s->usePreviousScene = strcmp(scene, previous_scene_name) == 0;
return true;
}
void TimeSwitch::load(obs_data_t *obj)
{
if (loadOldTime(obj, this)) {
return;
}
SceneSwitcherEntry::load(obj);
trigger = (timeTrigger)obs_data_get_int(obj, "trigger");

View File

@@ -3,7 +3,9 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
constexpr auto default_def_transition_dealy = 300;
bool DefaultSceneTransition::pause = false;
unsigned int DefaultSceneTransition::delay = default_def_transition_dealy;
void AdvSceneSwitcher::on_transitionsAdd_clicked()
{
@@ -180,18 +182,48 @@ void AdvSceneSwitcher::on_transitionOverridecheckBox_stateChanged(int state)
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
if (!state) {
switcher->tansitionOverrideOverride = false;
} else {
switcher->tansitionOverrideOverride = true;
if (!state && !switcher->adjustActiveTransitionType) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError"));
ui->adjustActiveTransitionType->setChecked(true);
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->transitionOverrideOverride = state;
}
obs_weak_source_t *getNextTransition(obs_weak_source_t *scene1,
obs_weak_source_t *scene2)
void AdvSceneSwitcher::on_adjustActiveTransitionType_stateChanged(int state)
{
if (loading) {
return;
}
// This option only makes sense if we are allowed to use transition overrides
if (!state && !switcher->transitionOverrideOverride) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError"));
ui->transitionOverridecheckBox->setChecked(true);
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->adjustActiveTransitionType = state;
}
void AdvSceneSwitcher::defTransitionDelayValueChanged(int value)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
DefaultSceneTransition::delay = value;
}
std::pair<obs_weak_source_t *, int> getNextTransition(obs_weak_source_t *scene1,
obs_weak_source_t *scene2)
{
obs_weak_source_t *ws = nullptr;
int duration = 0;
if (scene1 && scene2) {
for (SceneTransition &t : switcher->sceneTransitions) {
if (!t.initialized()) {
@@ -200,28 +232,26 @@ obs_weak_source_t *getNextTransition(obs_weak_source_t *scene1,
if (t.scene == scene1 && t.scene2 == scene2) {
ws = t.transition;
duration = t.duration * 1000;
break;
}
}
obs_weak_source_addref(ws);
}
return ws;
return std::make_pair(ws, duration);
}
void overwriteTransitionOverride(obs_weak_source_t *sceneWs,
obs_source_t *transition, transitionData &td)
void overwriteTransitionOverride(sceneSwitchInfo ssi, transitionData &td)
{
obs_source_t *scene = obs_weak_source_get_source(sceneWs);
obs_source_t *scene = obs_weak_source_get_source(ssi.scene);
obs_data_t *data = obs_source_get_private_settings(scene);
td.name = obs_data_get_string(data, "transition");
td.duration = obs_data_get_int(data, "duration");
td.duration = obs_data_get_int(data, "transition_duration");
const char *name = obs_source_get_name(transition);
int duration = obs_frontend_get_transition_duration();
std::string name = GetWeakSourceName(ssi.transition);
obs_data_set_string(data, "transition", name);
obs_data_set_int(data, "transition_duration", duration);
obs_data_set_string(data, "transition", name.c_str());
obs_data_set_int(data, "transition_duration", ssi.duration);
obs_data_release(data);
obs_source_release(scene);
@@ -237,96 +267,97 @@ void restoreTransitionOverride(obs_source_t *scene, transitionData td)
obs_data_release(data);
}
void setNextTransition(OBSWeakSource &targetScene, obs_source_t *currentSource,
OBSWeakSource &transition,
bool &transitionOverrideOverride, transitionData &td)
void setNextTransition(sceneSwitchInfo &sceneSwitch,
obs_source_t *currentSource, transitionData &td)
{
// Priority:
// 1. Transition tab
// 2. Individual switcher entry
// 3. Current transition settings
// Transition Tab
obs_weak_source_t *currentScene =
obs_source_get_weak_source(currentSource);
obs_weak_source_t *nextTransitionWs =
getNextTransition(currentScene, targetScene);
auto tinfo = getNextTransition(currentScene, sceneSwitch.scene);
obs_weak_source_release(currentScene);
obs_source_t *nextTransition = nullptr;
if (nextTransitionWs) {
nextTransition = obs_weak_source_get_source(nextTransitionWs);
} else if (transition) {
nextTransition = obs_weak_source_get_source(transition);
OBSWeakSource nextTransition = tinfo.first;
int nextTransitionDuration = tinfo.second;
// Individual switcher entry
if (!nextTransition) {
nextTransition = sceneSwitch.transition;
}
if (!nextTransitionDuration) {
nextTransitionDuration = sceneSwitch.duration;
}
if (nextTransition) {
obs_frontend_set_current_transition(nextTransition);
// Current transition settings
if (!nextTransition) {
auto ct = obs_frontend_get_current_transition();
nextTransition = obs_source_get_weak_source(ct);
obs_weak_source_release(nextTransition);
obs_source_release(ct);
}
if (!nextTransitionDuration) {
nextTransitionDuration = obs_frontend_get_transition_duration();
}
if (transitionOverrideOverride) {
overwriteTransitionOverride(targetScene, nextTransition, td);
if (switcher->adjustActiveTransitionType) {
obs_frontend_set_transition_duration(nextTransitionDuration);
auto t = obs_weak_source_get_source(nextTransition);
obs_frontend_set_current_transition(t);
obs_source_release(t);
}
obs_weak_source_release(nextTransitionWs);
obs_source_release(nextTransition);
if (switcher->transitionOverrideOverride) {
overwriteTransitionOverride({sceneSwitch.scene, nextTransition,
nextTransitionDuration},
td);
}
}
void SwitcherData::saveSceneTransitions(obs_data_t *obj)
{
obs_data_array_t *sceneTransitionsArray = obs_data_array_create();
for (SceneTransition &s : switcher->sceneTransitions) {
for (SceneTransition &s : sceneTransitions) {
obs_data_t *array_obj = obs_data_create();
obs_source_t *source1 = obs_weak_source_get_source(s.scene);
obs_source_t *source2 = obs_weak_source_get_source(s.scene2);
obs_source_t *transition =
obs_weak_source_get_source(s.transition);
if (source1 && source2 && transition) {
const char *sceneName1 = obs_source_get_name(source1);
const char *sceneName2 = obs_source_get_name(source2);
const char *transitionName =
obs_source_get_name(transition);
obs_data_set_string(array_obj, "Scene1", sceneName1);
obs_data_set_string(array_obj, "Scene2", sceneName2);
obs_data_set_string(array_obj, "transition",
transitionName);
obs_data_array_push_back(sceneTransitionsArray,
array_obj);
}
obs_source_release(source1);
obs_source_release(source2);
obs_source_release(transition);
s.save(array_obj);
obs_data_array_push_back(sceneTransitionsArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "sceneTransitions", sceneTransitionsArray);
obs_data_array_release(sceneTransitionsArray);
obs_data_array_t *defaultTransitionsArray = obs_data_array_create();
for (DefaultSceneTransition &s : switcher->defaultSceneTransitions) {
for (DefaultSceneTransition &s : defaultSceneTransitions) {
obs_data_t *array_obj = obs_data_create();
obs_source_t *source = obs_weak_source_get_source(s.scene);
obs_source_t *transition =
obs_weak_source_get_source(s.transition);
if (source && transition) {
const char *sceneName = obs_source_get_name(source);
const char *transitionName =
obs_source_get_name(transition);
obs_data_set_string(array_obj, "Scene", sceneName);
obs_data_set_string(array_obj, "transition",
transitionName);
obs_data_array_push_back(defaultTransitionsArray,
array_obj);
}
obs_source_release(source);
obs_source_release(transition);
s.save(array_obj);
obs_data_array_push_back(defaultTransitionsArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "defaultTransitions", defaultTransitionsArray);
obs_data_array_release(defaultTransitionsArray);
obs_data_set_bool(obj, "tansitionOverrideOverride",
switcher->tansitionOverrideOverride);
transitionOverrideOverride);
obs_data_set_default_bool(obj, "adjustActiveTransitionType",
adjustActiveTransitionType);
obs_data_set_bool(obj, "adjustActiveTransitionType",
adjustActiveTransitionType);
obs_data_set_default_int(obj, "defTransitionDelay",
default_def_transition_dealy);
obs_data_set_int(obj, "defTransitionDelay",
DefaultSceneTransition::delay);
}
void SwitcherData::loadSceneTransitions(obs_data_t *obj)
{
switcher->sceneTransitions.clear();
sceneTransitions.clear();
obs_data_array_t *sceneTransitionsArray =
obs_data_get_array(obj, "sceneTransitions");
@@ -335,22 +366,13 @@ void SwitcherData::loadSceneTransitions(obs_data_t *obj)
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj =
obs_data_array_item(sceneTransitionsArray, i);
const char *scene1 = obs_data_get_string(array_obj, "Scene1");
const char *scene2 = obs_data_get_string(array_obj, "Scene2");
const char *transition =
obs_data_get_string(array_obj, "transition");
switcher->sceneTransitions.emplace_back(
GetWeakSourceByName(scene1),
GetWeakSourceByName(scene2),
GetWeakTransitionByName(transition));
sceneTransitions.emplace_back();
sceneTransitions.back().load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(sceneTransitionsArray);
switcher->defaultSceneTransitions.clear();
defaultSceneTransitions.clear();
obs_data_array_t *defaultTransitionsArray =
obs_data_get_array(obj, "defaultTransitions");
@@ -359,21 +381,18 @@ void SwitcherData::loadSceneTransitions(obs_data_t *obj)
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj =
obs_data_array_item(defaultTransitionsArray, i);
const char *scene = obs_data_get_string(array_obj, "Scene");
const char *transition =
obs_data_get_string(array_obj, "transition");
switcher->defaultSceneTransitions.emplace_back(
GetWeakSourceByName(scene),
GetWeakTransitionByName(transition));
defaultSceneTransitions.emplace_back();
defaultSceneTransitions.back().load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(defaultTransitionsArray);
switcher->tansitionOverrideOverride =
transitionOverrideOverride =
obs_data_get_bool(obj, "tansitionOverrideOverride");
adjustActiveTransitionType =
obs_data_get_bool(obj, "adjustActiveTransitionType");
DefaultSceneTransition::delay =
obs_data_get_int(obj, "defTransitionDelay");
}
void AdvSceneSwitcher::setupTransitionsTab()
@@ -411,7 +430,27 @@ void AdvSceneSwitcher::setupTransitionsTab()
}
ui->transitionOverridecheckBox->setChecked(
switcher->tansitionOverrideOverride);
switcher->transitionOverrideOverride);
ui->adjustActiveTransitionType->setChecked(
switcher->adjustActiveTransitionType);
QSpinBox *defTransitionDelay = new QSpinBox();
defTransitionDelay->setSuffix("ms");
defTransitionDelay->setMinimum(50);
defTransitionDelay->setMaximum(10000);
defTransitionDelay->setValue(DefaultSceneTransition::delay);
defTransitionDelay->setToolTip(obs_module_text(
"AdvSceneSwitcher.transitionTab.defaultTransition.delay.help"));
QWidget::connect(defTransitionDelay, SIGNAL(valueChanged(int)), this,
SLOT(defTransitionDelayValueChanged(int)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{defTransitionDelay}}", defTransitionDelay}};
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.transitionTab.defaultTransition.delay"),
ui->defTransitionDelayLayout, widgetPlaceholders);
}
bool SceneTransition::initialized()
@@ -425,25 +464,49 @@ bool SceneTransition::valid()
(SceneSwitcherEntry::valid() && WeakSourceValid(scene2));
}
void SceneTransition::save(obs_data_t *obj)
{
SceneSwitcherEntry::save(obj, "targetType", "Scene1");
obs_data_set_string(obj, "Scene2", GetWeakSourceName(scene2).c_str());
obs_data_set_double(obj, "duration", duration);
}
void SceneTransition::load(obs_data_t *obj)
{
SceneSwitcherEntry::load(obj, "targetType", "Scene1");
const char *sourceName = obs_data_get_string(obj, "Scene2");
scene2 = GetWeakSourceByName(sourceName);
duration = obs_data_get_double(obj, "duration");
}
TransitionSwitchWidget::TransitionSwitchWidget(QWidget *parent,
SceneTransition *s)
: SwitchWidget(parent, s, false, false, false)
{
scenes2 = new QComboBox();
duration = new QDoubleSpinBox();
duration->setMinimum(0.0);
duration->setMaximum(99.000000);
duration->setSuffix("s");
QWidget::connect(scenes2, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(Scene2Changed(const QString &)));
QWidget::connect(duration, SIGNAL(valueChanged(double)), this,
SLOT(DurationChanged(double)));
AdvSceneSwitcher::populateSceneSelection(scenes2, false);
if (s) {
scenes2->setCurrentText(GetWeakSourceName(s->scene2).c_str());
duration->setValue(s->duration);
}
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", scenes},
{"{{scenes2}}", scenes2},
{"{{duration}}", duration},
{"{{transitions}}", transitions}};
placeWidgets(obs_module_text("AdvSceneSwitcher.transitionTab.entry"),
mainLayout, widgetPlaceholders);
@@ -484,6 +547,16 @@ void TransitionSwitchWidget::Scene2Changed(const QString &text)
switchData->scene2 = GetWeakSourceByQString(text);
}
void TransitionSwitchWidget::DurationChanged(double dur)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->duration = dur;
}
DefTransitionSwitchWidget::DefTransitionSwitchWidget(QWidget *parent,
DefaultSceneTransition *s)
: SwitchWidget(parent, s, false, false, false)
@@ -522,12 +595,22 @@ void DefTransitionSwitchWidget::swapSwitchData(DefTransitionSwitchWidget *s1,
s2->setSwitchData(t);
}
void DefaultSceneTransition::save(obs_data_t *obj)
{
SceneSwitcherEntry::save(obj, "targetType", "Scene");
}
void DefaultSceneTransition::load(obs_data_t *obj)
{
SceneSwitcherEntry::load(obj, "targetType", "Scene");
}
bool DefaultSceneTransition::checkMatch(OBSWeakSource currentScene)
{
return scene == currentScene;
}
void setTransitionDelayed(OBSWeakSource transition)
void setTransitionDelayed(OBSWeakSource transition, unsigned int delay)
{
// A hardcoded delay of 50 ms before switching transition type is
// necessary due to OBS_FRONTEND_EVENT_SCENE_CHANGED seemingly firing a
@@ -535,14 +618,14 @@ void setTransitionDelayed(OBSWeakSource transition)
//
// The same is to be the case for OBS_FRONTEND_EVENT_TRANSITION_STOPPED.
//
// 50 ms was chosen as it seems to avoid the problem mentioned above and
// becuase that is the minimum value which can be chosen for the scene
// switcher's check interval.
// 50 ms was chosen as a default value as it seems to avoid the problem
// mentioned above and becuase that is the minimum value which can be
// chosen for the scene switcher's check interval.
// Thus it can be made sure that the delayed setting of the transition
// does not interfere with any new scene changes triggered by the scene
// switcher
std::this_thread::sleep_for(std::chrono::milliseconds(50));
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
obs_source_t *transitionSource = obs_weak_source_get_source(transition);
obs_frontend_set_current_transition(transitionSource);
@@ -552,6 +635,6 @@ void setTransitionDelayed(OBSWeakSource transition)
void DefaultSceneTransition::setTransition()
{
std::thread t;
t = std::thread(setTransitionDelayed, transition);
t = std::thread(setTransitionDelayed, transition, delay);
t.detach();
}

View File

@@ -9,136 +9,6 @@
bool VideoSwitch::pause = false;
static QMetaObject::Connection addPulse;
static void ScreenshotTick(void *param, float);
AdvSSScreenshotObj::AdvSSScreenshotObj(obs_source_t *source)
: weakSource(OBSGetWeakRef(source))
{
obs_add_tick_callback(ScreenshotTick, this);
}
AdvSSScreenshotObj::~AdvSSScreenshotObj()
{
obs_enter_graphics();
gs_stagesurface_destroy(stagesurf);
gs_texrender_destroy(texrender);
obs_leave_graphics();
obs_remove_tick_callback(ScreenshotTick, this);
}
void AdvSSScreenshotObj::Screenshot()
{
OBSSource source = OBSGetStrongRef(weakSource);
if (source) {
cx = obs_source_get_base_width(source);
cy = obs_source_get_base_height(source);
} else {
obs_video_info ovi;
obs_get_video_info(&ovi);
cx = ovi.base_width;
cy = ovi.base_height;
}
if (!cx || !cy) {
blog(LOG_WARNING, "Cannot screenshot, invalid target size");
obs_remove_tick_callback(ScreenshotTick, this);
done = true;
return;
}
texrender = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
stagesurf = gs_stagesurface_create(cx, cy, GS_RGBA);
gs_texrender_reset(texrender);
if (gs_texrender_begin(texrender, cx, cy)) {
vec4 zero;
vec4_zero(&zero);
gs_clear(GS_CLEAR_COLOR, &zero, 0.0f, 0);
gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f);
gs_blend_state_push();
gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
if (source) {
obs_source_inc_showing(source);
obs_source_video_render(source);
obs_source_dec_showing(source);
} else {
obs_render_main_texture();
}
gs_blend_state_pop();
gs_texrender_end(texrender);
}
}
void AdvSSScreenshotObj::Download()
{
gs_stage_texture(stagesurf, gs_texrender_get_texture(texrender));
}
void AdvSSScreenshotObj::Copy()
{
uint8_t *videoData = nullptr;
uint32_t videoLinesize = 0;
image = QImage(cx, cy, QImage::Format::Format_RGBX8888);
if (gs_stagesurface_map(stagesurf, &videoData, &videoLinesize)) {
int linesize = image.bytesPerLine();
for (int y = 0; y < (int)cy; y++)
memcpy(image.scanLine(y),
videoData + (y * videoLinesize), linesize);
gs_stagesurface_unmap(stagesurf);
}
}
void AdvSSScreenshotObj::MarkDone()
{
time = std::chrono::high_resolution_clock::now();
done = true;
}
#define STAGE_SCREENSHOT 0
#define STAGE_DOWNLOAD 1
#define STAGE_COPY_AND_SAVE 2
#define STAGE_FINISH 3
static void ScreenshotTick(void *param, float)
{
AdvSSScreenshotObj *data =
reinterpret_cast<AdvSSScreenshotObj *>(param);
if (data->stage == STAGE_FINISH) {
return;
}
obs_enter_graphics();
switch (data->stage) {
case STAGE_SCREENSHOT:
data->Screenshot();
break;
case STAGE_DOWNLOAD:
data->Download();
break;
case STAGE_COPY_AND_SAVE:
data->Copy();
data->MarkDone();
obs_remove_tick_callback(ScreenshotTick, data);
break;
}
obs_leave_graphics();
data->stage++;
}
void AdvSceneSwitcher::on_videoAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
@@ -257,13 +127,14 @@ void AdvSceneSwitcher::on_getScreenshot_clicked()
sw->SetFilePath(file.fileName());
}
void SwitcherData::checkVideoSwitch(bool &match, OBSWeakSource &scene,
bool SwitcherData::checkVideoSwitch(OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (VideoSwitch::pause) {
return;
return false;
}
bool match = false;
for (auto &s : videoSwitches) {
bool matched = s.checkMatch();
if (!match && matched) {
@@ -275,6 +146,7 @@ void SwitcherData::checkVideoSwitch(bool &match, OBSWeakSource &scene,
}
}
}
return match;
}
void SwitcherData::saveVideoSwitches(obs_data_t *obj)
@@ -294,7 +166,7 @@ void SwitcherData::saveVideoSwitches(obs_data_t *obj)
void SwitcherData::loadVideoSwitches(obs_data_t *obj)
{
switcher->videoSwitches.clear();
videoSwitches.clear();
obs_data_array_t *videoArray = obs_data_get_array(obj, "videoSwitches");
size_t count = obs_data_array_count(videoArray);
@@ -302,7 +174,7 @@ void SwitcherData::loadVideoSwitches(obs_data_t *obj)
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(videoArray, i);
switcher->videoSwitches.emplace_back();
videoSwitches.emplace_back();
videoSwitches.back().load(array_obj);
obs_data_release(array_obj);
@@ -355,6 +227,11 @@ void VideoSwitch::save(obs_data_t *obj)
obs_data_set_bool(obj, "ignoreInactiveSource", ignoreInactiveSource);
}
bool requiresFileInput(videoSwitchType t)
{
return t == videoSwitchType::MATCH || t == videoSwitchType::DIFFER;
}
void VideoSwitch::load(obs_data_t *obj)
{
SceneSwitcherEntry::load(obj);
@@ -367,7 +244,7 @@ void VideoSwitch::load(obs_data_t *obj)
file = obs_data_get_string(obj, "filePath");
ignoreInactiveSource = obs_data_get_bool(obj, "ignoreInactiveSource");
if (condition != videoSwitchType::HAS_NOT_CHANGED) {
if (requiresFileInput(condition)) {
(void)loadImageFromFile();
}
}
@@ -423,6 +300,10 @@ bool VideoSwitch::checkMatch()
conditionMatch = screenshotData->image ==
matchImage;
break;
case videoSwitchType::HAS_CHANGED:
conditionMatch = screenshotData->image !=
matchImage;
break;
default:
break;
}
@@ -444,7 +325,7 @@ bool VideoSwitch::checkMatch()
match = true;
}
if (condition == videoSwitchType::HAS_NOT_CHANGED) {
if (!requiresFileInput(condition)) {
matchImage = std::move(screenshotData->image);
}
previousTime = std::move(screenshotData->time);
@@ -480,6 +361,8 @@ static inline void populateConditionSelection(QComboBox *list)
obs_module_text("AdvSceneSwitcher.videoTab.condition.differ"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.videoTab.condition.hasNotChanged"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.videoTab.condition.hasChanged"));
}
VideoSwitchWidget::VideoSwitchWidget(QWidget *parent, VideoSwitch *s)
@@ -530,7 +413,7 @@ VideoSwitchWidget::VideoSwitchWidget(QWidget *parent, VideoSwitch *s)
filePath->setText(QString::fromStdString(s->file));
ignoreInactiveSource->setChecked(s->ignoreInactiveSource);
if (s->condition == videoSwitchType::HAS_NOT_CHANGED) {
if (!requiresFileInput(s->condition)) {
filePath->hide();
browseButton->hide();
}
@@ -582,8 +465,7 @@ void VideoSwitchWidget::swapSwitchData(VideoSwitchWidget *s1,
void VideoSwitchWidget::UpdatePreviewTooltip()
{
if (!switchData ||
switchData->condition == videoSwitchType::HAS_NOT_CHANGED) {
if (!switchData || !requiresFileInput(switchData->condition)) {
return;
}
@@ -627,12 +509,12 @@ void VideoSwitchWidget::ConditionChanged(int cond)
std::lock_guard<std::mutex> lock(switcher->m);
switchData->condition = static_cast<videoSwitchType>(cond);
if (switchData->condition == videoSwitchType::HAS_NOT_CHANGED) {
filePath->hide();
browseButton->hide();
} else {
if (requiresFileInput(switchData->condition)) {
filePath->show();
browseButton->show();
} else {
filePath->hide();
browseButton->hide();
}
// Reload image data to avoid incorrect matches.

View File

@@ -176,7 +176,7 @@ void checkWindowTitleSwitchDirect(WindowSwitch &s,
OBSWeakSource &scene,
OBSWeakSource &transition)
{
bool focus = s.window == currentWindowTitle;
bool focus = (!s.focus || s.window == currentWindowTitle);
bool fullscreen = (!s.fullscreen || isFullscreen(s.window));
bool max = (!s.maximized || isMaximized(s.window));
@@ -202,7 +202,7 @@ void checkWindowTitleSwitchRegex(WindowSwitch &s,
} catch (const std::regex_error &) {
}
bool focus = window == currentWindowTitle;
bool focus = (!s.focus || window == currentWindowTitle);
bool fullscreen = (!s.fullscreen || isFullscreen(window));
bool max = (!s.maximized || isMaximized(window));
@@ -214,15 +214,16 @@ void checkWindowTitleSwitchRegex(WindowSwitch &s,
}
}
void SwitcherData::checkWindowTitleSwitch(bool &match, OBSWeakSource &scene,
bool SwitcherData::checkWindowTitleSwitch(OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (WindowSwitch::pause) {
return;
return false;
}
std::string currentWindowTitle;
GetCurrentWindowTitle(currentWindowTitle);
bool match = false;
// Check if current window is ignored
for (auto &window : ignoreWindowsSwitches) {
@@ -268,12 +269,13 @@ void SwitcherData::checkWindowTitleSwitch(bool &match, OBSWeakSource &scene,
break;
}
}
return match;
}
void SwitcherData::saveWindowTitleSwitches(obs_data_t *obj)
{
obs_data_array_t *windowTitleArray = obs_data_array_create();
for (WindowSwitch &s : switcher->windowSwitches) {
for (WindowSwitch &s : windowSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
@@ -285,7 +287,7 @@ void SwitcherData::saveWindowTitleSwitches(obs_data_t *obj)
obs_data_array_release(windowTitleArray);
obs_data_array_t *ignoreWindowsArray = obs_data_array_create();
for (std::string &window : switcher->ignoreWindowsSwitches) {
for (std::string &window : ignoreWindowsSwitches) {
obs_data_t *array_obj = obs_data_create();
obs_data_set_string(array_obj, "ignoreWindow", window.c_str());
obs_data_array_push_back(ignoreWindowsArray, array_obj);
@@ -297,7 +299,7 @@ void SwitcherData::saveWindowTitleSwitches(obs_data_t *obj)
void SwitcherData::loadWindowTitleSwitches(obs_data_t *obj)
{
switcher->windowSwitches.clear();
windowSwitches.clear();
obs_data_array_t *windowTitleArray =
obs_data_get_array(obj, "switches");
@@ -307,14 +309,14 @@ void SwitcherData::loadWindowTitleSwitches(obs_data_t *obj)
obs_data_t *array_obj =
obs_data_array_item(windowTitleArray, i);
switcher->windowSwitches.emplace_back();
windowSwitches.emplace_back();
windowSwitches.back().load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(windowTitleArray);
switcher->ignoreWindowsSwitches.clear();
ignoreWindowsSwitches.clear();
obs_data_array_t *ignoreWindowsArray =
obs_data_get_array(obj, "ignoreWindows");
@@ -327,7 +329,7 @@ void SwitcherData::loadWindowTitleSwitches(obs_data_t *obj)
const char *window =
obs_data_get_string(array_obj, "ignoreWindow");
switcher->ignoreWindowsSwitches.emplace_back(window);
ignoreWindowsSwitches.emplace_back(window);
obs_data_release(array_obj);
}
@@ -379,46 +381,8 @@ void WindowSwitch::save(obs_data_t *obj)
obs_data_set_bool(obj, "focus", focus);
}
// To be removed in future version
bool loadOldWindow(obs_data_t *obj, WindowSwitch *s)
{
if (!s) {
return false;
}
const char *scene = obs_data_get_string(obj, "scene");
if (strcmp(scene, "") == 0) {
return false;
}
s->scene = GetWeakSourceByName(scene);
const char *transition = obs_data_get_string(obj, "transition");
s->transition = GetWeakTransitionByName(transition);
s->window = obs_data_get_string(obj, "window_title");
s->fullscreen = obs_data_get_bool(obj, "fullscreen");
#if __APPLE__
// TODO:
// not implemented on MacOS as I cannot test it
s->maximized = false;
#else
s->maximized = obs_data_get_bool(obj, "maximized");
#endif
s->focus = obs_data_get_bool(obj, "focus") ||
!obs_data_has_user_value(obj, "focus");
s->usePreviousScene = strcmp(scene, previous_scene_name) == 0;
return true;
}
void WindowSwitch::load(obs_data_t *obj)
{
if (loadOldWindow(obj, this)) {
return;
}
SceneSwitcherEntry::load(obj);
window = obs_data_get_string(obj, "windowTitle");

View File

@@ -1,11 +1,14 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <util/platform.h>
#include "../headers/advanced-scene-switcher.hpp"
#include <TlHelp32.h>
#include <Psapi.h>
#include <locale>
#include <codecvt>
#include <string>
#include <vector>
#include <QStringList>
#include <QRegularExpression>
#define MAX_SEARCH 1000