Compare commits

..

101 Commits
1.6.2 ... 1.10

Author SHA1 Message Date
WarmUpTill
e4bb034303 Add video tab (#131)
This tab will allow switching scenes based on the video output of sources.
2021-03-05 14:08:17 +01:00
WarmUpTill
78e14df620 Add german locale for extended sequences (#135) 2021-03-01 20:25:18 +01:00
WarmUpTill
34f07bfb07 Clean up extended sequence (#134)
* Update original switch widget status if changed in extend edit

* Clean up

* Add more comments

* Clean up unecessary code.

Sleep no longer happens inside the sequence switch check so we don't
need to stop / start the switcher after importing new entries or deleting
existing entries.
2021-02-28 15:38:33 +01:00
WarmUpTill
ee74d39424 Fixes and Minor rework (#132)
* Fix warning in scene trigger log functions.
* Use GetWeakSourceName() when saving scene trigger sources.
* Fix prioFuncsValid().
2021-02-26 19:21:02 +01:00
WarmUpTill
33667a1f23 Fix crash on advancing to unset scene for extended sequence (#130) 2021-02-24 18:53:52 +01:00
WarmUpTill
7947ad2608 Add option to extend scene sequences (#121)
Extending a scene sequence allows for more complex switching setups, but should also simplify some setups.
Sequences like "A -> B -> A -> C", which previously were only possible by creating a copy of A, can now be specified in a single entry.
To extend a sequence either select the sequence you want to modify and click the extend sequence button or simply double click the entry.
2021-02-24 18:19:23 +01:00
WarmUpTill
920c188213 Add scene trigger for starting and stopping the scene switcher (#129) 2021-02-23 21:00:25 +01:00
WarmUpTill
28fbff1adc Allow specifying transition when reading scenes from file. (#128)
The transition can be specified on the second line of the file.
For example:
Scene 1
Slide
2021-02-21 20:17:32 +01:00
WarmUpTill
326617aad7 Fix checkSwitchInfoFromFile ignoring file switch puase (#127) 2021-02-21 20:16:51 +01:00
WarmUpTill
499ba7dd21 Update the UI's status element if the status changed externally (#126) 2021-02-20 18:58:07 +01:00
WarmUpTill
f4902933ab Fix getCurrentScene() returning garbage if scene group has type random (#125) 2021-02-20 18:08:47 +01:00
WarmUpTill
9aa0b31bff Add scene trigger to unmute source (#124) 2021-02-20 18:08:35 +01:00
WarmUpTill
6d5086b971 Add option to select 'current transition' (#123)
* move addSelectionEntry to AdvSceneSwitcher class and allow select entry to be 'selectable'

Can be useful for implementing 'current transition' as an option or just allowing to select 'None' for screen region ignore

* Add option to select 'current transition'
2021-02-19 19:03:38 +01:00
WarmUpTill
f32f47db4b Install qtbase5-private-dev on Linux (#122) 2021-02-13 19:28:24 +01:00
WarmUpTill
74c0cd1736 Adjust sleep log message and comment for sceneChangedDuringWait() (#120) 2021-02-13 19:14:28 +01:00
WarmUpTill
a74a952b1d Fix log messages containing two '[adv-ss]' tags (#118) 2021-02-07 18:41:47 +01:00
WarmUpTill
5bbfef81e0 Improve scene trigger handling (#117) 2021-02-06 18:23:57 +01:00
WarmUpTill
c31d1cd975 Delay setting of default scene transitions (#116) 2021-02-06 18:23:41 +01:00
WarmUpTill
81d91a9139 resovle name conflict (#115) 2021-02-04 07:24:52 +01:00
WarmUpTill
a354daa71d Fix regex matching for window tab (#113)
Regex matches could never match if full-screen or maximized was selected due to only checking for direct matches during these checks.
2021-02-03 20:46:25 +01:00
WarmUpTill
a84a205608 Minor style changes (#114)
* use GetWeakSourceName() where possible

* add getCurrentScene() to scene group

* silence OBS warning log and fix typo in audio switch

* extract showing initial status of widgets to function in generic switch
2021-02-03 20:27:25 +01:00
WarmUpTill
a5f0eb8da8 Add option to automatically start scene switcher on recording / streaming start (#112) 2021-02-03 20:02:07 +01:00
WarmUpTill
35151ea134 Add scene trigger tab (#110) 2021-02-03 19:50:37 +01:00
Kevin Nash
1510a2c2fa Specify scenes to exclude from Region triggers (#111)
Adds an option to exclude a region triggered scene switch if you're
currently in the specified Scene.
2021-02-03 19:49:18 +01:00
WarmUpTill
8e8cb4bf3e use exportWindowTitle on export of settings 2021-01-27 19:05:46 +01:00
WarmUpTill
a480be3ea0 Update installation instructions 2021-01-25 22:15:40 +01:00
WarmUpTill
27ae5107cd Add help messages if tab is empty (#107) 2021-01-24 16:04:40 +01:00
WarmUpTill
69d452f978 allow selection of previous scene for exe,file,region,window and scene group for idle switches (#106) 2021-01-23 23:21:59 +01:00
WarmUpTill
fe9457427f add linger variable instead of sleeping inside sequence match (#105)
This solves issues with the busy loop detection and adds the possibility of other switching methods to delay the scene switch
2021-01-23 20:32:32 +01:00
WarmUpTill
d989c2b570 Add scene groups and rework save and load (#103) 2021-01-23 17:39:35 +01:00
WarmUpTill
1f4679433e ask for backup of settings if a new version detected (#104) 2021-01-23 17:08:17 +01:00
WarmUpTill
b42de8d5c8 fix crash on settings import (#102)
This crash could happen if the scene switcher was currently stuck waiting in a scene sequence causing while the import of new settings took place potentially invalidating the settings of the sequence after wakeup.
2021-01-20 19:01:56 +01:00
WarmUpTill
0950d93828 allow selection of target for auto stop of recoding or streaming 2021-01-18 01:20:56 +01:00
WarmUpTill
5156c66b26 add warning if translation cannot be found (#99)
* add warning if translation cannot be found

* extract displaying message boxes to separate function
2021-01-09 14:27:51 +01:00
WarmUpTill
346240473a use maximum peak value during switch interval for audio match (#98)
This change should in theory improve the situation, so "pauses" - like they happen in human speech - in the range less than the configured scene switcher check interval will still result in a match, if there was at least a short moment where the configured volume threshold was reached.

Also extract attaching of the volmeter to an audio source to a separate function
2021-01-09 09:10:15 +01:00
WarmUpTill
17c4fefb37 add fallback option if multiple audio matches are active (#95) 2021-01-07 18:59:06 +01:00
WarmUpTill
2b90493ae9 add switch cooldown option (#97)
* add switch cooldown functionality

* slightly adjust sequence check for cooldown

* add UI implementation for cooldown feature
2021-01-07 18:58:35 +01:00
WarmUpTill
bf97975fa8 link to Transition Table instead of Transition Matrix plugin (#96) 2021-01-06 12:25:45 +01:00
WarmUpTill
4e971f59a9 add option to allow scene sequence to be interrupted (#93) 2021-01-02 14:32:18 +01:00
WarmUpTill
2f0b900745 fix invalid audio matches 2020-12-31 16:59:52 +01:00
WarmUpTill
c6a25f9d85 fix typo 2020-12-31 16:19:58 +01:00
WarmUpTill
e78383d3ed clean up build and build instructions (#92)
* fix build issue with atomic bool initialization

* clean up cmake

* fix warning

* add missing '\' to linux instructions
2020-12-31 15:45:08 +01:00
WarmUpTill
281e9325dd update locale (#91)
* add german locale

* adjust description of ignore window functionality

* adjust order of readWriteSceneFile

* allow translation of 'previous scene'
2020-12-31 14:59:05 +01:00
WarmUpTill
caa2833272 allow pause individual switching methods (#90)
* allow pausing individual switching methods

* clean up pause tab

* allow import of old pauseScenes and pauseWindows
2020-12-31 14:24:09 +01:00
WarmUpTill
fae50024ea rename mediaSources 2020-12-28 17:38:34 +01:00
WarmUpTill
a05e38d3cb fix typos 2020-12-28 17:28:57 +01:00
WarmUpTill
d73749fc3a update README.txt 2020-12-28 17:14:11 +01:00
WarmUpTill
d20cdac5e4 add duration and 'below' condition type options to audio tab (#85) 2020-12-28 17:06:30 +01:00
WarmUpTill
e377be3999 Minor UI changes (#89)
* automatically scroll to new entry

* fix remote file check always matching

* sort combobox entries alphabetically (meida / processes / audio)

* fix some visual issues on non-default OBS themes
2020-12-28 17:03:55 +01:00
WarmUpTill
80e2b3dd14 fix new time matching if QTime is not initialized 2020-12-28 17:02:32 +01:00
WarmUpTill
3a05e61814 add windows installer (#88) 2020-12-23 01:51:48 +01:00
WarmUpTill
dda44a2931 create release artifact (#84) 2020-12-21 22:52:25 +01:00
shugen002
8ba78b12c1 I18n Support (#72)
* adapt UI for i18n

* add placeWidgets() helper function to ease translation

* add hint about Transition Matrix plugin on Transition tab

* translate

* rework UI of file tab

* write status of plugin to scene info file
2020-12-21 11:24:04 +01:00
WarmUpTill
07b95cb8ae do not sleep less than 1ms (#82) 2020-12-13 17:26:59 +01:00
WarmUpTill
faca5f8863 add option to only match if file content changed (#80) 2020-12-12 16:31:07 +01:00
WarmUpTill
084c263260 always match checkRandom() if random tab only has one entry 2020-12-10 20:14:07 +01:00
WarmUpTill
a1c592f1ec format 2020-12-10 20:14:07 +01:00
WarmUpTill
41f34f8f40 remove runtime of check() functions from interval sleep 2020-12-10 20:14:07 +01:00
WarmUpTill
9711390aac add no match delay option (#78) 2020-12-06 14:10:17 +01:00
WarmUpTill
25f28b9796 Add media state played to end (#58) 2020-12-06 14:09:21 +01:00
WarmUpTill
a1484f9621 fix warning 2020-12-06 14:03:04 +01:00
WarmUpTill
b54506c5ee add 'OBS' entry to GetWindowList() on Windows(#76) 2020-11-28 16:23:17 +01:00
WarmUpTill
4af7540497 hide guide frames when switching to a different tab (#75) 2020-11-28 12:36:58 +01:00
Guillaume Turchini
ba15e6812f Add a log when auto stopping stream / record (#74)
* Add a log when auto stopping stream / record

I had a hard time deciphering why the stream stopped automatically for someone when using the plugin as the logs contained nothing indicating an error or an action from the plugin.
He activated the checkbox to auto stop stream when a scene is activated (which I didn't know) so the plugin was doing exactly what asked.
I think adding a log is a good idea for an action as important as stopping the stream.
I added the same for starting stream for the same reasons.
2020-11-27 17:34:17 +01:00
WarmUpTill
22a90c7d64 fix crash on deleting last screen region entry when guide frame was active (#70) 2020-11-24 22:57:52 +01:00
WarmUpTill
f6c3c65e26 resolve potential deadlocks (#66)
A deadlock could occur if the save callback is called just before the frontend set functions are used.
Thus unlock() is necessary before setting current scene or transition.
(e.g. if start recording is activated just before using the above functions, as this apparently triggers a save() call)
2020-11-21 01:40:24 +01:00
WarmUpTill
4f6275619a Recommend building in-tree 2020-11-20 22:02:12 +01:00
WarmUpTill
d461342835 Update github actions (#65)
* fix macOS build

* remove unused BUILD_CAPTIONS variable
2020-11-20 22:00:33 +01:00
shugen002
16c4a2d5a9 Adjust UI And Improve (#63) 2020-11-20 19:10:06 +01:00
WarmUpTill
45a880d8f8 Add version info log (#61)
add generated version info log on startup of the plugin using the "GetGitRevisionDescription" cmake module from https://github.com/rpavlik/cmake-modules
2020-11-15 20:34:11 +01:00
WarmUpTill
bcf132685b Update CI (#60)
* Remove deprecated use of set-env in Github Actions

* use newer versions of checkout / upload-artifact / setup-msbuild
2020-11-14 19:32:16 +01:00
WarmUpTill
e5c1e81d81 clean up and silence warnings 2020-11-14 18:45:25 +01:00
WarmUpTill
2ae97cd90e Merge branch 'master' of https://github.com/WarmUpTill/SceneSwitcher 2020-11-14 17:11:21 +01:00
WarmUpTill
e5fffa0b82 log if sequence is canceled if verbose 2020-11-10 18:31:42 +01:00
WarmUpTill
a0119426d6 remove dead code 2020-11-10 18:31:42 +01:00
WarmUpTill
45e5738d2c do not hold main lock in callback handling 2020-11-10 18:31:42 +01:00
WarmUpTill
cdd1259f12 style changes 2020-11-09 22:20:58 +01:00
WarmUpTill
e5d6f2eef0 Merge branch 'master' of https://github.com/WarmUpTill/SceneSwitcher 2020-11-07 18:57:00 +01:00
WarmUpTill
e026bd7420 fix crash on first startup on fresh install of OBS 2020-11-07 18:56:08 +01:00
WarmUpTill
4cc1a53244 fix build by enabling BUILD_CAPTIONS (#56) 2020-11-02 19:44:30 +01:00
WarmUpTill
8eb99a3fde fix comment 2020-11-02 19:06:51 +01:00
WarmUpTill
bf30a058ee fix crash on deleting scene sequences
This crash could occur when a scene sequence was currently in progress and the entries were changed while in the 'wait for ...' state.
2020-11-02 19:05:19 +01:00
WarmUpTill
0c6a7b140e add comment about use of 'matched' 2020-11-02 19:00:50 +01:00
WarmUpTill
d279b35086 fix crash on importing scene sequence
This crash could occur when a scene sequence was currently in progress and the entries were changed while in the 'wait for ...' state.
2020-11-02 18:38:26 +01:00
WarmUpTill
8e26148255 fix media states being retriggered multiple times (#54) 2020-11-01 18:53:48 +01:00
WarmUpTill
1a3ed0d09c add clang format check to CI and adjust formatting (#55) 2020-11-01 12:39:14 +01:00
WarmUpTill
214821b69f UI rework (#44)
* rework general tab UI

* rework audio tab UI

* rework media tab UI

* rework time tab UI

* rework random tab UI

* rework window tab UI

* rework sreen region tab UI

* rework sequence tab UI

* rework transition tab UI

* adjust log messages to new format

* fix include path and warning

* highlight widgets to guide users (can be disabled)

* add helper frame for screen region tab

* rename main SceneSwitcher widget to AdvSceneSwitcher to resolve naming conflicts with the frontend tools on certain platforms (e.g. Debian 10)

* add media state 'any'

* adjust media switch handling to support vlc source
2020-10-31 16:25:44 +01:00
WarmUpTill
7edb6eb6c1 add handling of window title of metro apps 2020-10-24 15:23:08 +02:00
WarmUpTill
46916dc5d3 workaround for MacOS CI issue (#52) 2020-10-22 21:56:54 +02:00
WarmUpTill
8aed15f3a8 allow WS_EX_TOOLWINDOW as valid windows to match 'Task Switching' 2020-10-22 19:44:23 +02:00
WarmUpTill
1e6a022787 improve hotkey handling (#49) 2020-10-12 20:55:51 +02:00
WarmUpTill
fb1eac5268 fix crash on exit when on autoStart scene (#47) 2020-10-10 01:56:08 +02:00
WarmUpTill
787b664d29 Add vlc source to media tab (#42) 2020-09-30 19:07:40 +02:00
WarmUpTill
dd63ff846c Refactor, fixes and maximized option for title tab (#40)
* move definitions of switcher structs to separate files

* remove useless comment

* cleanup and silence warnings

* add generic switch

* removed delay on main thread startup as it served no purpose

* clean up ultility.hpp

* do not save switchStr to file as it could cause issues when scenes are renamed

* rename sceneRoundTrip to sceneSequence

* add option to switch if window is maximized to title tab
2020-09-25 21:42:58 +02:00
WarmUpTill
1559d437ec adjust install_name_tool calls to new qt dir (#39) 2020-09-21 22:24:47 +02:00
WarmUpTill
8d023ee385 Add audio tab (#36)
* add volumemeter widget

* add audio tab UI

* add checkAudioSwitch functionality

* use constexpr instead of preprocessing
2020-09-21 00:52:42 +02:00
WarmUpTill
3fd3801e56 load libcurl at runtime (#33)
* load libcurl at runtime

* add warning to file tab if curl cant be loaded
2020-09-13 09:13:09 +02:00
WarmUpTill
3ec1e99f4b fix MacOS CI issues due to QT dependency (#35)
* fix MacOS CI issues due to QT deps

* adjust to new name of deps archive

* use MACOS_DEPS_VERSION instead of OSX_DEPS_VERSION

* disable python dependency during build
2020-09-13 01:30:08 +02:00
WarmUpTill
21a788b48b Improve time tab (#34)
* add support for weekdays and allow reordering on time tab

* add option to use time relative to streaming/recording start on time tab
2020-09-13 00:03:38 +02:00
WarmUpTill
a5c8d776f3 only try auto start streaming/recording once on scene chagne 2020-08-24 20:55:54 +02:00
WarmUpTill
95261ce9bb add first draft of auto start of recording / streaming 2020-08-16 19:48:54 +02:00
76 changed files with 16509 additions and 7321 deletions

View File

@@ -15,16 +15,16 @@ jobs:
name: "macOS 64-bit"
runs-on: [macos-latest]
env:
QT_VERSION: 5.14.1
OSX_DEPS_VERSION: '2020-04-07'
QT_VERSION: '5.15.2'
MACOS_DEPS_VERSION: '2020-12-11'
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v2.3.4
with:
repository: obsproject/obs-studio
submodules: 'recursive'
- name: "Checkout plugin"
uses: actions/checkout@v2
uses: actions/checkout@v2.3.4
with:
path: UI/frontend-plugins/${{ env.PLUGIN_NAME }}
- name: Fetch Git Tags
@@ -34,22 +34,35 @@ jobs:
- name: 'Install prerequisites (Homebrew)'
shell: bash
run: |
cd UI/frontend-plugins/${{ env.PLUGIN_NAME }}/CI/macos
brew update
brew bundle
cd -
if [ -d /usr/local/opt/openssl@1.0.2t ]; then
brew uninstall openssl@1.0.2t
brew untap local/openssl
fi
if [ -d /usr/local/opt/python@2.7.17 ]; then
brew uninstall python@2.7.17
brew untap local/python2
fi
brew bundle --file ./CI/scripts/macos/Brewfile
- name: 'Install prerequisite: Pre-built dependencies'
if: steps.deps-cache.outputs.cache-hit != 'true'
shell: bash
run: |
curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.OSX_DEPS_VERSION }}/osx-deps-${{ env.OSX_DEPS_VERSION }}.tar.gz
tar -xf ./osx-deps-${{ env.OSX_DEPS_VERSION }}.tar.gz -C "/tmp"
curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/macos-deps-${{ env.MACOS_DEPS_VERSION }}.tar.gz
tar -xf ./macos-deps-${{ env.MACOS_DEPS_VERSION }}.tar.gz -C "/tmp"
- name: 'Install prerequisite: Pre-built dependency Qt'
if: steps.deps-qt-cache.outputs.cache-hit != 'true'
shell: bash
run: |
curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/macos-qt-${{ env.QT_VERSION }}-${{ env.MACOS_DEPS_VERSION }}.tar.gz
tar -xf ./macos-qt-${{ env.QT_VERSION }}-${{ env.MACOS_DEPS_VERSION }}.tar.gz -C "/tmp"
xattr -r -d com.apple.quarantine /tmp/obsdeps
- name: Configure
shell: bash
run: |
echo "add_subdirectory(${{ env.PLUGIN_NAME }})" >> UI/frontend-plugins/CMakeLists.txt
mkdir ./build
cd ./build
cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 -DDepsPath="/tmp/obsdeps" -DQTDIR="/usr/local/Cellar/qt/${{ env.QT_VERSION }}" ..
cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -DDISABLE_PYTHON=ON -DDepsPath="/tmp/obsdeps" -DQTDIR="/tmp/obsdeps" ..
cd -
- name: Build
shell: bash
@@ -71,19 +84,19 @@ jobs:
cd UI/frontend-plugins/${{ env.PLUGIN_NAME }}
install_name_tool -change @rpath/libobs-frontend-api.dylib @executable_path/../Frameworks/libobs-frontend-api.dylib ../../../build/UI/frontend-plugins/SceneSwitcher/advanced-scene-switcher.so
install_name_tool -change @rpath/libobs.0.dylib @executable_path/../Frameworks/libobs.0.dylib ../../../build/UI/frontend-plugins/SceneSwitcher/advanced-scene-switcher.so
install_name_tool -change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets ../../../build/UI/frontend-plugins/SceneSwitcher/advanced-scene-switcher.so
install_name_tool -change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui ../../../build/UI/frontend-plugins/SceneSwitcher/advanced-scene-switcher.so
install_name_tool -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore ../../../build/UI/frontend-plugins/SceneSwitcher/advanced-scene-switcher.so
install_name_tool -change /tmp/obsdeps/lib/QtWidgets.framework/Versions/5/QtWidgets @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets ../../../build/UI/frontend-plugins/SceneSwitcher/advanced-scene-switcher.so
install_name_tool -change /tmp/obsdeps/lib/QtGui.framework/Versions/5/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui ../../../build/UI/frontend-plugins/SceneSwitcher/advanced-scene-switcher.so
install_name_tool -change /tmp/obsdeps/lib/QtCore.framework/Versions/5/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore ../../../build/UI/frontend-plugins/SceneSwitcher/advanced-scene-switcher.so
FILE_DATE=$(date +%Y-%m-%d)
FILE_NAME=${{ env.PLUGIN_NAME }}-$FILE_DATE-${{ github.sha }}-macos.pkg
echo "::set-env name=FILE_NAME::${FILE_NAME}"
echo "FILE_NAME=${FILE_NAME}" >> $GITHUB_ENV
packagesbuild ./CI/macos/${{ env.PLUGIN_NAME }}.pkgproj
cd -
mkdir ./nightly
mv UI/frontend-plugins/${{ env.PLUGIN_NAME }}/${{ env.PLUGIN_NAME }}.pkg ./nightly/${FILE_NAME}
- name: Publish
if: success()
uses: actions/upload-artifact@v2-preview
uses: actions/upload-artifact@v2.2.1
with:
name: '${{ env.FILE_NAME }}'
path: ./nightly/*.pkg
@@ -92,12 +105,12 @@ jobs:
runs-on: [ubuntu-latest]
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v2.3.4
with:
repository: obsproject/obs-studio
submodules: 'recursive'
- name: "Checkout plugin"
uses: actions/checkout@v2
uses: actions/checkout@v2.3.4
with:
path: UI/frontend-plugins/${{ env.PLUGIN_NAME }}
- name: Add plugin to obs cmake
@@ -148,6 +161,7 @@ jobs:
pkg-config \
python3-dev \
qtbase5-dev \
qtbase5-private-dev \
libqt5svg5-dev \
swig \
libxss-dev
@@ -156,7 +170,7 @@ jobs:
run: |
mkdir ./build
cd ./build
cmake -DUNIX_STRUCTURE=0 -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/obs-studio-portable" -DBUILD_CAPTIONS=OFF -DWITH_RTMPS=OFF -DBUILD_BROWSER=OFF ..
cmake -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
@@ -166,32 +180,34 @@ jobs:
run: |
FILE_DATE=$(date +%Y-%m-%d)
FILE_NAME=${{ env.PLUGIN_NAME }}-$FILE_DATE-${{ github.sha }}-linux64.tar.gz
echo "::set-env name=FILE_NAME::${FILE_NAME}"
mkdir -p ./${{ env.PLUGIN_NAME }}/bin/64bit/
mv ./build/UI/frontend-plugins/${{ env.PLUGIN_NAME }}/${{ env.LIB_NAME }}.so ./${{ env.PLUGIN_NAME }}/bin/64bit/${{ env.LIB_NAME }}.so
tar -cvzf "${FILE_NAME}" ${{ env.PLUGIN_NAME }}
echo "FILE_NAME=${FILE_NAME}" >> $GITHUB_ENV
mkdir -p ./${{ env.LIB_NAME }}/bin/64bit/
mv ./build/UI/frontend-plugins/${{ env.PLUGIN_NAME }}/${{ env.LIB_NAME }}.so ./${{ env.LIB_NAME }}/bin/64bit/${{ env.LIB_NAME }}.so
mv ./UI/frontend-plugins/${{ env.PLUGIN_NAME }}/data ./${{ env.LIB_NAME }}/data
tar -cvzf "${FILE_NAME}" ${{ env.LIB_NAME }}
- name: 'Publish'
uses: actions/upload-artifact@v2-preview
uses: actions/upload-artifact@v2.2.1
with:
name: '${{ env.FILE_NAME }}'
path: '*.tar.gz'
win64:
name: Windows 64-bit
windows:
name: Windows
runs-on: [windows-latest]
env:
QT_VERSION: 5.10.1
CMAKE_GENERATOR: "Visual Studio 16 2019"
CMAKE_SYSTEM_VERSION: "10.0.18363.657"
WINDOWS_DEPS_VERSION: '2017'
steps:
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.0
uses: microsoft/setup-msbuild@v1.0.2
- name: Checkout obs
uses: actions/checkout@v2
uses: actions/checkout@v2.3.4
with:
repository: obsproject/obs-studio
submodules: 'recursive'
- name: Checkout plugin
uses: actions/checkout@v2
uses: actions/checkout@v2.3.4
with:
path: UI/frontend-plugins/${{ env.PLUGIN_NAME}}
- name: Add plugin to obs cmake
@@ -205,81 +221,82 @@ jobs:
7z x Qt_${{ env.QT_VERSION }}.7z -o"${{ github.workspace }}/cmbuild/QT"
- name: 'Install prerequisite: Pre-built dependencies'
run: |
curl -kLO https://cdn-fastly.obsproject.com/downloads/dependencies2017.zip -f --retry 5 -C -
7z x dependencies2017.zip -o"${{ github.workspace }}/cmbuild/deps"
curl -kLO https://cdn-fastly.obsproject.com/downloads/dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -f --retry 5 -C -
7z x dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -o"${{ github.workspace }}/cmbuild/deps"
- name: Configure
run: |
mkdir ./build
mkdir ./package
mkdir ./build32
cd ./build32
cmake -G"${{ env.CMAKE_GENERATOR }}" -A"Win32" -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DBUILD_BROWSER=false -DBUILD_CAPTIONS=false -DCOMPILE_D3D12_HOOK=false -DDepsPath="${{ github.workspace }}/cmbuild/deps/win32" -DQTDIR="${{ github.workspace }}/cmbuild/QT/${{ env.QT_VERSION }}/msvc2017" -DCOPIED_DEPENDENCIES=FALSE -DCOPY_DEPENDENCIES=TRUE ..
cd ..
mkdir ./build64
cd ./build64
cmake -G"${{ env.CMAKE_GENERATOR }}" -A"x64" -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DBUILD_BROWSER=false -DBUILD_CAPTIONS=false -DCOMPILE_D3D12_HOOK=false -DDepsPath="${{ github.workspace }}/cmbuild/deps/win64" -DQTDIR="${{ github.workspace }}/cmbuild/QT/${{ env.QT_VERSION }}/msvc2017_64" -DCOPIED_DEPENDENCIES=FALSE -DCOPY_DEPENDENCIES=TRUE ..
- name: Build
run: msbuild /m /p:Configuration=RelWithDebInfo .\build64\obs-studio.sln
run: |
msbuild /m /p:Configuration=RelWithDebInfo .\build32\obs-studio.sln
msbuild /m /p:Configuration=RelWithDebInfo .\build64\obs-studio.sln
- name: Package
if: success()
run: |
$env:FILE_DATE=(Get-Date -UFormat "%F")
$env:FILE_NAME="${{ env.PLUGIN_NAME }}-${env:FILE_DATE}-${{ github.sha }}-win64.zip"
echo "::set-env name=FILE_NAME::${env:FILE_NAME}"
robocopy .\build64\rundir\RelWithDebInfo\obs-plugins\64bit\ .\build\obs-plugins\64bit ${{ env.LIB_NAME }}.* /E /XF .gitignore
7z a ${env:FILE_NAME} .\build\*
- name: Publish
$env:FILE_NAME="${{ env.PLUGIN_NAME }}-${env:FILE_DATE}-${{ github.sha }}-windows"
echo "FILE_NAME=${env:FILE_NAME}" >> ${env:GITHUB_ENV}
robocopy .\build32\rundir\RelWithDebInfo\obs-plugins\32bit\ .\package\obs-plugins\32bit ${{ env.LIB_NAME }}.* /E /XF .gitignore
robocopy .\build64\rundir\RelWithDebInfo\obs-plugins\64bit\ .\package\obs-plugins\64bit ${{ env.LIB_NAME }}.* /E /XF .gitignore
robocopy .\build64\rundir\RelWithDebInfo\data\obs-plugins\${{ env.LIB_NAME }}\ .\package\data\obs-plugins\${{ env.LIB_NAME }}\ /E /XF .gitignore
exit 0
- name: Publish zip
if: success()
uses: actions/upload-artifact@v2-preview
uses: actions/upload-artifact@v2.2.1
with:
name: '${{ env.FILE_NAME }}'
path: '*-win64.zip'
win32:
name: Windows 32-bit
runs-on: [windows-latest]
env:
QT_VERSION: 5.10.1
CMAKE_GENERATOR: "Visual Studio 16 2019"
CMAKE_SYSTEM_VERSION: "10.0.18363.657"
path: package/*
- name: "Package Installer (Prereqs)"
run: |
curl "-kL" "https://github.com/Xaymar/msvc-redist-helper/releases/download/0.1/msvc-redist-helper-64.exe" "-f" "--retry" "5" "-o" "msvc-redist-helper-64.exe"
curl "-kL" "https://github.com/Xaymar/msvc-redist-helper/releases/download/0.1/msvc-redist-helper-32.exe" "-f" "--retry" "5" "-o" "msvc-redist-helper-32.exe"
curl "-kL" "https://files.jrsoftware.org/is/6/innosetup-6.0.3.exe" "-f" "--retry" "5" "-o" "inno.exe"
.\inno.exe /VERYSILENT /SP- /SUPPRESSMSGBOXES /NORESTART
- name: "Package Installer (Compile)"
run: |
& 'C:\Program Files (x86)\Inno Setup 6\ISCC.exe' /Qp ".\build64\UI\frontend-plugins\${{ env.PLUGIN_NAME }}\CI\windows\setup.iss"
- name: Publish installer
if: success()
uses: actions/upload-artifact@v2.2.0
with:
name: '${{ env.FILE_NAME }}-installer'
path: build64/UI/frontend-plugins/SceneSwitcher/CI/windows/Output/*.exe
release:
needs: [macos64, ubuntu64, windows]
name: 'Create Release'
runs-on: [ubuntu-latest]
steps:
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.0
- name: Checkout obs
uses: actions/checkout@v2
- name: "Checkout plugin"
uses: actions/checkout@v2.3.4
with:
repository: obsproject/obs-studio
submodules: 'recursive'
- name: Checkout plugin
uses: actions/checkout@v2
path: plugin/${{ env.PLUGIN_NAME }}
- name: Download artifacts
uses: actions/download-artifact@v2
with:
path: UI/frontend-plugins/${{ env.PLUGIN_NAME }}
- name: Add plugin to obs cmake
shell: cmd
run: echo add_subdirectory(${{ env.PLUGIN_NAME }}) >> UI/frontend-plugins/CMakeLists.txt
- name: Fetch Git Tags
run: git fetch --prune --tags --unshallow
- name: 'Install prerequisite: QT'
path: artifacts
- name: 'Package'
shell: bash
run: |
curl -kLO https://cdn-fastly.obsproject.com/downloads/Qt_${{ env.QT_VERSION }}.7z -f --retry 5 -C -
7z x Qt_${{ env.QT_VERSION }}.7z -o"${{ github.workspace }}/cmbuild/QT"
- name: 'Install prerequisite: Pre-built dependencies'
run: |
curl -kLO https://cdn-fastly.obsproject.com/downloads/dependencies2017.zip -f --retry 5 -C -
7z x dependencies2017.zip -o"${{ github.workspace }}/cmbuild/deps"
- name: Configure
run: |
mkdir ./build
mkdir ./build32
cd ./build32
cmake -G"${{ env.CMAKE_GENERATOR }}" -A"Win32" -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DBUILD_BROWSER=false -DBUILD_CAPTIONS=false -DCOMPILE_D3D12_HOOK=false -DDepsPath="${{ github.workspace }}/cmbuild/deps/win32" -DQTDIR="${{ github.workspace }}/cmbuild/QT/${{ env.QT_VERSION }}/msvc2017" -DCOPIED_DEPENDENCIES=FALSE -DCOPY_DEPENDENCIES=TRUE ..
- name: Build
run: msbuild /m /p:Configuration=RelWithDebInfo .\build32\obs-studio.sln
- name: Package
if: success()
run: |
$env:FILE_DATE=(Get-Date -UFormat "%F")
$env:FILE_NAME="${{ env.PLUGIN_NAME }}-${env:FILE_DATE}-${{ github.sha }}-win32.zip"
echo "::set-env name=FILE_NAME::${env:FILE_NAME}"
robocopy .\build32\rundir\RelWithDebInfo\obs-plugins\32bit\ .\build\obs-plugins\32bit ${{ env.LIB_NAME }}.* /E /XF .gitignore
7z a ${env:FILE_NAME} .\build\*
mkdir -p ${{ env.PLUGIN_NAME }}/Linux ${{ env.PLUGIN_NAME }}/MacOs ${{ env.PLUGIN_NAME }}/Windows
tar xf artifacts/${{ env.PLUGIN_NAME }}*-linux64.tar.gz/${{ env.PLUGIN_NAME }}*-linux64.tar.gz -C ${{ env.PLUGIN_NAME }}/Linux/
mv artifacts/${{ env.PLUGIN_NAME }}*-macos.pkg/${{ env.PLUGIN_NAME }}*-macos.pkg ${{ env.PLUGIN_NAME }}/MacOs/${{ env.PLUGIN_NAME }}.pkg
mv ./artifacts/${{ env.PLUGIN_NAME }}*-windows/* ${{ env.PLUGIN_NAME }}/Windows/
mv ./artifacts/${{ env.PLUGIN_NAME }}*-windows-installer/*.exe ${{ env.PLUGIN_NAME }}/Windows/
rm ${{ env.PLUGIN_NAME }}/Windows/obs-plugins/32bit/advanced-scene-switcher.pdb
rm ${{ env.PLUGIN_NAME }}/Windows/obs-plugins/64bit/advanced-scene-switcher.pdb
cp plugin/${{ env.PLUGIN_NAME }}/CI/release/README.txt ${{ env.PLUGIN_NAME }}/
FILE_NAME=${{ env.PLUGIN_NAME }}.zip
zip -r ${FILE_NAME} ${{ env.PLUGIN_NAME }}/
- name: Publish
if: success()
uses: actions/upload-artifact@v2-preview
uses: actions/upload-artifact@v2.2.1
with:
name: '${{ env.FILE_NAME }}'
path: '*-win32.zip'
name: "Release"
path: '*.zip'

32
.github/workflows/clang-format.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Clang Format Check
on: [push, pull_request]
jobs:
ubuntu64:
runs-on: ubuntu-latest
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
- name: Check the Formatting
run: |
./CI/formatcode.sh
./CI/check-format.sh

11
CI/check-format.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
dirty=$(git ls-files --modified)
set +x
if [[ $dirty ]]; then
echo "================================="
echo "Files were not formatted properly"
echo "$dirty"
echo "================================="
exit 1
fi

31
CI/formatcode.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Original source https://github.com/Project-OSRM/osrm-backend/blob/master/scripts/format.sh
set +x
set -o errexit
set -o pipefail
set -o nounset
# Runs the Clang Formatter in parallel on the code base.
# Return codes:
# - 1 there are files to be formatted
# - 0 everything looks fine
# Get CPU count
OS=$(uname)
NPROC=1
if [[ $OS = "Linux" || $OS = "Darwin" ]] ; then
NPROC=$(getconf _NPROCESSORS_ONLN)
fi
# Discover clang-format
if 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' \
| xargs -L100 -P${NPROC} ${CLANG_FORMAT} -i -style=file -fallback-style=none

View File

@@ -2,5 +2,4 @@ brew "jack"
brew "speexdsp"
brew "cmake"
brew "freetype"
brew "fdk-aac"
brew "https://gist.githubusercontent.com/DDRBoxman/9c7a2b08933166f4b61ed9a44b242609/raw/ef4de6c587c6bd7f50210eccd5bd51ff08e6de13/qt.rb"
brew "fdk-aac"

View File

@@ -60,6 +60,22 @@
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../data</string>
<key>PATH_TYPE</key>
<integer>1</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>

38
CI/release/README.txt Normal file
View File

@@ -0,0 +1,38 @@
Note that you will have to use OBS version 26.1 or newer!
--- Windows ---
Recommended: Run the provided installer. (You might have to click 'More info' and select 'Run anyway' if it is blocked by Windows)
Alternatively manually copy the 'obs-plugins' and 'data' folders in the respective OBS Studio installation directory.
It is usually located at 'C:\Program Files (x86)\obs-studio\'.
Remember to install the Visual C++ Redistributable for Visual Studio 2019, if you have not done so already. (See plugin overview page)
--- macOS ---
Recommended: Run the provided installer. (You might have to right-click or control-click and select 'open' if it is blocked)
Alternatively extract the *so file and data folder and either ...
... right-click the OBS app inside your Applications folder and choose 'Show Package Contents'.
Copy the advanced-scene-switcher.so file to 'Contents/Plugins' and the 'data' folder to 'Contents/Resources'.
... or copy the advanced-scene-switcher.so file to Library/Application Support/obs-studio/plugins/advanced-scene-switcher/bin/ .
And the 'data' folder to 'Library/Application Support/obs-studio/plugins/advanced-scene-switcher/'.
--- Linux ---
Copy the advanced-scene-switcher.so file and into the OBS Studio plugin folder.
The location of this folder can vary, so you might have to look around a bit.
Examples are ...
/usr/lib/obs-plugins/
/usr/lib/x86_64-linux-gnu/obs-plugins/
/usr/share/obs/obs-plugins/
~/.config/obs-studio/plugins/advanced-scene-switcher/bin/64bit/
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:
~/.config/obs-studio/plugins/advanced-scene-switcher/
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
In doubt please check where other "en-US.ini" files are located on your system.

64
CI/windows/setup.iss.in Normal file
View File

@@ -0,0 +1,64 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "Advanced Scene Switcher"
#define MyAppVersion "@GIT_SHA1@"
#define MyAppURL "https://github.com/WarmUpTill/SceneSwitcher"
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{A4ADDF26-4426-4D2E-B26A-C7C878DA8FC9}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={code:GetDirName}
DefaultGroupName={#MyAppName}
AllowNoIcons=yes
; Uncomment the following line to run in non administrative install mode (install for current user only.)
;PrivilegesRequired=lowest
OutputBaseFilename=AdvancedSceneSwitcherSetup
Compression=lzma
SolidCompression=yes
WizardStyle=modern
ArchitecturesInstallIn64BitMode=x64
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Files]
Source: "@ISS_MSVC_REDIST_HELPER_DIR@\msvc-redist-helper-64.exe"; DestDir: "{app}"; DestName: "msvc-redist-helper.exe"; Flags: ignoreversion dontcopy; Check: Is64BitInstallMode
Source: "@ISS_MSVC_REDIST_HELPER_DIR@\msvc-redist-helper-32.exe"; DestDir: "{app}"; DestName: "msvc-redist-helper.exe"; Flags: ignoreversion dontcopy; Check: not Is64BitInstallMode
Source: "@ISS_PLUGIN_FILES_DIR@\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
[Code]
// credit where it's due:
// following function comes from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45
function GetDirName(Value: string): string;
var
InstallPath: string;
begin
// initialize default path, which will be returned when the following registry
// key queries fail due to missing keys or for some different reason
Result := ExpandConstant('{pf}\obs-studio');
// query the first registry value; if this succeeds, return the obtained value
if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then
Result := InstallPath
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
ResultCode: Integer;
begin
if (CurStep=ssPostInstall) then
begin
ExtractTemporaryFile('msvc-redist-helper.exe');
Exec(ExpandConstant('{tmp}\msvc-redist-helper.exe'), '2019', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
end;

View File

@@ -2,9 +2,28 @@ cmake_minimum_required(VERSION 3.5)
project(advanced-scene-switcher)
# generate version info
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
include(GetGitRevisionDescription)
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/version.cpp" @ONLY)
if(WIN32)
get_filename_component(ISS_PLUGIN_FILES_DIR "${CMAKE_BINARY_DIR}\\..\\package" ABSOLUTE)
file(TO_NATIVE_PATH "${ISS_PLUGIN_FILES_DIR}" ISS_PLUGIN_FILES_DIR)
get_filename_component(ISS_MSVC_REDIST_HELPER_DIR "${CMAKE_PACKAGE_PREFIX}\\.." ABSOLUTE)
file(TO_NATIVE_PATH "${ISS_MSVC_REDIST_HELPER_DIR}" ISS_MSVC_REDIST_HELPER_DIR)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/CI/windows/setup.iss.in" "${CMAKE_CURRENT_BINARY_DIR}/CI/windows/setup.iss" @ONLY)
endif()
if(WIN32)
get_filename_component(ISS_PLUGIN_FILES_DIR "${CMAKE_BINARY_DIR}\\..\\package" ABSOLUTE)
file(TO_NATIVE_PATH "${ISS_PLUGIN_FILES_DIR}" ISS_PLUGIN_FILES_DIR)
get_filename_component(ISS_MSVC_REDIST_HELPER_DIR "${CMAKE_PACKAGE_PREFIX}\\.." ABSOLUTE)
file(TO_NATIVE_PATH "${ISS_MSVC_REDIST_HELPER_DIR}" ISS_MSVC_REDIST_HELPER_DIR)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/CI/windows/setup.iss.in" "${CMAKE_CURRENT_BINARY_DIR}/CI/windows/setup.iss" @ONLY)
endif()
if(BUILD_OUT_OF_TREE)
# out of tree builds
# credits to c3r1c3
set(CMAKE_PREFIX_PATH "${QTDIR}")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/external")
@@ -16,83 +35,113 @@ if(BUILD_OUT_OF_TREE)
find_package(Libcurl REQUIRED)
find_path(LIBOBS_FRONTEND_INCLUDE_DIR HINTS ${LIBOBS_INCLUDE_DIRS})
find_file(LIBOBS_FRONTEND_API_LIB NAMES libobs-frontend-api.* HINTS ${LIBOBS_LIB} )
endif()
if(APPLE)
find_library(COCOA Cocoa)
if(APPLE)
find_library(COCOA Cocoa)
if(BUILD_OUT_OF_TREE)
find_package(Qt5MacExtras REQUIRED)
include_directories(${COCOA})
add_link_options("-v")
endif()
include_directories(${COCOA})
endif()
if(UNIX AND NOT APPLE)
find_package(X11 REQUIRED)
link_libraries(${X11_LIBRARIES})
include_directories(${X11_INCLUDE_DIR})
endif()
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
)
if(UNIX AND NOT APPLE)
find_package(X11 REQUIRED)
link_libraries(${X11_LIBRARIES})
include_directories(${X11_INCLUDE_DIR})
endif()
set(advanced-scene-switcher_SOURCES
${advanced-scene-switcher_SOURCES}
src/advanced-scene-switcher.cpp
src/advanced-scene-switcher-module.c
src/scene-transitions.cpp
src/screen-region-switch.cpp
src/priority.cpp
src/executable-switch.cpp
src/idle-switch.cpp
src/scene-round-trip.cpp
src/file-switch.cpp
src/window-title-switch.cpp
src/media-switch.cpp
src/hotkey.cpp
src/general.cpp
src/pause-switch.cpp
src/random.cpp
src/time-switch.cpp
)
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
src/headers/switch-executable.hpp
src/headers/switch-file.hpp
src/headers/switch-idle.hpp
src/headers/switch-media.hpp
src/headers/switch-pause.hpp
src/headers/switch-random.hpp
src/headers/switch-screen-region.hpp
src/headers/switch-time.hpp
src/headers/switch-transitions.hpp
src/headers/switch-window.hpp
src/headers/switch-sequence.hpp
src/headers/switch-video.hpp
src/headers/switch-generic.hpp
src/headers/version.h
)
set(advanced-scene-switcher_UI
${advanced-scene-switcher_UI}
forms/advanced-scene-switcher.ui
)
set(advanced-scene-switcher_SOURCES
${advanced-scene-switcher_SOURCES}
src/advanced-scene-switcher.cpp
src/advanced-scene-switcher-module.c
src/switcher-data-structs.cpp
src/scene-group.cpp
src/scene-trigger.cpp
src/switch-transitions.cpp
src/switch-screen-region.cpp
src/switch-priority.cpp
src/switch-executable.cpp
src/switch-idle.cpp
src/switch-sequence.cpp
src/switch-file.cpp
src/switch-window.cpp
src/switch-media.cpp
src/hotkey.cpp
src/general.cpp
src/switch-pause.cpp
src/switch-random.cpp
src/switch-time.cpp
src/switch-audio.cpp
src/switch-video.cpp
src/switch-generic.cpp
src/curl-helper.cpp
src/volume-control.cpp
src/version.cpp
)
if(WIN32)
set(advanced-scene-switcher_PLATFORM_SOURCES
src/win/advanced-scene-switcher-win.cpp)
set(advanced-scene-switcher_UI
${advanced-scene-switcher_UI}
forms/advanced-scene-switcher.ui
)
elseif(APPLE)
set(advanced-scene-switcher_PLATFORM_SOURCES
src/osx/advanced-scene-switcher-osx.mm)
set_source_files_properties(advanced-scene-switcher-osx.mm
PROPERTIES COMPILE_FLAGS "-fobjc-arc")
set(advanced-scene-switcher_PLATFORM_LIBS
${COCOA})
if(WIN32)
set(advanced-scene-switcher_PLATFORM_SOURCES
src/win/advanced-scene-switcher-win.cpp)
elseif(APPLE)
set(advanced-scene-switcher_PLATFORM_SOURCES
src/osx/advanced-scene-switcher-osx.mm)
set_source_files_properties(advanced-scene-switcher-osx.mm
PROPERTIES COMPILE_FLAGS "-fobjc-arc")
set(advanced-scene-switcher_PLATFORM_LIBS
${COCOA})
else()
set(advanced-scene-switcher_PLATFORM_SOURCES
src/linux/advanced-scene-switcher-nix.cpp)
set(advanced-scene-switcher_PLATFORM_LIBS
Xss)
endif()
elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")
set(advanced-scene-switcher_PLATFORM_SOURCES
src/linux/advanced-scene-switcher-nix.cpp)
set(advanced-scene-switcher_PLATFORM_LIBS
Xss)
endif()
qt5_wrap_ui(advanced-scene-switcher_UI_HEADERS
${advanced-scene-switcher_UI}
${advanced-scene-switcher_PLATFORM_UI})
qt5_wrap_ui(advanced-scene-switcher_UI_HEADERS
${advanced-scene-switcher_UI}
${advanced-scene-switcher_PLATFORM_UI})
add_library(advanced-scene-switcher MODULE
${advanced-scene-switcher_HEADERS}
${advanced-scene-switcher_SOURCES}
${advanced-scene-switcher_UI_HEADERS}
${advanced-scene-switcher_PLATFORM_SOURCES}
${advanced-scene-switcher_PLATFORM_HEADERS}
)
add_library(advanced-scene-switcher MODULE
${advanced-scene-switcher_HEADERS}
${advanced-scene-switcher_SOURCES}
${advanced-scene-switcher_UI_HEADERS}
${advanced-scene-switcher_PLATFORM_SOURCES}
${advanced-scene-switcher_PLATFORM_HEADERS}
)
if(BUILD_OUT_OF_TREE)
include_directories(
"${LIBOBS_INCLUDE_DIRS}"
"${LIBOBS_FRONTEND_INCLUDE_DIR}"
@@ -105,8 +154,7 @@ if(BUILD_OUT_OF_TREE)
${LIBOBS_LIB}
${LIBOBS_FRONTEND_API_LIB}
Qt5::Core
Qt5::Widgets
${LIBCURL_LIBRARIES})
Qt5::Widgets)
# Additional commands to install the module in the correct place.
# Find all the translation files so we can copy them to the correct
@@ -129,7 +177,6 @@ if(BUILD_OUT_OF_TREE)
# OSX
if(APPLE)
set_target_properties(advanced-scene-switcher PROPERTIES PREFIX "")
endif()
# Linux
@@ -149,86 +196,12 @@ if(BUILD_OUT_OF_TREE)
install(FILES ${ASS_TRANSLATION_FILES}
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/advanced-scene-switcher/locale")
endif()
else ()
# in-tree build
if(APPLE)
find_library(COCOA Cocoa)
include_directories(${COCOA})
endif()
if(UNIX AND NOT APPLE)
find_package(X11 REQUIRED)
link_libraries(${X11_LIBRARIES})
include_directories(${X11_INCLUDE_DIR})
endif()
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
)
set(advanced-scene-switcher_SOURCES
${advanced-scene-switcher_SOURCES}
src/advanced-scene-switcher.cpp
src/advanced-scene-switcher-module.c
src/scene-transitions.cpp
src/screen-region-switch.cpp
src/priority.cpp
src/executable-switch.cpp
src/idle-switch.cpp
src/scene-round-trip.cpp
src/file-switch.cpp
src/window-title-switch.cpp
src/media-switch.cpp
src/hotkey.cpp
src/general.cpp
src/pause-switch.cpp
src/random.cpp
src/time-switch.cpp
)
set(advanced-scene-switcher_UI
${advanced-scene-switcher_UI}
forms/advanced-scene-switcher.ui
)
if(WIN32)
set(advanced-scene-switcher_PLATFORM_SOURCES
src/win/advanced-scene-switcher-win.cpp)
elseif(APPLE)
set(advanced-scene-switcher_PLATFORM_SOURCES
src/osx/advanced-scene-switcher-osx.mm)
set_source_files_properties(advanced-scene-switcher-osx.mm
PROPERTIES COMPILE_FLAGS "-fobjc-arc")
set(advanced-scene-switcher_PLATFORM_LIBS
${COCOA})
else()
set(advanced-scene-switcher_PLATFORM_SOURCES
src/linux/advanced-scene-switcher-nix.cpp)
set(advanced-scene-switcher_PLATFORM_LIBS
Xss)
endif()
qt5_wrap_ui(advanced-scene-switcher_UI_HEADERS
${advanced-scene-switcher_UI}
${advanced-scene-switcher_PLATFORM_UI})
add_library(advanced-scene-switcher MODULE
${advanced-scene-switcher_HEADERS}
${advanced-scene-switcher_SOURCES}
${advanced-scene-switcher_UI_HEADERS}
${advanced-scene-switcher_PLATFORM_SOURCES}
${advanced-scene-switcher_PLATFORM_HEADERS}
)
else()
target_link_libraries(advanced-scene-switcher
${advanced-scene-switcher_PLATFORM_LIBS}
obs-frontend-api
Qt5::Widgets
libobs
${LIBCURL_LIBRARIES})
libobs)
install_obs_plugin(advanced-scene-switcher data)
install_obs_plugin_with_data(advanced-scene-switcher data)
endif()

View File

@@ -3,7 +3,7 @@ An automated scene switcher for OBS Studio
More information can be found on https://obsproject.com/forum/resources/automatic-scene-switching.395/
## Compiling in tree
## Compiling in tree (recommended)
Add the "SceneSwitcher" source directory to your obs-studio source directory under obs-studio/UI/frontend-plugins/:
```
cd obs-studio/UI/frontend-plugins/
@@ -49,9 +49,10 @@ Most versions of Linux you can use cmake-gui or the command line.
**For the command line:**
```
cmake -DBUILD_OUT_OF_TREE=1 -DLIBOBS_INCLUDE_DIR="<path to the libobs/ sub-folder in obs-studio's source code>"
-DLIBOBS_FRONTEND_INCLUDE_DIR="<path to the UI/obs-frontend-api/ sub-folder in obs-studio's source code>"
-DLIBOBS_FRONTEND_API_LIB="< path to the libobs-frontend-api.so file (usually in the same place as LIBOBS_LIB)>"
cmake -DBUILD_OUT_OF_TREE=1 -DLIBOBS_INCLUDE_DIR="<path to the libobs/ sub-folder in obs-studio's source code>" \
-DLIBOBS_FRONTEND_INCLUDE_DIR="<path to the UI/obs-frontend-api/ sub-folder in obs-studio's source code>" \
-DLIBOBS_LIB="<path to the libobs.so file>" \
-DLIBOBS_FRONTEND_API_LIB="<path to the libobs-frontend-api.so file (usually in the same place as LIBOBS_LIB)>" \
-DCMAKE_INSTALL_PREFIX=/usr ..
make -j4
sudo make install

View File

@@ -0,0 +1,279 @@
# - Returns a version string from Git
#
# These functions force a re-configure on each git commit so that you can
# trust the values of the variables in your build system.
#
# get_git_head_revision(<refspecvar> <hashvar> [<additional arguments to git describe> ...])
#
# Returns the refspec and sha hash of the current head revision
#
# git_describe(<var> [<additional arguments to git describe> ...])
#
# Returns the results of git describe on the source tree, and adjusting
# the output so that it tests false if an error occurs.
#
# git_describe_working_tree(<var> [<additional arguments to git describe> ...])
#
# Returns the results of git describe on the working tree (--dirty option),
# and adjusting the output so that it tests false if an error occurs.
#
# git_get_exact_tag(<var> [<additional arguments to git describe> ...])
#
# Returns the results of git describe --exact-match on the source tree,
# and adjusting the output so that it tests false if there was no exact
# matching tag.
#
# git_local_changes(<var>)
#
# Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes.
# Uses the return code of "git diff-index --quiet HEAD --".
# Does not regard untracked files.
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2020 Ryan Pavlik <ryan.pavlik@gmail.com> <abiryan@ryand.net>
# http://academic.cleardefinition.com
#
# Copyright 2009-2013, Iowa State University.
# Copyright 2013-2020, Ryan Pavlik
# Copyright 2013-2020, Contributors
# SPDX-License-Identifier: BSL-1.0
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
if(__get_git_revision_description)
return()
endif()
set(__get_git_revision_description YES)
# We must run the following at "include" time, not at function call time,
# to find the path to this module rather than the path to a calling list file
get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
# Function _git_find_closest_git_dir finds the next closest .git directory
# that is part of any directory in the path defined by _start_dir.
# The result is returned in the parent scope variable whose name is passed
# as variable _git_dir_var. If no .git directory can be found, the
# function returns an empty string via _git_dir_var.
#
# Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and
# neither foo nor bar contain a file/directory .git. This wil return
# C:/bla/.git
#
function(_git_find_closest_git_dir _start_dir _git_dir_var)
set(cur_dir "${_start_dir}")
set(git_dir "${_start_dir}/.git")
while(NOT EXISTS "${git_dir}")
# .git dir not found, search parent directories
set(git_previous_parent "${cur_dir}")
get_filename_component(cur_dir ${cur_dir} DIRECTORY)
if(cur_dir STREQUAL git_previous_parent)
# We have reached the root directory, we are not in git
set(${_git_dir_var}
""
PARENT_SCOPE)
return()
endif()
set(git_dir "${cur_dir}/.git")
endwhile()
set(${_git_dir_var}
"${git_dir}"
PARENT_SCOPE)
endfunction()
function(get_git_head_revision _refspecvar _hashvar)
_git_find_closest_git_dir("${CMAKE_CURRENT_SOURCE_DIR}" GIT_DIR)
if(NOT "${GIT_DIR}" STREQUAL "")
file(RELATIVE_PATH _relative_to_source_dir "${CMAKE_SOURCE_DIR}"
"${GIT_DIR}")
if("${_relative_to_source_dir}" MATCHES "[.][.]")
# We've gone above the CMake root dir.
set(GIT_DIR "")
endif()
endif()
if("${GIT_DIR}" STREQUAL "")
set(${_refspecvar}
"GITDIR-NOTFOUND"
PARENT_SCOPE)
set(${_hashvar}
"GITDIR-NOTFOUND"
PARENT_SCOPE)
return()
endif()
# Check if the current source dir is a git submodule or a worktree.
# In both cases .git is a file instead of a directory.
#
if(NOT IS_DIRECTORY ${GIT_DIR})
# The following git command will return a non empty string that
# points to the super project working tree if the current
# source dir is inside a git submodule.
# Otherwise the command will return an empty string.
#
execute_process(
COMMAND "${GIT_EXECUTABLE}" rev-parse
--show-superproject-working-tree
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
OUTPUT_VARIABLE out
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT "${out}" STREQUAL "")
# If out is empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule
file(READ ${GIT_DIR} submodule)
string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE
${submodule})
string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE)
get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE}
ABSOLUTE)
set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD")
else()
# GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree
file(READ ${GIT_DIR} worktree_ref)
# The .git directory contains a path to the worktree information directory
# inside the parent git repo of the worktree.
#
string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir
${worktree_ref})
string(STRIP ${git_worktree_dir} git_worktree_dir)
_git_find_closest_git_dir("${git_worktree_dir}" GIT_DIR)
set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD")
endif()
else()
set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD")
endif()
set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
if(NOT EXISTS "${GIT_DATA}")
file(MAKE_DIRECTORY "${GIT_DATA}")
endif()
if(NOT EXISTS "${HEAD_SOURCE_FILE}")
return()
endif()
set(HEAD_FILE "${GIT_DATA}/HEAD")
configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY)
configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in"
"${GIT_DATA}/grabRef.cmake" @ONLY)
include("${GIT_DATA}/grabRef.cmake")
set(${_refspecvar}
"${HEAD_REF}"
PARENT_SCOPE)
set(${_hashvar}
"${HEAD_HASH}"
PARENT_SCOPE)
endfunction()
function(git_describe _var)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
get_git_head_revision(refspec hash)
if(NOT GIT_FOUND)
set(${_var}
"GIT-NOTFOUND"
PARENT_SCOPE)
return()
endif()
if(NOT hash)
set(${_var}
"HEAD-HASH-NOTFOUND"
PARENT_SCOPE)
return()
endif()
# TODO sanitize
#if((${ARGN}" MATCHES "&&") OR
# (ARGN MATCHES "||") OR
# (ARGN MATCHES "\\;"))
# message("Please report the following error to the project!")
# message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}")
#endif()
#message(STATUS "Arguments to execute_process: ${ARGN}")
execute_process(
COMMAND "${GIT_EXECUTABLE}" describe --tags --always ${hash} ${ARGN}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE res
OUTPUT_VARIABLE out
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT res EQUAL 0)
set(out "${out}-${res}-NOTFOUND")
endif()
set(${_var}
"${out}"
PARENT_SCOPE)
endfunction()
function(git_describe_working_tree _var)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
if(NOT GIT_FOUND)
set(${_var}
"GIT-NOTFOUND"
PARENT_SCOPE)
return()
endif()
execute_process(
COMMAND "${GIT_EXECUTABLE}" describe --dirty ${ARGN}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE res
OUTPUT_VARIABLE out
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT res EQUAL 0)
set(out "${out}-${res}-NOTFOUND")
endif()
set(${_var}
"${out}"
PARENT_SCOPE)
endfunction()
function(git_get_exact_tag _var)
git_describe(out --exact-match ${ARGN})
set(${_var}
"${out}"
PARENT_SCOPE)
endfunction()
function(git_local_changes _var)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
get_git_head_revision(refspec hash)
if(NOT GIT_FOUND)
set(${_var}
"GIT-NOTFOUND"
PARENT_SCOPE)
return()
endif()
if(NOT hash)
set(${_var}
"HEAD-HASH-NOTFOUND"
PARENT_SCOPE)
return()
endif()
execute_process(
COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD --
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
RESULT_VARIABLE res
OUTPUT_VARIABLE out
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
if(res EQUAL 0)
set(${_var}
"CLEAN"
PARENT_SCOPE)
else()
set(${_var}
"DIRTY"
PARENT_SCOPE)
endif()
endfunction()

View File

@@ -0,0 +1,41 @@
#
# Internal file for GetGitRevisionDescription.cmake
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
set(HEAD_HASH)
file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
if(HEAD_CONTENTS MATCHES "ref")
# named branch
string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
if(EXISTS "@GIT_DIR@/${HEAD_REF}")
configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
else()
configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY)
file(READ "@GIT_DATA@/packed-refs" PACKED_REFS)
if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}")
set(HEAD_HASH "${CMAKE_MATCH_1}")
endif()
endif()
else()
# detached HEAD
configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
endif()
if(NOT HEAD_HASH)
file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
string(STRIP "${HEAD_HASH}" HEAD_HASH)
endif()

3
cmake/version.cpp.in Normal file
View File

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

288
data/locale/de-DE.ini Normal file
View File

@@ -0,0 +1,288 @@
AdvSceneSwitcher.pluginName="Erweiterter Automatischer Szenenwechsler"
AdvSceneSwitcher.windowTitle="Erweiterter Automatischer Szenenwechsler"
; General Tab
AdvSceneSwitcher.generalTab.title="Allgemein"
AdvSceneSwitcher.generalTab.status="Status"
AdvSceneSwitcher.generalTab.status.hotkeytips="Hotkeys können in den OBS Einstellungen konfiguriert werden"
AdvSceneSwitcher.generalTab.status.currentStatus="Erweiterter Automatischer Szenenwechsler ist:"
AdvSceneSwitcher.generalTab.status.onStartup="Beim Start von OBS:"
AdvSceneSwitcher.generalTab.status.onStartup.asLastRun="Aktiviere den Szenenwechsler wenn er aktiv war"
AdvSceneSwitcher.generalTab.status.onStartup.alwaysStart="Aktiviere den Szenenwechsler"
AdvSceneSwitcher.generalTab.status.onStartup.doNotStart="Aktiviere den Szenenwechsler nicht"
AdvSceneSwitcher.generalTab.status.start="Start"
AdvSceneSwitcher.generalTab.status.stop="Stop"
AdvSceneSwitcher.generalTab.status.autoStart="Starte auotmatischen den Szenenwechsler beim:"
AdvSceneSwitcher.generalTab.status.autoStart.never="Niemals"
AdvSceneSwitcher.generalTab.status.autoStart.recording="Aufnehmen"
AdvSceneSwitcher.generalTab.status.autoStart.streaming="Streamen"
AdvSceneSwitcher.generalTab.status.autoStart.recordingAndStreaming="Aufnehmen oder Streamen"
AdvSceneSwitcher.generalTab.status.checkInterval="Teste Bedingungen alle"
AdvSceneSwitcher.generalTab.generalBehavior="Allgemeines Verhalten"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet="Wenn keine Bedingung erfüllt ist für "
AdvSceneSwitcher.generalTab.generalBehavior.onNoMetDelayTooltip="Kann nur so genau sein wie das eingestellte Interval zum Testen der Bedingungen."
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch="Nicht wechseln"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom="Wechsle zu einer Szene auf dem Zufall-Tab"
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.disableUIHints="Deaktiviere UI Tipps"
AdvSceneSwitcher.generalTab.priority="Priorität"
AdvSceneSwitcher.generalTab.priority.description="Szenenwechselmethoden sortiert nach Priorität"
AdvSceneSwitcher.generalTab.priority.threadPriority="Benutze Thread Priorität"
AdvSceneSwitcher.generalTab.priority.threadPriorityNotice="(Erhöhen der Priorität über \"Normal\" ist nicht empfohlen)"
AdvSceneSwitcher.generalTab.saveOrLoadsettings="Speichern / Laden der Einstellungen"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.export="Exportieren"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.import="Importieren"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.exportWindowTitle="Exportiere Erweiterter Automatischer Szenenwechsler Einstellungen zu Datei ..."
AdvSceneSwitcher.generalTab.saveOrLoadsettings.importWindowTitle="Exportiere Erweiterter Automatischer Szenenwechsler Einstellungen von Datei ..."
AdvSceneSwitcher.generalTab.saveOrLoadsettings.textType="Text Dateien (*.txt)"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.loadFail="Importieren der Einstellungen ist fehlgeschlagen"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.loadSuccess="Einstellungen wurden erfolgreich importiert"
AdvSceneSwitcher.generalTab.priority.fileContent="Datei Inhalt"
AdvSceneSwitcher.generalTab.priority.sceneSequence="Szenenfolge"
AdvSceneSwitcher.generalTab.priority.idleDetection="Inaktivitätserkennung"
AdvSceneSwitcher.generalTab.priority.executable="Prozess"
AdvSceneSwitcher.generalTab.priority.screenRegion="Bildschrimbereich"
AdvSceneSwitcher.generalTab.priority.windowTitle="Fenstername"
AdvSceneSwitcher.generalTab.priority.media="Medien"
AdvSceneSwitcher.generalTab.priority.time="Zeit"
AdvSceneSwitcher.generalTab.priority.audio="Audio"
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.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.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."
; Pause Scenes Tab
AdvSceneSwitcher.pauseTab.title="Pause"
AdvSceneSwitcher.pauseTab.pauseOnScene="Pausiere den Szenenwechsler auf einer Szene"
AdvSceneSwitcher.pauseTab.pauseInFocus1="Pausiere den Szenenwechsler wenn "
AdvSceneSwitcher.pauseTab.pauseInFocus2="im Fokus ist"
AdvSceneSwitcher.pauseTab.pauseTypeScene="Szene aktiv ist"
AdvSceneSwitcher.pauseTab.pauseTypeWindow="Fenster im Fokus ist"
AdvSceneSwitcher.pauseTab.pauseTargetAll="alle"
AdvSceneSwitcher.pauseTab.pauseEntry="Pausiere {{pauseTargets}} Überprüfungen wenn {{scenes}} {{windows}} {{pauseTypes}}"
AdvSceneSwitcher.pauseTab.help="Auf diesem Tab können einzelne Szenenwechselmethoden pausiert werden, wenn eine Szene aktiv oder ein Fenster im Fokus ist.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
; Window Title Tab
AdvSceneSwitcher.windowTitleTab.title="Fenstername"
AdvSceneSwitcher.windowTitleTab.regexrDescription="<html><head/><body><p>Gib entweder den gesamten Fensternamen ein oder eine gültigen regulären Ausdruck. Die Syntax für regulärer Ausdrücke (regex) kann hier überprüft werden: <a href=\"https://regexr.com\"><span style=\" text-decoration: underline; color:#268bd2;\">RegExr</span></a></p></body></html>"
AdvSceneSwitcher.windowTitleTab.stayInFocus1="Ignoriere diesen Fensternamen"
AdvSceneSwitcher.windowTitleTab.stayInFocus2=" "
AdvSceneSwitcher.windowTitleTab.fullscreen="wenn Vollbild"
AdvSceneSwitcher.windowTitleTab.maximized="wenn maximiert"
AdvSceneSwitcher.windowTitleTab.focused="wenn fokussiert"
AdvSceneSwitcher.windowTitleTab.entry="{{windows}} {{scenes}} {{transitions}} {{fullscreen}} {{maximized}} {{focused}}"
AdvSceneSwitcher.windowTitleTab.windowsHelp="Wechsle Szenen basiert auf dem Fensternamen eines laufenden Prozesses.\nFolgende zusätzliche Bedingungen können ausgewählt werden:\nDas Fenster ist im Vollbildmodus\nDas Fenster ist maximiert\nDas Fenster ist fokussiert\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
AdvSceneSwitcher.windowTitleTab.ignoreWindowsHelp="Ist ein Fenstername ignoriert so verhält sich der Szenenwechsler als ob das vorhergehende Fenster immer noch fokussiert ist.\nDies kann in Situationen nützlich sein, in den oft zwischen Fenster gewechselt wird, dies aber nicht immer einen Szenenwechsel auslösen soll.\n\nWähle oben ein Fenster aus oder gib einen Fensternamen ein und klicke auf das Plus Symbol um es zur Liste hinzuzufügen."
; Executable Tab
AdvSceneSwitcher.executableTab.title="Prozess"
AdvSceneSwitcher.executableTab.implemented="Implementiert von dasOven"
AdvSceneSwitcher.executableTab.requiresFocus="nur wenn dieser fokussiert ist"
AdvSceneSwitcher.executableTab.entry="Wenn {{processes}} läuft wechsle zu {{scenes}} mit {{transitions}} {{requiresFocus}}"
AdvSceneSwitcher.executableTab.help="Dieser Tab erlaubt es Szenen zu wechseln, wenn bestimmte Prozesse laufen.\nDas kann nützlich sein, wenn sich der Name eines Fensters ändern kann oder dieser nicht bekannt ist.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
; Screen Region Tab
AdvSceneSwitcher.screenRegionTab.title="Bildschrimbereich"
AdvSceneSwitcher.screenRegionTab.currentPosition="Mauszeiger ist bei:"
AdvSceneSwitcher.screenRegionTab.showGuideFrames="Hilfsrahmen einblenden"
AdvSceneSwitcher.screenRegionTab.hideGuideFrames="Hilfsrahmen ausblenden"
AdvSceneSwitcher.screenRegionTab.excludeScenes.None="Keine Auswahl"
AdvSceneSwitcher.screenRegionTab.entry="Wenn der Mauszeiger in {{minX}} {{minY}} x {{maxX}} {{maxY}} ist wechsle zu {{scenes}} mit {{transitions}} außer wenn auf {{excludeScenes}}"
AdvSceneSwitcher.screenRegionTab.help="Dieser Tab ermöglicht es basierend auf der Position des Mauszeigers Szenen zu wechseln.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
; Media Tab
AdvSceneSwitcher.mediaTab.title="Medien"
AdvSceneSwitcher.mediaTab.implemented="Implementiert von Exeldro"
AdvSceneSwitcher.mediaTab.states.none="Keine Auswahl"
AdvSceneSwitcher.mediaTab.states.playing="Abspielen"
AdvSceneSwitcher.mediaTab.states.opening="Öffnen"
AdvSceneSwitcher.mediaTab.states.buffering="Buffern"
AdvSceneSwitcher.mediaTab.states.Paused="Pausiert"
AdvSceneSwitcher.mediaTab.states.stopped="Gestoppt"
AdvSceneSwitcher.mediaTab.states.ended="Beendet"
AdvSceneSwitcher.mediaTab.states.error="Fehler"
AdvSceneSwitcher.mediaTab.states.playedToEnd="Zu Ende gespielt"
AdvSceneSwitcher.mediaTab.states.any="Beliebig"
AdvSceneSwitcher.mediaTab.timeRestriction.none="Keine Auswahl"
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="Dauer kürzer"
AdvSceneSwitcher.mediaTab.timeRestriction.longer="Dauer länger"
AdvSceneSwitcher.mediaTab.timeRestriction.remainShorter="übrige Zeit kürzer"
AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger="übrige Zeit länger"
AdvSceneSwitcher.mediaTab.entry="Wenn {{mediaSources}} im Status {{states}} ist und{{timeRestrictions}} {{time}} ist wechsle zu {{scenes}} mit {{transitions}}"
AdvSceneSwitcher.mediaTab.help="Dieser Tab ermöglicht es basieren auf dem Status von Medienquellen Szenen zu wechseln.\nZum Beispiel kann automatisch zur vorhergehenden Szene gewechselt werden, sobald sich eine Medienquelle im Status Beendet befindet.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
; File Tab
AdvSceneSwitcher.fileTab.title="Datei Inhalt"
AdvSceneSwitcher.fileTab.readWriteSceneFile="Lese / schreibe Szene von / zu Datei"
AdvSceneSwitcher.fileTab.currentSceneOutputFile="Schreibe den Namen der aktiven Szene in folgende Datei:"
AdvSceneSwitcher.fileTab.switchSceneBaseOnFile="Aktiviere Lesen der Szene aus Datei"
AdvSceneSwitcher.fileTab.switchSceneNameInputFile="Lies den Namen der Scene zu der gewechselt werden soll von folgender Datei:"
AdvSceneSwitcher.fileTab.switchSceneBaseOnFileContent="Wechsle Scene basierend auf Datei Inhalt"
AdvSceneSwitcher.fileTab.remoteFileWarning="Vorsicht: Der Szenenwechsler wird versuchen den angegebenen Pfad alle x ms zu öffnen!"
AdvSceneSwitcher.fileTab.remoteFileWarning1="Vorsicht: Der Szenenwechsler öffnet den angegebenen Pfad alle "
AdvSceneSwitcher.fileTab.remoteFileWarning2="ms"
AdvSceneSwitcher.fileTab.libcurlWarning="Laden von libcurl fehlgeschlagen! Erreichen von remote Dateien wird nicht möglich sein!"
AdvSceneSwitcher.fileTab.selectWrite=Datei auswählen in die geschrieben werden soll ..."
AdvSceneSwitcher.fileTab.selectRead="Datei auswählen die gelesen werden soll ..."
AdvSceneSwitcher.fileTab.textFileType="Text Dateien (*.txt)"
AdvSceneSwitcher.fileTab.anyFileType="Beliebige Dateien (*.*)"
AdvSceneSwitcher.fileTab.remote="remote Datei"
AdvSceneSwitcher.fileTab.local="lokale Datei"
AdvSceneSwitcher.fileTab.useRegExp="verwende reguläre Ausdrücke"
AdvSceneSwitcher.fileTab.checkfileContentTime="nur wenn sich das Änderungsdatum geändert"
AdvSceneSwitcher.fileTab.checkfileContent="nur wenn sich der Inhalt geänder hat"
AdvSceneSwitcher.fileTab.entry="Wechsle zu {{scenes}} mit {{transitions}} wenn der Inhalt von {{fileType}} {{filePath}} {{browseButton}} passt zu:"
AdvSceneSwitcher.fileTab.entry2="{{matchText}}"
AdvSceneSwitcher.fileTab.entry3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.fileTab.help="Dieser Tab erlaubt es Szenen zu wechseln basierend auf dem Inhalt von lokalen oder remote Dateien.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
; Random Tab
AdvSceneSwitcher.randomTab.title="Zufall"
AdvSceneSwitcher.randomTab.randomDisabledWarning="Funktionalität deaktiviert - Um diese zu aktivieren wähle \"Wenn keine Bedingung erfüllt ist wechsle zu einer Szene auf dem Zufall-Tab\" auf dem Allgemein-Tab aus"
AdvSceneSwitcher.randomTab.entry="Wenn keine Bedingung erfüllt ist wechsle zu {{scenes}} mit {{transitions}} für {{delay}}"
AdvSceneSwitcher.randomTab.help="Der Szenenwechsler wählt zufällig einen Eintrag auf diesem Tab aus, zu dem er für die angegebene Zeit wechseln wird.\nDer gleiche Eintrag wird nicht zweimal nacheinander ausgewählt werden.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
; Time Tab
AdvSceneSwitcher.timeTab.title="Zeit"
AdvSceneSwitcher.timeTab.anyDay="An allen Tagen"
AdvSceneSwitcher.timeTab.mondays="Montags"
AdvSceneSwitcher.timeTab.tuesdays="Dienstags"
AdvSceneSwitcher.timeTab.wednesdays="Mittwochs"
AdvSceneSwitcher.timeTab.thursdays="Donnerstags"
AdvSceneSwitcher.timeTab.fridays="Freitags"
AdvSceneSwitcher.timeTab.saturdays="Samstags"
AdvSceneSwitcher.timeTab.sundays="Sonntags"
AdvSceneSwitcher.timeTab.afterstart="Nach Streamen-/Aufnehmenstart"
AdvSceneSwitcher.timeTab.afterstart.tip="Hier wird die Zeit relativ zum Starten des Streamen/Aufnehmen gemessen"
AdvSceneSwitcher.timeTab.entry="{{triggers}} um {{time}} wechsle zu {{scenes}} mit {{transitions}}"
AdvSceneSwitcher.timeTab.help="Dieser Tab ermöglicht es Szenen basierend auf der aktuellen Uhrzeit zu wechseln.\n\nDer Szenenwechsler versucht den Szenenwechsel nur zur eingestellten Uhrzeit auszulösen.\nBeachte, dass die Prioritätseinstellungen auf dem Allgemein Tab entsprechend konfiguriert sind, sodass keine andere Szenenwechselmethode hier konfiguriert Einstellungen zur gegebenen Uhrzeit übertrifft.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
; Idle Tab
AdvSceneSwitcher.idleTab.title="Inaktivitätserkennung"
AdvSceneSwitcher.idleTab.enable="Aktiviere Inaktivitätserkennung"
AdvSceneSwitcher.idleTab.idleswitch="After {{duration}} ohne Keyboard- oder Mauseingaben wechsle zu {{scenes}} mit {{transitions}}"
AdvSceneSwitcher.idleTab.dontSwitchIfFocus1="Wechsle nicht wenn"
AdvSceneSwitcher.idleTab.dontSwitchIfFocus2="im Fokus ist"
; Scene Sequence Tab
AdvSceneSwitcher.sceneSequenceTab.title="Szenenfolge"
AdvSceneSwitcher.sceneSequenceTab.description="Eine Szenenfolge kann durch Pausieren, Stoppen des Szenenwechslers oder manuelle Szenenwechsel abgebrochen werden"
AdvSceneSwitcher.sceneSequenceTab.save="Speichere Szenenfolgen in eine Datei"
AdvSceneSwitcher.sceneSequenceTab.load="Lade Szenenfolgen von einer Datei"
AdvSceneSwitcher.sceneSequenceTab.saveTitle="Speichere Szenenfolgen in eine Datei ..."
AdvSceneSwitcher.sceneSequenceTab.loadTitle="Wähle eine Datei aus von der Szenenfolgen gelesen werden sollen ..."
AdvSceneSwitcher.sceneSequenceTab.loadFail="Laden der Einstellungen ist fehlgeschlagen!"
AdvSceneSwitcher.sceneSequenceTab.loadSuccess="Laden der Einstellungen war erfolgreich!"
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.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.multiMatchfallbackCondition="Wenn mehrere Einträge zutreffen ..."
AdvSceneSwitcher.audioTab.multiMatchfallback="... für {{duration}} 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
AdvSceneSwitcher.videoTab.title="Video"
AdvSceneSwitcher.videoTab.getScreenshot="Screenshot für ausgewählten Eintrag erstellen"
AdvSceneSwitcher.videoTab.getScreenshotHelp="Erstellt einen Screenshot für die Video Quelle des ausgewählten Eintrag und setzt diesen als Ziel Bild"
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.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>"
; Scene Group Tab
AdvSceneSwitcher.sceneGroupTab.title="Szenengruppe"
AdvSceneSwitcher.sceneGroupTab.list="Szenengruppen"
AdvSceneSwitcher.sceneGroupTab.edit="Szenengruppe bearbeiten"
AdvSceneSwitcher.sceneGroupTab.edit.name="Name:"
AdvSceneSwitcher.sceneGroupTab.edit.type="Typ: {{type}}"
AdvSceneSwitcher.sceneGroupTab.type.count="Anzahl"
AdvSceneSwitcher.sceneGroupTab.type.time="Zeit"
AdvSceneSwitcher.sceneGroupTab.type.random="Zufall"
AdvSceneSwitcher.sceneGroupTab.edit.count="Schreite zur nächsten Szene in dieser Gruppe fort nach {{count}} Szenewechseln"
AdvSceneSwitcher.sceneGroupTab.edit.time="Schreite zur nächsten Szene in dieser Gruppe fort nach {{time}}"
AdvSceneSwitcher.sceneGroupTab.edit.random="Wähle eine zufällig Szene in der Liste aus"
AdvSceneSwitcher.sceneGroupTab.edit.repeat="Beginne von vorne wenn Ende der Szenenliste erreicht ist"
AdvSceneSwitcher.sceneGroupTab.edit.addScene="Szene hinzufügen"
AdvSceneSwitcher.sceneGroupTab.add="Szenengruppe hinzufügen"
AdvSceneSwitcher.sceneGroupTab.defaultname="Szenengruppe %1"
AdvSceneSwitcher.sceneGroupTab.exists="Szenengruppen- oder Szenenname existiert bereits"
AdvSceneSwitcher.sceneGroupTab.help="Szenengruppen können, genau wie reguläre Szenen, als Ziel eines Szenenwechsler Eintrags ausgewählt werden.\n\nSzenengruppen bestehen aus einer Liste von Szenen.\nDie aktive Szene der Szenengruppe schreitet abhängig von den konfigurierten Einstellungen durch die Liste der zugewiesenen Szenen fort.\n\nMögliche Einstellungen zum Fortschreiten der aktiven Szene sind:\nAnzahl der Szenenwechsel.\nZeit seit dem letzten Szenenwechsel in der Szenengruppe.\nOder zufällig.\n\nSo kann zum Beispiel eine Szenengruppe welche die folgenden Szenen enthält ...\nSzene 1\nSzene 2\nSzene 3 \n... beim erstem Mal zu \"Szene 1\" wechseln.\nBeim zweiten Mal zu \"Szene 2\".\nDie restlichem Male wechselt die Szenengruppe zu \"Szene 3\".\n\nKlicke auf das markierte Plus Symbol, um eine neuen Szenengruppe hinzuzufügen."
AdvSceneSwitcher.sceneGroupTab.scenes.help="Wähle die Szenengruppe, die du bearbeiten möchtest, auf der linken Seite aus.\n\nWähle oben eine Szene aus, die du zur Szenengruppe hinzufügen möchtest, und klicke das Plus Symbol.\n\nDie gleiche Szene kann mehrfach in derselben Szenengruppe auftauchen."
; Scene Trigger Tab
AdvSceneSwitcher.sceneTriggerTab.title="Szenen Trigger"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none="--Trigger auswählen--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive="aktiv ist"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneInactive="nicht aktiv ist"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneLeave="verlassen wird"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none="--Aktion auswählen--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording="Aufnahme starten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.pauseRecording="Aufnahme pausieren"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unpauseRecording="Aufnahme fortsetzen"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopRecording="Aufnahme stoppen"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming="Streamen stoppen"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="Streamen starten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="Replay Buffer starten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="Replay Buffer stoppen"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="Audio Quelle stumm schalten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="Audio Quelle aktiv schalten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="Szenenwechsler starten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="Szenenwechsler stoppen"
AdvSceneSwitcher.sceneTriggerTab.entry="Wenn {{scenes}} {{triggers}} {{actions}} {{audioSources}} nach {{duration}} "
AdvSceneSwitcher.sceneTriggerTab.help="Dieser Tab ermöglicht es automatisiert Handlungen, wie etwa das Stoppen von einer Aufnahme oder des Streamens, bei einem Szenenwechsel auszuführen."
; Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Starte den Erweiteren Szenenwechsler"
AdvSceneSwitcher.hotkey.stopSwitcherHotkey="Stoppe den Erweiteren Szenenwechsler"
AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="Starte/Stoppe den Erweiteren Szenenwechsler"
AdvSceneSwitcher.askBackup="Neue Version des Erweiterten Automatischen Szenenwechslers wurde erkannt.\nSoll ein Backup der alten Einstellungen angelegt werden?"
AdvSceneSwitcher.close="Schließen"
AdvSceneSwitcher.browse="Öffnen"
AdvSceneSwitcher.selectScene="--Szene auswählen--"
AdvSceneSwitcher.selectPreviousScene="Vorhergehende Szene"
AdvSceneSwitcher.currentTransition="Aktueller Szenenübergang"
AdvSceneSwitcher.selectTransition="--Szenenübergang auswählen--"
AdvSceneSwitcher.selectWindow="--Fenster auswählen--"
AdvSceneSwitcher.selectAudioSource="--Audio Quelle auswählen--"
AdvSceneSwitcher.selectVideoSource="--Video Quelle auswählen--"
AdvSceneSwitcher.selectMediaSource="--Medien Quelle auswählen--"
AdvSceneSwitcher.selectProcess="--Prozess auswählen--"
AdvSceneSwitcher.enterPath="--Pfad eingeben--"
AdvSceneSwitcher.enterText="--Text eingeben--"
AdvSceneSwitcher.invaildEntriesWillNotBeSaved="Ungültige Einträge werden nicht gespeichert"
AdvSceneSwitcher.selectWindowTip="Benutze \"OBS\" um das OBS-Studio Fenster auszuwählen\nBenutze\"Task Switching\"um die ALT + TAB Tastenkombination auszuwählen"
AdvSceneSwitcher.status.active="Aktiv"
AdvSceneSwitcher.status.inactive="Inaktiv"
AdvSceneSwitcher.unit.milliseconds="Millisekunden"
AdvSceneSwitcher.unit.secends="Sekunden"
AdvSceneSwitcher.unit.minutes="Minuten"
AdvSceneSwitcher.unit.hours="Stunden"

287
data/locale/en-US.ini Normal file
View File

@@ -0,0 +1,287 @@
AdvSceneSwitcher.pluginName="Advanced Scene Switcher"
AdvSceneSwitcher.windowTitle="Advanced Scene Switcher"
; General Tab
AdvSceneSwitcher.generalTab.title="General"
AdvSceneSwitcher.generalTab.status="Status"
AdvSceneSwitcher.generalTab.status.hotkeytips="Hotkeys can be defined in the OBS settings"
AdvSceneSwitcher.generalTab.status.currentStatus="Advanced Scene Switcher is:"
AdvSceneSwitcher.generalTab.status.onStartup="On startup of OBS:"
AdvSceneSwitcher.generalTab.status.onStartup.asLastRun="Start the scene switcher if it was running"
AdvSceneSwitcher.generalTab.status.onStartup.alwaysStart="Always start the scene switcher"
AdvSceneSwitcher.generalTab.status.onStartup.doNotStart="Do not start the scene switcher"
AdvSceneSwitcher.generalTab.status.start="Start"
AdvSceneSwitcher.generalTab.status.stop="Stop"
AdvSceneSwitcher.generalTab.status.autoStart="Automatically start the scene switcher when:"
AdvSceneSwitcher.generalTab.status.autoStart.never="Never"
AdvSceneSwitcher.generalTab.status.autoStart.recording="Recording"
AdvSceneSwitcher.generalTab.status.autoStart.streaming="Streaming"
AdvSceneSwitcher.generalTab.status.autoStart.recordingAndStreaming="Recording or Streaming"
AdvSceneSwitcher.generalTab.status.checkInterval="Check switch conditions every"
AdvSceneSwitcher.generalTab.generalBehavior="General behavior"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet="If no switch condition is met for"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMetDelayTooltip="Will only ever be as accurate as the configured check interval."
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch="Don't switch"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom="Switch to any scene in Random tab"
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.disableUIHints="Disable UI hints"
AdvSceneSwitcher.generalTab.priority="Priority"
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
AdvSceneSwitcher.generalTab.priority.threadPriority="Use thread priority"
AdvSceneSwitcher.generalTab.priority.threadPriorityNotice="(Raising the priority above \"Normal\" is not recommended)"
AdvSceneSwitcher.generalTab.saveOrLoadsettings="Save / load settings"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.export="Export"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.import="Import"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.exportWindowTitle="Export Advanced Scene Switcher settings to file ..."
AdvSceneSwitcher.generalTab.saveOrLoadsettings.importWindowTitle="Import Advanced Scene Switcher settings from file ..."
AdvSceneSwitcher.generalTab.saveOrLoadsettings.textType="Text files (*.txt)"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.loadFail="Advanced Scene Switcher failed to import settings"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.loadSuccess="Advanced Scene Switcher settings imported successfully"
AdvSceneSwitcher.generalTab.priority.fileContent="File Content"
AdvSceneSwitcher.generalTab.priority.sceneSequence="Scene Sequence"
AdvSceneSwitcher.generalTab.priority.idleDetection="Idle Detection"
AdvSceneSwitcher.generalTab.priority.executable="Executable"
AdvSceneSwitcher.generalTab.priority.screenRegion="Screen Region"
AdvSceneSwitcher.generalTab.priority.windowTitle="Window Title"
AdvSceneSwitcher.generalTab.priority.media="Media"
AdvSceneSwitcher.generalTab.priority.time="Time"
AdvSceneSwitcher.generalTab.priority.audio="Audio"
AdvSceneSwitcher.generalTab.priority.video="Video"
; 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.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.defaultTransitionEntry="When scene {{scenes}} is active change default scene transition to {{transitions}}"
AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="Click on the plus symbol to add an entry."
; Pause Scenes Tab
AdvSceneSwitcher.pauseTab.title="Pause"
AdvSceneSwitcher.pauseTab.pauseOnScene="Pause the Scene Switcher on scene"
AdvSceneSwitcher.pauseTab.pauseInFocus1="Pause the Scene Switcher when "
AdvSceneSwitcher.pauseTab.pauseInFocus2="is in focus"
AdvSceneSwitcher.pauseTab.pauseTypeScene="scene is active"
AdvSceneSwitcher.pauseTab.pauseTypeWindow="window is in focus"
AdvSceneSwitcher.pauseTab.pauseTargetAll="all"
AdvSceneSwitcher.pauseTab.pauseEntry="Pause {{pauseTargets}} checks when {{pauseTypes}} {{scenes}} {{windows}}"
AdvSceneSwitcher.pauseTab.help="On this tab you can configure to pause individual switching methods if a scene is active or window is in focus.\n\nClick on the highlighted plus symbol to continue."
; Window Title Tab
AdvSceneSwitcher.windowTitleTab.title="Title"
AdvSceneSwitcher.windowTitleTab.regexrDescription="<html><head/><body><p>Enter either direct window titles or valid regex. You can check syntax and matches for regular expressions using <a href=\"https://regexr.com\"><span style=\" text-decoration: underline; color:#268bd2;\">RegExr</span></a></p></body></html>"
AdvSceneSwitcher.windowTitleTab.stayInFocus1="Ignore this window name"
AdvSceneSwitcher.windowTitleTab.stayInFocus2=" "
AdvSceneSwitcher.windowTitleTab.fullscreen="if fullscreen"
AdvSceneSwitcher.windowTitleTab.maximized="if maximized"
AdvSceneSwitcher.windowTitleTab.focused="if focused"
AdvSceneSwitcher.windowTitleTab.entry="{{windows}} {{scenes}} {{transitions}} {{fullscreen}} {{maximized}} {{focused}}"
AdvSceneSwitcher.windowTitleTab.windowsHelp="Switch scenes based on the window title of running applications.\nThe following additional conditions can be selected:\nThe window is Fullscreen\nThe window is maximized\nThe window is focused\n\nClick on the highlighted plus symbol to continue."
AdvSceneSwitcher.windowTitleTab.ignoreWindowsHelp="If a window title is ignored the scene switcher will act as if the previously selected window is still in focus.\nThis will allow you to avoid scene switches, if you frequently switch to a different window, which shall not trigger a scene change.\n\nChoose a window or enter a window title above and click on the plus symbol below to add it to the list."
; Executable Tab
AdvSceneSwitcher.executableTab.title="Executable"
AdvSceneSwitcher.executableTab.implemented="Implemented by dasOven"
AdvSceneSwitcher.executableTab.requiresFocus="only if focused"
AdvSceneSwitcher.executableTab.entry="When {{processes}} is running switch to {{scenes}} using {{transitions}} {{requiresFocus}}"
AdvSceneSwitcher.executableTab.help="This tab will allow you to automatically switch scenes if a process is running.\nThis can be useful in situations where the window name could change or is not known.\n\nClick on the highlighted plus symbol to continue."
; Screen Region Tab
AdvSceneSwitcher.screenRegionTab.title="Region"
AdvSceneSwitcher.screenRegionTab.currentPosition="Cursor is currently at:"
AdvSceneSwitcher.screenRegionTab.showGuideFrames="Show guide frames"
AdvSceneSwitcher.screenRegionTab.hideGuideFrames="Hide guide frames"
AdvSceneSwitcher.screenRegionTab.excludeScenes.None="No selection"
AdvSceneSwitcher.screenRegionTab.entry="If cursor is in {{minX}} {{minY}} x {{maxX}} {{maxY}} switch to {{scenes}} using {{transitions}} unless in {{excludeScenes}}"
AdvSceneSwitcher.screenRegionTab.help="This tab will allow you to automatically switch scenes based on the current position of your mouse cursor.\n\nClick on the highlighted plus symbol to continue."
; Media Tab
AdvSceneSwitcher.mediaTab.title="Media"
AdvSceneSwitcher.mediaTab.implemented="Implemented by Exeldro"
AdvSceneSwitcher.mediaTab.states.none="None"
AdvSceneSwitcher.mediaTab.states.playing="Playing"
AdvSceneSwitcher.mediaTab.states.opening="Opening"
AdvSceneSwitcher.mediaTab.states.buffering="Buffering"
AdvSceneSwitcher.mediaTab.states.Paused="Paused"
AdvSceneSwitcher.mediaTab.states.stopped="Stopped"
AdvSceneSwitcher.mediaTab.states.ended="Ended"
AdvSceneSwitcher.mediaTab.states.error="Error"
AdvSceneSwitcher.mediaTab.states.playedToEnd="Played to end"
AdvSceneSwitcher.mediaTab.states.any="Any"
AdvSceneSwitcher.mediaTab.timeRestriction.none="None"
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="Time shorter"
AdvSceneSwitcher.mediaTab.timeRestriction.longer="Time longer"
AdvSceneSwitcher.mediaTab.timeRestriction.remainShorter="Time remaining shorter"
AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger="Time remaining longer"
AdvSceneSwitcher.mediaTab.entry="When {{mediaSources}} state is {{states}} and {{timeRestrictions}} {{time}} switch to {{scenes}} using {{transitions}}"
AdvSceneSwitcher.mediaTab.help="This tab will allow you to switch scenes based on the states of media sources.\nFor example, you can automatically switch back to the previous scene once the selected media sourced ended its playback.\n\nClick on the highlighted plus symbol to continue."
; File Tab
AdvSceneSwitcher.fileTab.title="File"
AdvSceneSwitcher.fileTab.readWriteSceneFile="Read / write scene from / to file"
AdvSceneSwitcher.fileTab.currentSceneOutputFile="Write the name of the current scene to this file:"
AdvSceneSwitcher.fileTab.switchSceneBaseOnFile="Enable switching of scenes based on file input"
AdvSceneSwitcher.fileTab.switchSceneNameInputFile="Read scene name to be switched to from this file:"
AdvSceneSwitcher.fileTab.switchSceneBaseOnFileContent="Switch scene based on file contents"
AdvSceneSwitcher.fileTab.remoteFileWarning="Please note that if you choose the remote option the scene switcher will try to access the remote location every x ms as specified on the General tab!"
AdvSceneSwitcher.fileTab.remoteFileWarning1="Note that the scene switcher will try to access the remote location every "
AdvSceneSwitcher.fileTab.remoteFileWarning2="ms"
AdvSceneSwitcher.fileTab.libcurlWarning="Failed to load libcurl! Accessing remote files will not be possible!"
AdvSceneSwitcher.fileTab.selectWrite="Select a file to write to ..."
AdvSceneSwitcher.fileTab.selectRead="Select a file to read from ..."
AdvSceneSwitcher.fileTab.textFileType="Text files (*.txt)"
AdvSceneSwitcher.fileTab.anyFileType="Any files (*.*)"
AdvSceneSwitcher.fileTab.remote="remote file"
AdvSceneSwitcher.fileTab.local="local file"
AdvSceneSwitcher.fileTab.useRegExp="use regular expressions (pattern matching)"
AdvSceneSwitcher.fileTab.checkfileContentTime="if modification date changed"
AdvSceneSwitcher.fileTab.checkfileContent="if content changed"
AdvSceneSwitcher.fileTab.entry="Switch to {{scenes}} using {{transitions}} if content of {{fileType}} {{filePath}} {{browseButton}} matches:"
AdvSceneSwitcher.fileTab.entry2="{{matchText}}"
AdvSceneSwitcher.fileTab.entry3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.fileTab.help="This tab will allow you to automatically switch scenes based on the content of remote or local files.\n\nClick on the highlighted plus symbol to continue."
; Random Tab
AdvSceneSwitcher.randomTab.title="Random"
AdvSceneSwitcher.randomTab.randomDisabledWarning="Functionality disabled - To activate select \"If no switch condition is met switch to any scene in Random tab\" on General tab"
AdvSceneSwitcher.randomTab.entry="If no switch condition is met switch to {{scenes}} using {{transitions}} for {{delay}}"
AdvSceneSwitcher.randomTab.help="The scene switcher will randomly choose an entry on this tab to switch to for the configured time.\nNote that the same entry will not be chosen twice in a row.\n\nClick on the highlighted plus symbol to continue."
; Time Tab
AdvSceneSwitcher.timeTab.title="Time"
AdvSceneSwitcher.timeTab.anyDay="On any day"
AdvSceneSwitcher.timeTab.mondays="Mondays"
AdvSceneSwitcher.timeTab.tuesdays="Tuesdays"
AdvSceneSwitcher.timeTab.wednesdays="Wednesdays"
AdvSceneSwitcher.timeTab.thursdays="Thursdays"
AdvSceneSwitcher.timeTab.fridays="Fridays"
AdvSceneSwitcher.timeTab.saturdays="Saturdays"
AdvSceneSwitcher.timeTab.sundays="Sundays"
AdvSceneSwitcher.timeTab.afterstart="After streaming/recording start"
AdvSceneSwitcher.timeTab.afterstart.tip="The time relative to the start of streaming / recording will be used"
AdvSceneSwitcher.timeTab.entry="{{triggers}} at {{time}} switch to {{scenes}} using {{transitions}}"
AdvSceneSwitcher.timeTab.help="This tab will allow you to automatically switch to a different scene based on the current local time.\n\nNote that the scene switcher will only switch scenes at the exact time you specified.\nMake sure you have configured the priority settings on the General tab to your liking so the selected time point will not be missed due to other switching methods having a higher priority.\n\nClick on the highlighted plus symbol to continue."
; Idle Tab
AdvSceneSwitcher.idleTab.title="Idle"
AdvSceneSwitcher.idleTab.enable="Enable Idle Detection"
AdvSceneSwitcher.idleTab.idleswitch="After {{duration}} of no keyboard or mouse inputs switch to scene {{scenes}} using the {{transitions}}"
AdvSceneSwitcher.idleTab.dontSwitchIfFocus1="Do not switch if"
AdvSceneSwitcher.idleTab.dontSwitchIfFocus2="is in focus"
; Scene Sequence Tab
AdvSceneSwitcher.sceneSequenceTab.title="Sequence"
AdvSceneSwitcher.sceneSequenceTab.description="A sequence of automatic scene switches can be cancelled by either pausing/stopping the scene switcher or manually switching to a different scene"
AdvSceneSwitcher.sceneSequenceTab.save="Save scene sequences to file"
AdvSceneSwitcher.sceneSequenceTab.load="Load scene sequences from file"
AdvSceneSwitcher.sceneSequenceTab.saveTitle="Save Scene Sequence to file ..."
AdvSceneSwitcher.sceneSequenceTab.loadTitle="Select a file to read Scene Sequence from ..."
AdvSceneSwitcher.sceneSequenceTab.loadFail="Advanced Scene Switcher failed to import settings!"
AdvSceneSwitcher.sceneSequenceTab.loadSuccess="Advanced Scene Switcher settings imported successfully!"
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.extendEdit="Extend Sequence"
AdvSceneSwitcher.sceneSequenceTab.extendEntry="After {{delay}} {{delayUnits}} 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.multiMatchfallbackCondition="If multiple entries match ..."
AdvSceneSwitcher.audioTab.multiMatchfallback="... for {{duration}} 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
AdvSceneSwitcher.videoTab.title="Video"
AdvSceneSwitcher.videoTab.getScreenshot="Get screenshot for selected entry"
AdvSceneSwitcher.videoTab.getScreenshotHelp="Get Screenshot of the currently selected entry's video source and automatically set it as the target image"
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.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>"
; Scene Group Tab
AdvSceneSwitcher.sceneGroupTab.title="Scene Group"
AdvSceneSwitcher.sceneGroupTab.list="Scene Groups"
AdvSceneSwitcher.sceneGroupTab.edit="Edit Scene Groups"
AdvSceneSwitcher.sceneGroupTab.edit.name="Name:"
AdvSceneSwitcher.sceneGroupTab.edit.type="Type: {{type}}"
AdvSceneSwitcher.sceneGroupTab.type.count="Count"
AdvSceneSwitcher.sceneGroupTab.type.time="Time"
AdvSceneSwitcher.sceneGroupTab.type.random="Random"
AdvSceneSwitcher.sceneGroupTab.edit.count="Advance to next scene in list after {{count}} matches"
AdvSceneSwitcher.sceneGroupTab.edit.time="Advance to next scene in list after {{time}} has passed"
AdvSceneSwitcher.sceneGroupTab.edit.random="Choose next scene in list at random"
AdvSceneSwitcher.sceneGroupTab.edit.repeat="Start from beginning if end of scene list is reached"
AdvSceneSwitcher.sceneGroupTab.edit.addScene="Add scene"
AdvSceneSwitcher.sceneGroupTab.add="Add Scene Group"
AdvSceneSwitcher.sceneGroupTab.defaultname="Scene Group %1"
AdvSceneSwitcher.sceneGroupTab.exists="Scene Group or Scene name exists already"
AdvSceneSwitcher.sceneGroupTab.help="Scene Groups can be selected as a target just like a regular scene.\n\nAs the name suggests a scene group is a collection of multiple scenes.\nThe scene group will advance through the list of its assigned scenes depending on the configured settings, which can be found on the right side.\n\nYou can configure the scene group to advance to the next scene in the list:\nAfter a number of times the scene group is selected as a target.\nAfter a certain amount of time has passed.\nOr randomly.\n\nFor example, a scene group containing the scenes ...\nScene 1\nScene 2\nScene 3 \n... will activate \"Scene 1\" the first time it is selected as a target.\nThe second time it will activate \"Scene 2\".\nThe remaining times \"Scene 3\" will be activated.\n\nClick the highlighted plus symbol below to add a new scene group."
AdvSceneSwitcher.sceneGroupTab.scenes.help="Select the scene group you want to modify on the left.\n\nSelect a scene to add to this scene group by selecting the scene above and clicking the plus symbol below.\n\nA scene can be added multiple times to the same scene group."
; Scene Trigger Tab
AdvSceneSwitcher.sceneTriggerTab.title="Scene Triggers"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none="--select trigger--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive="is active"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneInactive="is not active"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneLeave="switched away from"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none="--select action--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording="start recording"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.pauseRecording="pause recording"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unpauseRecording="unpause recording"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopRecording="stop recording"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming="stop streaming"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="start streaming"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="start replay buffer"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="stop replay buffer"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="mute source"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="unmute source"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="start the scene switcher"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="stop the scene switcher"
AdvSceneSwitcher.sceneTriggerTab.entry="When {{scenes}} {{triggers}} {{actions}} {{audioSources}} after {{duration}}"
AdvSceneSwitcher.sceneTriggerTab.help="This tab allows you to trigger actions on scene changes, like stopping recording or streaming."
; Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher"
AdvSceneSwitcher.hotkey.stopSwitcherHotkey="Stop the Advanced Scene Switcher"
AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="Toggle Start/Stop for the Advanced Scene Switcher"
AdvSceneSwitcher.askBackup="Detected a new version of the Advanced Scene Switcher.\nShould a backup of the old settings be created?"
AdvSceneSwitcher.close="Close"
AdvSceneSwitcher.browse="Browse"
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.currentTransition="Current Transition"
AdvSceneSwitcher.selectTransition="--select transition--"
AdvSceneSwitcher.selectWindow="--select window--"
AdvSceneSwitcher.selectAudioSource="--select audio source--"
AdvSceneSwitcher.selectVideoSource="--select video source--"
AdvSceneSwitcher.selectMediaSource="--select media source--"
AdvSceneSwitcher.selectProcess="--select process--"
AdvSceneSwitcher.enterPath="--enter path--"
AdvSceneSwitcher.enterText="--enter text--"
AdvSceneSwitcher.invaildEntriesWillNotBeSaved="invalid entries will not be saved"
AdvSceneSwitcher.selectWindowTip="Use \"OBS\" to specify OBS window\nUse \"Task Switching\"to specify ALT + TAB"
AdvSceneSwitcher.status.active="Active"
AdvSceneSwitcher.status.inactive="Inactive"
AdvSceneSwitcher.unit.milliseconds="milliseconds"
AdvSceneSwitcher.unit.secends="seconds"
AdvSceneSwitcher.unit.minutes="minutes"
AdvSceneSwitcher.unit.hours="hours"

203
data/locale/zh-CN.ini Normal file
View File

@@ -0,0 +1,203 @@
AdvSceneSwitcher.pluginName="高级场景切换器"
AdvSceneSwitcher.windowTitle="高级场景切换器"
; General Tab
AdvSceneSwitcher.generalTab.title="通用"
AdvSceneSwitcher.generalTab.status="状态"
AdvSceneSwitcher.generalTab.status.hotkeytips="快捷键可在OBS设置中设置"
AdvSceneSwitcher.generalTab.status.currentStatus="当前状态:"
AdvSceneSwitcher.generalTab.status.onStartup="在OBS启动时"
AdvSceneSwitcher.generalTab.status.onStartup.asLastRun="按照最后运行时的状态"
AdvSceneSwitcher.generalTab.status.onStartup.alwaysStart="总是自动启动"
AdvSceneSwitcher.generalTab.status.onStartup.doNotStart="不要启动"
AdvSceneSwitcher.generalTab.status.start="启动"
AdvSceneSwitcher.generalTab.status.stop="停止"
AdvSceneSwitcher.generalTab.status.autoStart.recording="录制"
AdvSceneSwitcher.generalTab.status.autoStart.streaming="推流"
AdvSceneSwitcher.generalTab.status.autoStart.recordingAndStreaming="录制和推流"
AdvSceneSwitcher.generalTab.status.checkInterval="检查切换条件时间间隔"
AdvSceneSwitcher.generalTab.generalBehavior="一般表现"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet="如果不符合任何切换条件"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMetDelayTooltip="时间精度最高只能达到配置的轮询间隔"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch="不切换"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom="切换到随机场景列表中任意随机场景"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="切换到"
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="详细日志输出"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="禁用UI提示"
AdvSceneSwitcher.generalTab.priority="优先级"
AdvSceneSwitcher.generalTab.priority.description="切换场景优先级 (最上方的项优先级最高)"
AdvSceneSwitcher.generalTab.priority.threadPriority="使用线程优先级"
AdvSceneSwitcher.generalTab.priority.threadPriorityNotice="(不推荐使用高于 \"Normal\" 的优先级)"
AdvSceneSwitcher.generalTab.saveOrLoadsettings="导出或导入设置"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.export="导出设置"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.import="导入设置"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.exportWindowTitle="导出高级场景切换器到文件..."
AdvSceneSwitcher.generalTab.saveOrLoadsettings.importWindowTitle="导入高级场景切换器到文件..."
AdvSceneSwitcher.generalTab.saveOrLoadsettings.textType="文本文件 (*.txt)"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.loadFail="高级场景切换器导入设置失败"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.loadSuccess="高级场景切换器导入设置成功"
AdvSceneSwitcher.generalTab.priority.fileContent="文件内容"
AdvSceneSwitcher.generalTab.priority.sceneSequence="场景序列"
AdvSceneSwitcher.generalTab.priority.idleDetection="闲置检测"
AdvSceneSwitcher.generalTab.priority.executable="程序"
AdvSceneSwitcher.generalTab.priority.screenRegion="屏幕区域"
AdvSceneSwitcher.generalTab.priority.windowTitle="窗口标题"
AdvSceneSwitcher.generalTab.priority.media="媒体"
AdvSceneSwitcher.generalTab.priority.time="时间"
AdvSceneSwitcher.generalTab.priority.audio="音频"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="转场特效"
AdvSceneSwitcher.transitionTab.transitionOverride="在场景切换器中设定的转场特效优先级高于场景设定的转场特效"
AdvSceneSwitcher.transitionTab.transitionForAToB="当自动从场景A切换到场景B时使用的转场特效"
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>这里的设定<span style=\"font-style:bold;\">只影响由场景切换器引起的转场有效</span>,不影响你手动的引起的转场。<br/>在这里设定的转场特效优先级高于场景切换器其他地方设置的<br/><br/>Click the plus symbol below to continue.</p></body></html>"
AdvSceneSwitcher.transitionTab.defaultTransition="当切换到这个场景时修改默认转场特效"
AdvSceneSwitcher.transitionTab.switchDefaultLabel="活跃时切换默认转场特效为"
AdvSceneSwitcher.transitionTab.entry="从场景 {{scenes}} 切换到场景 {{scenes2}} 时使用转场特效 {{transitions}}"
AdvSceneSwitcher.transitionTab.defaultTransitionEntry="当场景 {{scenes}} 被激活时更改默认转场特效为 {{transitions}}"
; Pause Scenes Tab
AdvSceneSwitcher.pauseTab.title="暂停"
AdvSceneSwitcher.pauseTab.pauseOnScene="当切换到这个场景时暂停场景切换器"
AdvSceneSwitcher.pauseTab.pauseInFocus1="当窗口"
AdvSceneSwitcher.pauseTab.pauseInFocus2="获得焦点时暂停场景切换器"
; Window Title Tab
AdvSceneSwitcher.windowTitleTab.title="窗口标题"
AdvSceneSwitcher.windowTitleTab.regexrDescription="<html><head/><body><p>你可以直接输入窗口标题或正则表达式. 你可以使用<a href=\"https://regexr.com\"><span style=\" text-decoration: underline; color:#268bd2;\">RegExr</span></a>来检查验证你的正则表达式</p></body></html>"
AdvSceneSwitcher.windowTitleTab.stayInFocus1="当前焦点窗口标题为"
AdvSceneSwitcher.windowTitleTab.stayInFocus2="时保持当前场景"
AdvSceneSwitcher.windowTitleTab.fullscreen="全屏时"
AdvSceneSwitcher.windowTitleTab.maximized="最大化时"
AdvSceneSwitcher.windowTitleTab.focused="窗口聚集时"
AdvSceneSwitcher.windowTitleTab.entry="{{windows}} {{scenes}} {{transitions}} {{fullscreen}} {{maximized}} {{focused}}"
; Executable Tab
AdvSceneSwitcher.executableTab.title="程序"
AdvSceneSwitcher.executableTab.implemented="感谢dasOven的实现"
AdvSceneSwitcher.executableTab.requiresFocus="仅获得焦点时"
AdvSceneSwitcher.executableTab.entry="当 {{processes}} 正在运行时使用 {{transitions}} 切换到 {{scenes}} {{requiresFocus}}"
; Screen Region Tab
AdvSceneSwitcher.screenRegionTab.title="屏幕区域"
AdvSceneSwitcher.screenRegionTab.currentPosition="鼠标当前位置:"
AdvSceneSwitcher.screenRegionTab.showGuideFrames="显示区域边界"
AdvSceneSwitcher.screenRegionTab.hideGuideFrames="隐藏区域边界"
AdvSceneSwitcher.screenRegionTab.entry="如果鼠标在 {{minX}} {{minY}} x {{maxX}} {{maxY}} 使用转场特效 {{transitions}} 切换到场景 {{scenes}} unless in {{excludeScenes}}"
; Media Tab
AdvSceneSwitcher.mediaTab.title="媒体"
AdvSceneSwitcher.mediaTab.implemented="感谢Exeldro的实现"
AdvSceneSwitcher.mediaTab.states.none="空"
AdvSceneSwitcher.mediaTab.states.playing="播放中"
AdvSceneSwitcher.mediaTab.states.opening="正在打开"
AdvSceneSwitcher.mediaTab.states.buffering="缓冲中"
AdvSceneSwitcher.mediaTab.states.Paused="暂停"
AdvSceneSwitcher.mediaTab.states.stopped="停止"
AdvSceneSwitcher.mediaTab.states.ended="结束"
AdvSceneSwitcher.mediaTab.states.error="错误"
AdvSceneSwitcher.mediaTab.states.any="任意"
AdvSceneSwitcher.mediaTab.timeRestriction.none="无"
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="播放时间小于"
AdvSceneSwitcher.mediaTab.timeRestriction.longer="播放时间大于"
AdvSceneSwitcher.mediaTab.timeRestriction.remainShorter="剩余时间少于"
AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger="剩余时间多于"
AdvSceneSwitcher.mediaTab.entry="当 {{mediaSources}} 状态为 {{states}} 并且 {{timeRestrictions}} {{time}} 时使用转场特效 {{transitions}} 切换到场景 {{scenes}}"
; File Tab
AdvSceneSwitcher.fileTab.title="文件"
AdvSceneSwitcher.fileTab.readWriteSceneFile="从文件加载或保存当前场景名"
AdvSceneSwitcher.fileTab.currentSceneOutputFile="将当前场景写入到这个文件:"
AdvSceneSwitcher.fileTab.switchSceneBaseOnFile="启用从文件读取应切换到的场景名"
AdvSceneSwitcher.fileTab.switchSceneNameInputFile="从这个文件读取应切换到的场景名:"
AdvSceneSwitcher.fileTab.switchSceneBaseOnFileContent="根据文件内容切换场景"
AdvSceneSwitcher.fileTab.remoteFileWarning="请注意 如果你使用网络文件,场景切换器会按照你在通用页设置的间隔时间尝试访问网络文件!"
AdvSceneSwitcher.fileTab.remoteFileWarning1="注意,场景切换器会每 "
AdvSceneSwitcher.fileTab.remoteFileWarning2="毫秒尝试访问网络文件"
AdvSceneSwitcher.fileTab.libcurlWarning="无法加载 libcurl 库! 访问网络文件功能将不可用!"
AdvSceneSwitcher.fileTab.selectWrite="写入到文件..."
AdvSceneSwitcher.fileTab.selectRead="从文件读取..."
AdvSceneSwitcher.fileTab.textFileType="文本文件 (*.txt)"
AdvSceneSwitcher.fileTab.anyFileType="任意文件 (*.*)"
AdvSceneSwitcher.fileTab.remote="网络"
AdvSceneSwitcher.fileTab.local="本地"
AdvSceneSwitcher.fileTab.useRegExp="使用正则表达式(模式匹配)"
AdvSceneSwitcher.fileTab.checkfileContentTime="仅当文件修改时间改变时检查文件内容"
AdvSceneSwitcher.fileTab.checkfileContent="仅当文件发生修改时"
AdvSceneSwitcher.fileTab.entry="如果 {{fileType}} 文件 {{filePath}} {{browseButton}} 匹配下列规则,使用转场特效 {{transitions}} 切换到场景 {{scenes}}"
AdvSceneSwitcher.fileTab.entry2="{{matchText}}"
AdvSceneSwitcher.fileTab.entry3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
; Random Tab
AdvSceneSwitcher.randomTab.title="随机场景列表"
AdvSceneSwitcher.randomTab.randomDisabledWarning="功能已被禁用 - 可通过在通用页设置\"如果不符合任何切换条件切换到随机场景列表中任意随机场景\"启用"
AdvSceneSwitcher.randomTab.entry="如果没有切换器条件满足,使用转场特效 {{transitions}} 切换到场景 {{scenes}} 并停留 {{delay}}"
; Time Tab
AdvSceneSwitcher.timeTab.title="时间"
AdvSceneSwitcher.timeTab.anyDay="每天"
AdvSceneSwitcher.timeTab.mondays="每周一"
AdvSceneSwitcher.timeTab.tuesdays="每周二"
AdvSceneSwitcher.timeTab.wednesdays="每周三"
AdvSceneSwitcher.timeTab.thursdays="每周四"
AdvSceneSwitcher.timeTab.fridays="每周五"
AdvSceneSwitcher.timeTab.saturdays="每周六"
AdvSceneSwitcher.timeTab.sundays="每周日"
AdvSceneSwitcher.timeTab.afterstart="推流或录制开始后"
AdvSceneSwitcher.timeTab.afterstart.tip="相对于直播或录制开始之后的时间点"
AdvSceneSwitcher.timeTab.entry="{{triggers}} 的 {{time}} 使用转场特效 {{transitions}} 切换到场景 {{scenes}}"
; Idle Tab
AdvSceneSwitcher.idleTab.title="闲置检测"
AdvSceneSwitcher.idleTab.enable="启用闲置检测"
AdvSceneSwitcher.idleTab.condition1="超过"
AdvSceneSwitcher.idleTab.condition2="没有鼠标或键盘操作"
AdvSceneSwitcher.idleTab.switchToScene="切换到场景"
AdvSceneSwitcher.idleTab.usingTransition1="使用"
AdvSceneSwitcher.idleTab.usingTransition2="转场特效"
AdvSceneSwitcher.idleTab.dontSwitchIfFocus1="如果当前窗口焦点在"
AdvSceneSwitcher.idleTab.dontSwitchIfFocus2="时不切换"
; Scene Sequence Tab
AdvSceneSwitcher.sceneSequenceTab.title="场景序列"
AdvSceneSwitcher.sceneSequenceTab.description="自动场景序列可以通过暂停或停止场景切换器或者手动切换到其他场景来取消"
AdvSceneSwitcher.sceneSequenceTab.save="保存当前序列到文件"
AdvSceneSwitcher.sceneSequenceTab.load="从文件加载序列"
AdvSceneSwitcher.sceneSequenceTab.saveTitle="保存当前序列到文件 ..."
AdvSceneSwitcher.sceneSequenceTab.loadTitle="从文件加载序列 ..."
AdvSceneSwitcher.sceneSequenceTab.loadFail="导入失败"
AdvSceneSwitcher.sceneSequenceTab.loadSuccess="导入失败成功"
AdvSceneSwitcher.sceneSequenceTab.fileType="文本文件 (*.txt)"
AdvSceneSwitcher.sceneSequenceTab.entry="当场景 {{startScenes}} 被激活 {{delay}} {{delayUnits}}后使用转场特效 {{transitions}} 切换到场景 {{scenes}} {{interruptible}}"
; Audio Tab
AdvSceneSwitcher.audioTab.title="音频"
AdvSceneSwitcher.audioTab.entry="当{{audioSources}} is {{condition}} {{volumeWidget}} for {{duration}} 使用转场特效 {{transitions}} 切换到场景 {{scenes}}"
; Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="启动高级场景切换器"
AdvSceneSwitcher.hotkey.stopSwitcherHotkey="停止高级场景切换器"
AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="切换(启动/停止)高级场景切换器"
AdvSceneSwitcher.close="关闭"
AdvSceneSwitcher.browse="浏览文件"
AdvSceneSwitcher.selectScene="--选择场景--"
AdvSceneSwitcher.selectTransition="--选择转场特效--"
AdvSceneSwitcher.selectWindow="--选择窗口--"
AdvSceneSwitcher.selectAudioSource="--选择音频源--"
AdvSceneSwitcher.selectMediaSource="--选择媒体源--"
AdvSceneSwitcher.selectProcess="--选择进程--"
AdvSceneSwitcher.enterPath="--输入路径--"
AdvSceneSwitcher.enterText="--输入文本--"
AdvSceneSwitcher.invaildEntriesWillNotBeSaved="无效项将不会被保存"
AdvSceneSwitcher.selectWindowTip="使用 \"OBS\" 来指定OBS窗口\n使用 \"Task Switching\" 来指定 Alt + Tab"
AdvSceneSwitcher.status.active="运行中"
AdvSceneSwitcher.status.inactive="已停止"
AdvSceneSwitcher.unit.milliseconds="毫秒"
AdvSceneSwitcher.unit.secends="秒"
AdvSceneSwitcher.unit.minutes="分钟"
AdvSceneSwitcher.unit.hours="小时"

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,15 @@
#include <obs-module.h>
#include <obs-frontend-api.h>
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("advanced-scene-switcher", "en-US")
void InitSceneSwitcher();
void FreeSceneSwitcher();
bool obs_module_load(void)
{
obs_frontend_push_ui_translation(obs_module_get_string);
InitSceneSwitcher();
return true;
}

View File

@@ -1,29 +1,25 @@
#include <QMainWindow>
#include <QDir>
#include <QAction>
#include <QtGui/qstandarditemmodel.h>
#include <QPropertyAnimation>
#include <QGraphicsColorizeEffect>
#include <QMessageBox>
#include <condition_variable>
#include <chrono>
#include <vector>
#include <mutex>
#include <thread>
#include <obs-frontend-api.h>
#include <obs-module.h>
#include <obs.hpp>
#include <obs-frontend-api.h>
#include <util/util.hpp>
#include "headers/switcher-data-structs.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
#include "headers/curl-helper.hpp"
#include "headers/version.h"
SwitcherData *switcher = nullptr;
/********************************************************************************
/******************************************************************************
* Create the Advanced Scene Switcher settings window
********************************************************************************/
SceneSwitcher::SceneSwitcher(QWidget *parent)
: QDialog(parent), ui(new Ui_SceneSwitcher)
******************************************************************************/
AdvSceneSwitcher::AdvSceneSwitcher(QWidget *parent)
: QDialog(parent), ui(new Ui_AdvSceneSwitcher)
{
ui->setupUi(this);
@@ -33,47 +29,20 @@ SceneSwitcher::SceneSwitcher(QWidget *parent)
loadUI();
}
void SceneSwitcher::populateSceneSelection(QComboBox *sel, bool addPrevious)
bool translationAvailable()
{
BPtr<char *> scenes = obs_frontend_get_scene_names();
char **temp = scenes;
while (*temp) {
const char *name = *temp;
sel->addItem(name);
temp++;
}
if (addPrevious)
sel->addItem(PREVIOUS_SCENE_NAME);
return !!strcmp(obs_module_text("AdvSceneSwitcher.pluginName"),
"AdvSceneSwitcher.pluginName");
}
void SceneSwitcher::populateTransitionSelection(QComboBox *sel)
void AdvSceneSwitcher::loadUI()
{
obs_frontend_source_list *transitions = new obs_frontend_source_list();
obs_frontend_get_transitions(transitions);
for (size_t i = 0; i < transitions->sources.num; i++) {
const char *name =
obs_source_get_name(transitions->sources.array[i]);
sel->addItem(name);
if (!translationAvailable()) {
(void)DisplayMessage(
"Failed to find plug-in's 'data' directory.\n"
"Please check installation instructions!");
}
obs_frontend_source_list_free(transitions);
}
void SceneSwitcher::populateWindowSelection(QComboBox *sel)
{
std::vector<std::string> windows;
GetWindowList(windows);
sort(windows.begin(), windows.end());
for (std::string &window : windows) {
sel->addItem(window.c_str());
}
}
void SceneSwitcher::loadUI()
{
#if __APPLE__
setMinimumHeight(700);
#endif
@@ -89,15 +58,364 @@ void SceneSwitcher::loadUI()
setupMediaTab();
setupFileTab();
setupTimeTab();
setupAudioTab();
setupVideoTab();
setupSceneGroupTab();
setupTriggerTab();
setTabOrder();
loading = false;
}
/********************************************************************************
/******************************************************************************
* 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)
{
sel->addItem(description);
if (strcmp(tooltip, "") != 0) {
sel->setItemData(0, tooltip, Qt::ToolTipRole);
}
QStandardItemModel *model =
qobject_cast<QStandardItemModel *>(sel->model());
QModelIndex firstIndex =
model->index(0, sel->modelColumn(), sel->rootModelIndex());
QStandardItem *firstItem = model->itemFromIndex(firstIndex);
if (!selectable) {
firstItem->setSelectable(false);
firstItem->setEnabled(false);
}
}
void AdvSceneSwitcher::populateSceneSelection(QComboBox *sel, bool addPrevious,
bool addSceneGroup,
bool addSelect,
std::string selectText,
bool selectable)
{
if (addSelect) {
if (selectText.empty()) {
addSelectionEntry(
sel,
obs_module_text("AdvSceneSwitcher.selectScene"),
selectable,
obs_module_text(
"AdvSceneSwitcher.invaildEntriesWillNotBeSaved"));
} else {
addSelectionEntry(sel, selectText.c_str(), selectable);
}
}
BPtr<char *> scenes = obs_frontend_get_scene_names();
char **temp = scenes;
while (*temp) {
const char *name = *temp;
sel->addItem(name);
temp++;
}
if (addPrevious) {
sel->addItem(obs_module_text(
"AdvSceneSwitcher.selectPreviousScene"));
}
if (addSceneGroup) {
for (auto &sg : switcher->sceneGroups) {
sel->addItem(QString::fromStdString(sg.name));
}
}
}
void AdvSceneSwitcher::populateTransitionSelection(QComboBox *sel,
bool addCurrent,
bool addSelect,
bool selectable)
{
if (addSelect) {
addSelectionEntry(
sel,
obs_module_text("AdvSceneSwitcher.selectTransition"),
selectable);
}
if (addCurrent) {
sel->addItem(
obs_module_text("AdvSceneSwitcher.currentTransition"));
}
obs_frontend_source_list *transitions = new obs_frontend_source_list();
obs_frontend_get_transitions(transitions);
for (size_t i = 0; i < transitions->sources.num; i++) {
const char *name =
obs_source_get_name(transitions->sources.array[i]);
sel->addItem(name);
}
obs_frontend_source_list_free(transitions);
}
void AdvSceneSwitcher::populateWindowSelection(QComboBox *sel, bool addSelect)
{
if (addSelect) {
addSelectionEntry(
sel, obs_module_text("AdvSceneSwitcher.selectWindow"));
}
std::vector<std::string> windows;
GetWindowList(windows);
sort(windows.begin(), windows.end());
for (std::string &window : windows) {
sel->addItem(window.c_str());
}
#ifdef WIN32
sel->setItemData(0, obs_module_text("AdvSceneSwitcher.selectWindowTip"),
Qt::ToolTipRole);
#endif
}
void AdvSceneSwitcher::populateAudioSelection(QComboBox *sel, bool addSelect)
{
if (addSelect) {
addSelectionEntry(
sel,
obs_module_text("AdvSceneSwitcher.selectAudioSource"),
false,
obs_module_text(
"AdvSceneSwitcher.invaildEntriesWillNotBeSaved"));
}
auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */
{
std::vector<std::string> *list =
reinterpret_cast<std::vector<std::string> *>(data);
uint32_t flags = obs_source_get_output_flags(source);
if ((flags & OBS_SOURCE_AUDIO) != 0) {
list->push_back(obs_source_get_name(source));
}
return true;
};
std::vector<std::string> audioSources;
obs_enum_sources(sourceEnum, &audioSources);
sort(audioSources.begin(), audioSources.end());
for (std::string &source : audioSources) {
sel->addItem(source.c_str());
}
}
void AdvSceneSwitcher::populateVideoSelection(QComboBox *sel, bool addScenes,
bool addSelect)
{
if (addSelect) {
addSelectionEntry(
sel,
obs_module_text("AdvSceneSwitcher.selectVideoSource"),
false,
obs_module_text(
"AdvSceneSwitcher.invaildEntriesWillNotBeSaved"));
}
auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */
{
std::vector<std::string> *list =
reinterpret_cast<std::vector<std::string> *>(data);
uint32_t flags = obs_source_get_output_flags(source);
std::string test = obs_source_get_name(source);
if ((flags & (OBS_SOURCE_VIDEO | OBS_SOURCE_ASYNC)) != 0) {
list->push_back(obs_source_get_name(source));
}
return true;
};
std::vector<std::string> videoSources;
obs_enum_sources(sourceEnum, &videoSources);
sort(videoSources.begin(), videoSources.end());
for (std::string &source : videoSources) {
sel->addItem(source.c_str());
}
if (addScenes) {
populateSceneSelection(sel, false, false, false);
}
}
void AdvSceneSwitcher::populateMediaSelection(QComboBox *sel, bool addSelect)
{
if (addSelect) {
addSelectionEntry(
sel,
obs_module_text("AdvSceneSwitcher.selectMediaSource"),
false,
obs_module_text(
"AdvSceneSwitcher.invaildEntriesWillNotBeSaved"));
}
auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */
{
std::vector<std::string> *list =
reinterpret_cast<std::vector<std::string> *>(data);
std::string sourceId = obs_source_get_id(source);
if (sourceId.compare("ffmpeg_source") == 0 ||
sourceId.compare("vlc_source") == 0) {
list->push_back(obs_source_get_name(source));
}
return true;
};
std::vector<std::string> mediaSources;
obs_enum_sources(sourceEnum, &mediaSources);
sort(mediaSources.begin(), mediaSources.end());
for (std::string &source : mediaSources) {
sel->addItem(source.c_str());
}
}
void AdvSceneSwitcher::populateProcessSelection(QComboBox *sel, bool addSelect)
{
if (addSelect) {
addSelectionEntry(
sel, obs_module_text("AdvSceneSwitcher.selectProcess"));
}
QStringList processes;
GetProcessList(processes);
processes.sort();
for (QString &process : processes)
sel->addItem(process);
}
void AdvSceneSwitcher::listAddClicked(QListWidget *list,
SwitchWidget *newWidget,
QPushButton *addButton,
QMetaObject::Connection *addHighlight)
{
if (!list || !newWidget) {
blog(LOG_WARNING,
"listAddClicked called without valid list or widget");
return;
}
if (addButton && addHighlight) {
addButton->disconnect(*addHighlight);
}
QListWidgetItem *item;
item = new QListWidgetItem(list);
list->addItem(item);
item->setSizeHint(newWidget->minimumSizeHint());
list->setItemWidget(item, newWidget);
list->scrollToItem(item);
}
bool AdvSceneSwitcher::listMoveUp(QListWidget *list)
{
int index = list->currentRow();
if (index == -1 || index == 0) {
return false;
}
QWidget *row = list->itemWidget(list->currentItem());
QListWidgetItem *itemN = list->currentItem()->clone();
list->insertItem(index - 1, itemN);
list->setItemWidget(itemN, row);
list->takeItem(index + 1);
list->setCurrentRow(index - 1);
return true;
}
bool AdvSceneSwitcher::listMoveDown(QListWidget *list)
{
int index = list->currentRow();
if (index == -1 || index == list->count() - 1) {
return false;
}
QWidget *row = list->itemWidget(list->currentItem());
QListWidgetItem *itemN = list->currentItem()->clone();
list->insertItem(index + 2, itemN);
list->setItemWidget(itemN, row);
list->takeItem(index);
list->setCurrentRow(index + 1);
return true;
}
QMetaObject::Connection AdvSceneSwitcher::PulseWidget(QWidget *widget,
QColor endColor,
QColor startColor,
QString specifier)
{
if (switcher->disableHints) {
return QMetaObject::Connection();
}
widget->setStyleSheet(specifier + "{ \
border-style: outset; \
border-width: 2px; \
border-radius: 10px; \
border-color: rgb(0,0,0,0) \
}");
QGraphicsColorizeEffect *eEffect = new QGraphicsColorizeEffect(widget);
widget->setGraphicsEffect(eEffect);
QPropertyAnimation *paAnimation =
new QPropertyAnimation(eEffect, "color");
paAnimation->setStartValue(startColor);
paAnimation->setEndValue(endColor);
paAnimation->setDuration(1000);
// Play backwards to return to original state on timer end
paAnimation->setDirection(QAbstractAnimation::Backward);
auto con = QWidget::connect(
paAnimation, &QPropertyAnimation::finished, [paAnimation]() {
QTimer::singleShot(1000, [paAnimation] {
paAnimation->start();
});
});
paAnimation->start();
return con;
}
/******************************************************************************
* Saving and loading
********************************************************************************/
******************************************************************************/
static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
{
if (saving) {
@@ -107,18 +425,7 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
obs_data_t *obj = obs_data_create();
switcher->saveWindowTitleSwitches(obj);
switcher->saveScreenRegionSwitches(obj);
switcher->savePauseSwitches(obj);
switcher->saveSceneRoundTripSwitches(obj);
switcher->saveSceneTransitions(obj);
switcher->saveIdleSwitches(obj);
switcher->saveExecutableSwitches(obj);
switcher->saveRandomSwitches(obj);
switcher->saveFileSwitches(obj);
switcher->saveMediaSwitches(obj);
switcher->saveTimeSwitches(obj);
switcher->saveGeneralSettings(obj);
switcher->saveSettings(obj);
obs_data_set_obj(save_data, "advanced-scene-switcher", obj);
@@ -129,185 +436,211 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
obs_data_t *obj =
obs_data_get_obj(save_data, "advanced-scene-switcher");
if (!obj)
if (!obj) {
obj = obs_data_create();
}
if (switcher->versionChanged(obj, g_GIT_SHA1)) {
AdvSceneSwitcher::AskBackup(obj);
}
switcher->loadWindowTitleSwitches(obj);
switcher->loadScreenRegionSwitches(obj);
switcher->loadPauseSwitches(obj);
switcher->loadSceneRoundTripSwitches(obj);
switcher->loadSceneTransitions(obj);
switcher->loadIdleSwitches(obj);
switcher->loadExecutableSwitches(obj);
switcher->loadRandomSwitches(obj);
switcher->loadFileSwitches(obj);
switcher->loadMediaSwitches(obj);
switcher->loadTimeSwitches(obj);
switcher->loadGeneralSettings(obj);
switcher->loadSettings(obj);
obs_data_release(obj);
switcher->m.unlock();
if (switcher->stop)
switcher->Stop();
else
// Stop the scene switcher at least once to
// avoid scene duplication issues with scene collection changes
bool start = !switcher->stop;
switcher->Stop();
if (start) {
switcher->Start();
}
}
}
/********************************************************************************
/******************************************************************************
* Main switcher thread
********************************************************************************/
******************************************************************************/
void SwitcherData::Thread()
{
blog(LOG_INFO, "Advanced Scene Switcher started");
//to avoid scene duplication when rapidly switching scene collection
std::this_thread::sleep_for(std::chrono::seconds(2));
blog(LOG_INFO, "started");
int sleep = 0;
int linger = 0;
std::chrono::milliseconds duration;
auto startTime = std::chrono::high_resolution_clock::now();
auto endTime = std::chrono::high_resolution_clock::now();
while (true) {
startLoop:
std::unique_lock<std::mutex> lock(m);
bool match = false;
OBSWeakSource scene;
OBSWeakSource transition;
std::chrono::milliseconds duration;
if (sleep > interval) {
endTime = std::chrono::high_resolution_clock::now();
auto runTime =
std::chrono::duration_cast<std::chrono::milliseconds>(
endTime - startTime);
if (sleep) {
duration = std::chrono::milliseconds(sleep);
if (verbose)
blog(LOG_INFO,
"Advanced Scene Switcher sleep for %d",
sleep);
} else {
duration = std::chrono::milliseconds(interval);
if (verbose)
duration = std::chrono::milliseconds(interval) +
std::chrono::milliseconds(linger) - runTime;
if (duration.count() < 1) {
blog(LOG_INFO,
"Advanced Scene Switcher sleep for %d",
interval);
"detected busy loop - refusing to sleep less than 1ms");
duration = std::chrono::milliseconds(50);
}
}
sleep = 0;
switcher->Prune();
writeSceneInfoToFile();
//sleep for a bit
vblog(LOG_INFO, "try to sleep for %ld", duration.count());
cv.wait_for(lock, duration);
startTime = std::chrono::high_resolution_clock::now();
sleep = 0;
linger = 0;
switcher->Prune();
if (switcher->stop) {
break;
}
setDefaultSceneTransitions();
if (autoStopEnable) {
autoStopStreamAndRecording();
}
if (checkPause()) {
continue;
}
match = checkForMatch(scene, transition, linger);
if (switcher->stop) {
break;
}
checkNoMatchSwitch(match, scene, transition, sleep);
checkSwitchCooldown(match);
for (int switchFuncName : functionNamesByPriority) {
switch (switchFuncName) {
case READ_FILE_FUNC:
checkSwitchInfoFromFile(match, scene,
transition);
checkFileContent(match, scene, transition);
break;
case IDLE_FUNC:
checkIdleSwitch(match, scene, transition);
break;
case EXE_FUNC:
checkExeSwitch(match, scene, transition);
break;
if (linger) {
duration = std::chrono::milliseconds(linger);
vblog(LOG_INFO, "sleep for %ld before switching scene",
duration.count());
case SCREEN_REGION_FUNC:
checkScreenRegionSwitch(match, scene,
transition);
break;
case WINDOW_TITLE_FUNC:
checkWindowTitleSwitch(match, scene,
transition);
break;
case ROUND_TRIP_FUNC:
checkSceneRoundTrip(match, scene, transition,
lock);
if (sceneChangedDuringWait()) //scene might have changed during the sleep
{
goto startLoop;
}
break;
case MEDIA_FUNC:
checkMediaSwitch(match, scene, transition);
break;
case TIME_FUNC:
checkTimeSwitch(match, scene, transition);
break;
}
cv.wait_for(lock, duration);
if (switcher->stop) {
goto endLoop;
}
if (match) {
break;
}
if (sceneChangedDuringWait()) {
vblog(LOG_INFO,
"scene was changed manually - ignoring match");
match = false;
linger = 0;
}
}
if (!match && switchIfNotMatching == SWITCH &&
nonMatchingScene) {
match = true;
scene = nonMatchingScene;
transition = nullptr;
}
if (!match && switchIfNotMatching == RANDOM_SWITCH) {
checkRandom(match, scene, transition, sleep);
}
// After this point we will call frontend functions
// obs_frontend_set_current_scene() and
// obs_frontend_set_current_transition()
//
// During this time SaveSceneSwitcher() could be called
// leading to a deadlock, so we have to unlock()
lock.unlock();
if (match) {
switchScene(scene, transition,
tansitionOverrideOverride, lock);
tansitionOverrideOverride);
}
writeSceneInfoToFile();
}
blog(LOG_INFO, "stopped");
}
bool SwitcherData::checkForMatch(OBSWeakSource &scene,
OBSWeakSource &transition, int &linger)
{
bool match = false;
if (uninterruptibleSceneSequenceActive) {
checkSceneSequence(match, scene, transition, linger);
if (match) {
return match;
}
}
endLoop:
blog(LOG_INFO, "Advanced Scene Switcher stopped");
for (int switchFuncName : functionNamesByPriority) {
switch (switchFuncName) {
case read_file_func:
checkSwitchInfoFromFile(match, scene, transition);
checkFileContent(match, scene, transition);
break;
case idle_func:
checkIdleSwitch(match, scene, transition);
break;
case exe_func:
checkExeSwitch(match, scene, transition);
break;
case screen_region_func:
checkScreenRegionSwitch(match, scene, transition);
break;
case window_title_func:
checkWindowTitleSwitch(match, scene, transition);
break;
case round_trip_func:
checkSceneSequence(match, scene, transition, linger);
break;
case media_func:
checkMediaSwitch(match, scene, transition);
break;
case time_func:
checkTimeSwitch(match, scene, transition);
break;
case audio_func:
checkAudioSwitch(match, scene, transition);
break;
case video_func:
checkVideoSwitch(match, scene, transition);
break;
}
if (switcher->stop) {
return false;
}
if (match) {
break;
}
}
return match;
}
void switchScene(OBSWeakSource &scene, OBSWeakSource &transition,
bool &transitionOverrideOverride,
std::unique_lock<std::mutex> &lock)
bool &transitionOverrideOverride)
{
if (!scene && switcher->verbose) {
blog(LOG_INFO, "nothing to switch to");
return;
}
obs_source_t *source = obs_weak_source_get_source(scene);
obs_source_t *currentSource = obs_frontend_get_current_scene();
if (source && source != currentSource) {
// this call might block when OBS_FRONTEND_EVENT_SCENE_CHANGED is active and mutex is held
// thus unlock mutex to avoid deadlock
lock.unlock();
transitionData td;
setNextTransition(scene, currentSource, transition,
transitionOverrideOverride, td);
obs_frontend_set_current_scene(source);
if (transitionOverrideOverride)
if (transitionOverrideOverride) {
restoreTransitionOverride(source, td);
lock.lock();
}
if (switcher->verbose)
blog(LOG_INFO,
"Advanced Scene Switcher switched scene");
if (switcher->verbose) {
blog(LOG_INFO, "switched scene");
}
}
obs_source_release(currentSource);
obs_source_release(source);
}
bool SwitcherData::sceneChangedDuringWait()
{
bool r = false;
obs_source_t *currentSource = obs_frontend_get_current_scene();
if (!currentSource)
return true;
obs_source_release(currentSource);
if (waitScene && currentSource != waitScene)
r = true;
return r;
}
void SwitcherData::Start()
{
if (!(th && th->isRunning())) {
@@ -315,6 +648,9 @@ void SwitcherData::Start()
switcher->th = new SwitcherThread();
switcher->th->start(
(QThread::Priority)switcher->threadPriority);
// Will be overwritten quickly but might be useful
writeToStatusFile("Advanced Scene Switcher running");
}
}
@@ -327,36 +663,86 @@ void SwitcherData::Stop()
th->wait();
delete th;
th = nullptr;
writeToStatusFile("Advanced Scene Switcher stopped");
}
}
/********************************************************************************
bool SwitcherData::sceneChangedDuringWait()
{
obs_source_t *currentSource = obs_frontend_get_current_scene();
if (!currentSource) {
return true;
}
obs_source_release(currentSource);
return (waitScene && currentSource != waitScene);
}
/******************************************************************************
* OBS module setup
********************************************************************************/
******************************************************************************/
extern "C" void FreeSceneSwitcher()
{
if (loaded_curl_lib) {
if (switcher->curl && f_curl_cleanup) {
f_curl_cleanup(switcher->curl);
}
delete loaded_curl_lib;
loaded_curl_lib = nullptr;
}
delete switcher;
switcher = nullptr;
}
void handleSceneChange(SwitcherData *s)
{
std::lock_guard<std::mutex> lock(s->m);
//stop waiting if scene was manually changed
if (s->sceneChangedDuringWait())
// Stop waiting if scene was changed
if (s->sceneChangedDuringWait()) {
s->cv.notify_one();
}
//set previous scene
// Set previous scene
obs_source_t *source = obs_frontend_get_current_scene();
obs_weak_source_t *ws = obs_source_get_weak_source(source);
obs_source_release(source);
obs_weak_source_release(ws);
if (source && s->PreviousScene2 != ws) {
s->previousScene = s->PreviousScene2;
s->PreviousScene2 = ws;
if (source && s->previousSceneHelper != ws) {
s->previousScene = s->previousSceneHelper;
s->previousSceneHelper = ws;
}
s->checkTriggers();
s->checkDefaultSceneTransitions();
}
void setLiveTime(SwitcherData *s)
{
s->liveTime = QDateTime::currentDateTime();
}
void resetLiveTime(SwitcherData *s)
{
s->liveTime = QDateTime();
}
void checkAutoStartRecording(SwitcherData *s)
{
if (s->autoStartEvent == AutoStartEvent::RECORDING ||
s->autoStartEvent == AutoStartEvent::RECORINDG_OR_STREAMING)
s->Start();
}
void checkAutoStartStreaming(SwitcherData *s)
{
if (s->autoStartEvent == AutoStartEvent::STREAMING ||
s->autoStartEvent == AutoStartEvent::RECORINDG_OR_STREAMING)
s->Start();
}
// Note to future self:
// be careful using switcher->m here as there is potential for deadlocks when using
// frontend functions such as obs_frontend_set_current_scene()
static void OBSEvent(enum obs_frontend_event event, void *switcher)
{
switch (event) {
@@ -366,6 +752,18 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
handleSceneChange((SwitcherData *)switcher);
break;
case OBS_FRONTEND_EVENT_RECORDING_STARTED:
setLiveTime((SwitcherData *)switcher);
checkAutoStartRecording((SwitcherData *)switcher);
break;
case OBS_FRONTEND_EVENT_STREAMING_STARTED:
setLiveTime((SwitcherData *)switcher);
checkAutoStartStreaming((SwitcherData *)switcher);
break;
case OBS_FRONTEND_EVENT_RECORDING_STOPPED:
case OBS_FRONTEND_EVENT_STREAMING_STOPPED:
resetLiveTime((SwitcherData *)switcher);
break;
default:
break;
}
@@ -373,16 +771,22 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
extern "C" void InitSceneSwitcher()
{
blog(LOG_INFO, "version: %s", g_GIT_SHA1);
QAction *action = (QAction *)obs_frontend_add_tools_menu_qaction(
"Advanced Scene Switcher");
obs_module_text("AdvSceneSwitcher.pluginName"));
switcher = new SwitcherData;
if (loadCurl() && f_curl_init) {
switcher->curl = f_curl_init();
}
auto cb = []() {
QMainWindow *window =
(QMainWindow *)obs_frontend_get_main_window();
SceneSwitcher ss(window);
AdvSceneSwitcher ss(window);
ss.exec();
};
@@ -390,25 +794,4 @@ extern "C" void InitSceneSwitcher()
obs_frontend_add_event_callback(OBSEvent, switcher);
action->connect(action, &QAction::triggered, cb);
char *path = obs_module_config_path("");
QDir dir(path);
if (!dir.exists()) {
dir.mkpath(".");
}
bfree(path);
obs_hotkey_id toggleHotkeyId = obs_hotkey_register_frontend(
"startStopToggleSwitcherHotkey",
"Toggle Start/Stop for the Advanced Scene Switcher",
startStopToggleHotkeyFunc, NULL);
loadKeybinding(toggleHotkeyId, TOGGLE_HOTKEY_PATH);
obs_hotkey_id startHotkeyId = obs_hotkey_register_frontend(
"startSwitcherHotkey", "Start the Advanced Scene Switcher",
startHotkeyFunc, NULL);
loadKeybinding(startHotkeyId, START_HOTKEY_PATH);
obs_hotkey_id stopHotkeyId = obs_hotkey_register_frontend(
"stopSwitcherHotkey", "Stop the Advanced Scene Switcher",
stopHotkeyFunc, NULL);
loadKeybinding(stopHotkeyId, STOP_HOTKEY_PATH);
}

68
src/curl-helper.cpp Normal file
View File

@@ -0,0 +1,68 @@
#include <QDir>
#include <QFileInfo>
#include <curl/curl.h>
#include <obs.hpp>
#include "headers/curl-helper.hpp"
initFunction f_curl_init = nullptr;
setOptFunction f_curl_setopt = nullptr;
performFunction f_curl_perform = nullptr;
cleanupFunction f_curl_cleanup = nullptr;
QLibrary *loaded_curl_lib = nullptr;
bool resolveCurl()
{
f_curl_init = (initFunction)loaded_curl_lib->resolve("curl_easy_init");
f_curl_setopt =
(setOptFunction)loaded_curl_lib->resolve("curl_easy_setopt");
f_curl_perform =
(performFunction)loaded_curl_lib->resolve("curl_easy_perform");
f_curl_cleanup =
(cleanupFunction)loaded_curl_lib->resolve("curl_easy_cleanup");
if (f_curl_init && f_curl_setopt && f_curl_perform && f_curl_cleanup) {
blog(LOG_INFO, "[adv-ss] curl loaded successfully");
return true;
}
blog(LOG_INFO, "[adv-ss] curl symbols not resolved");
return false;
}
bool loadCurl()
{
QStringList locations;
locations << QDir::currentPath();
#if defined(__linux__) || defined(__APPLE__)
locations << "/usr/lib";
locations << "/usr/local/lib";
locations << "/usr/lib/x86_64-linux-gnu";
locations << "/usr/local/opt/curl/lib";
#endif
for (QString path : locations) {
blog(LOG_INFO, "[adv-ss] trying '%s'",
path.toUtf8().constData());
QFileInfo libPath(
QDir(path).absoluteFilePath(curl_library_name));
if (libPath.exists() && libPath.isFile()) {
QString libFilePath = libPath.absoluteFilePath();
blog(LOG_INFO, "[adv-ss] found curl library at '%s'",
libFilePath.toUtf8().constData());
loaded_curl_lib = new QLibrary(libFilePath, nullptr);
if (resolveCurl()) {
return true;
} else {
delete loaded_curl_lib;
loaded_curl_lib = nullptr;
}
}
}
blog(LOG_WARNING, "[adv-ss] can't find the curl library");
return false;
}

View File

@@ -1,280 +0,0 @@
#include "headers/advanced-scene-switcher.hpp"
int SceneSwitcher::executableFindByData(const QString &exe)
{
int count = ui->executables->count();
for (int i = 0; i < count; i++) {
QListWidgetItem *item = ui->executables->item(i);
QString itemExe = item->data(Qt::UserRole).toString();
if (itemExe == exe)
return i;
}
return -1;
}
void SceneSwitcher::on_executables_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
return;
QListWidgetItem *item = ui->executables->item(idx);
QString exec = item->data(Qt::UserRole).toString();
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->executableSwitches) {
if (exec.compare(s.mExe) == 0) {
QString sceneName = GetWeakSourceName(s.mScene).c_str();
QString transitionName =
GetWeakSourceName(s.mTransition).c_str();
ui->executableScenes->setCurrentText(sceneName);
ui->executable->setCurrentText(exec);
ui->executableTransitions->setCurrentText(
transitionName);
ui->requiresFocusCheckBox->setChecked(s.mInFocus);
break;
}
}
}
void SceneSwitcher::on_executableUp_clicked()
{
int index = ui->executables->currentRow();
if (index != -1 && index != 0) {
ui->executables->insertItem(index - 1,
ui->executables->takeItem(index));
ui->executables->setCurrentRow(index - 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->executableSwitches.begin() + index,
switcher->executableSwitches.begin() + index - 1);
}
}
void SceneSwitcher::on_executableDown_clicked()
{
int index = ui->executables->currentRow();
if (index != -1 && index != ui->executables->count() - 1) {
ui->executables->insertItem(index + 1,
ui->executables->takeItem(index));
ui->executables->setCurrentRow(index + 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->executableSwitches.begin() + index,
switcher->executableSwitches.begin() + index + 1);
}
}
void SceneSwitcher::on_executableAdd_clicked()
{
QString sceneName = ui->executableScenes->currentText();
QString exeName = ui->executable->currentText();
QString transitionName = ui->executableTransitions->currentText();
bool inFocus = ui->requiresFocusCheckBox->isChecked();
if (exeName.isEmpty() || sceneName.isEmpty())
return;
OBSWeakSource source = GetWeakSourceByQString(sceneName);
OBSWeakSource transition = GetWeakTransitionByQString(transitionName);
QVariant v = QVariant::fromValue(exeName);
QString text = MakeSwitchNameExecutable(sceneName, exeName,
transitionName, inFocus);
int idx = executableFindByData(exeName);
if (idx == -1) {
std::lock_guard<std::mutex> lock(switcher->m);
switcher->executableSwitches.emplace_back(
source, transition, exeName.toUtf8().constData(),
inFocus);
QListWidgetItem *item =
new QListWidgetItem(text, ui->executables);
item->setData(Qt::UserRole, v);
} else {
QListWidgetItem *item = ui->executables->item(idx);
item->setText(text);
{
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->executableSwitches) {
if (s.mExe == exeName) {
s.mScene = source;
s.mTransition = transition;
s.mInFocus = inFocus;
break;
}
}
}
}
}
void SceneSwitcher::on_executableRemove_clicked()
{
QListWidgetItem *item = ui->executables->currentItem();
if (!item)
return;
QString exe = item->data(Qt::UserRole).toString();
{
std::lock_guard<std::mutex> lock(switcher->m);
auto &switches = switcher->executableSwitches;
for (auto it = switches.begin(); it != switches.end(); ++it) {
auto &s = *it;
if (s.mExe == exe) {
switches.erase(it);
break;
}
}
}
delete item;
}
void SwitcherData::checkExeSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
std::string title;
QStringList runningProcesses;
bool ignored = false;
// Check if current window is ignored
GetCurrentWindowTitle(title);
for (auto &window : ignoreWindowsSwitches) {
// True if ignored switch equals title
bool equals = (title == window);
// True if ignored switch matches title
bool matches = QString::fromStdString(title).contains(
QRegularExpression(QString::fromStdString(window)));
if (equals || matches) {
ignored = true;
title = lastTitle;
break;
}
}
lastTitle = title;
// Check for match
GetProcessList(runningProcesses);
for (ExecutableSceneSwitch &s : executableSwitches) {
// True if executable switch is running (direct)
bool equals = runningProcesses.contains(s.mExe);
// True if executable switch is running (regex)
bool matches = (runningProcesses.indexOf(
QRegularExpression(s.mExe)) != -1);
// True if focus is disabled OR switch is focused
bool focus = (!s.mInFocus || isInFocus(s.mExe));
// True if current window is ignored AND switch equals OR matches last window
bool ignore =
(ignored && (title == s.mExe.toStdString() ||
QString::fromStdString(title).contains(
QRegularExpression(s.mExe))));
if ((equals || matches) && (focus || ignore)) {
match = true;
scene = s.mScene;
transition = s.mTransition;
if (verbose)
blog(LOG_INFO,
"Advanced Scene Switcher exec match");
break;
}
}
}
void SwitcherData::saveExecutableSwitches(obs_data_t *obj)
{
obs_data_array_t *executableArray = obs_data_array_create();
for (ExecutableSceneSwitch &s : switcher->executableSwitches) {
obs_data_t *array_obj = obs_data_create();
obs_source_t *source = obs_weak_source_get_source(s.mScene);
obs_source_t *transition =
obs_weak_source_get_source(s.mTransition);
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_set_string(array_obj, "exefile",
s.mExe.toUtf8());
obs_data_set_bool(array_obj, "infocus", s.mInFocus);
obs_data_array_push_back(executableArray, array_obj);
obs_source_release(source);
obs_source_release(transition);
}
obs_data_release(array_obj);
}
obs_data_set_array(obj, "executableSwitches", executableArray);
obs_data_array_release(executableArray);
}
void SwitcherData::loadExecutableSwitches(obs_data_t *obj)
{
switcher->executableSwitches.clear();
obs_data_array_t *executableArray =
obs_data_get_array(obj, "executableSwitches");
size_t count = obs_data_array_count(executableArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(executableArray, i);
const char *scene = obs_data_get_string(array_obj, "scene");
const char *transition =
obs_data_get_string(array_obj, "transition");
const char *exe = obs_data_get_string(array_obj, "exefile");
bool infocus = obs_data_get_bool(array_obj, "infocus");
switcher->executableSwitches.emplace_back(
GetWeakSourceByName(scene),
GetWeakTransitionByName(transition), exe, infocus);
obs_data_release(array_obj);
}
obs_data_array_release(executableArray);
}
void SceneSwitcher::setupExecutableTab()
{
populateSceneSelection(ui->executableScenes, false);
populateTransitionSelection(ui->executableTransitions);
QStringList processes;
GetProcessList(processes);
for (QString &process : processes)
ui->executable->addItem(process);
for (auto &s : switcher->executableSwitches) {
std::string sceneName = GetWeakSourceName(s.mScene);
std::string transitionName = GetWeakSourceName(s.mTransition);
QString text = MakeSwitchNameExecutable(sceneName.c_str(),
s.mExe,
transitionName.c_str(),
s.mInFocus);
QListWidgetItem *item =
new QListWidgetItem(text, ui->executables);
item->setData(Qt::UserRole, s.mExe);
}
}

View File

@@ -1,475 +0,0 @@
#include <QFileDialog>
#include <QTextStream>
#include <QDateTime>
#include <obs.hpp>
#include <curl/curl.h>
#include "headers/advanced-scene-switcher.hpp"
#define LOCAL_FILE_IDX 0
#define REMOTE_FILE_IDX 1
void SceneSwitcher::on_browseButton_clicked()
{
QString path = QFileDialog::getOpenFileName(
this, tr("Select a file to write to ..."), QDir::currentPath(),
tr("Text files (*.txt)"));
if (!path.isEmpty())
ui->writePathLineEdit->setText(path);
}
void SceneSwitcher::on_readFileCheckBox_stateChanged(int state)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
if (!state) {
ui->browseButton_2->setDisabled(true);
ui->readPathLineEdit->setDisabled(true);
switcher->fileIO.readEnabled = false;
} else {
ui->browseButton_2->setDisabled(false);
ui->readPathLineEdit->setDisabled(false);
switcher->fileIO.readEnabled = true;
}
}
void SceneSwitcher::on_readPathLineEdit_textChanged(const QString &text)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
if (text.isEmpty()) {
switcher->fileIO.readEnabled = false;
return;
}
switcher->fileIO.readEnabled = true;
switcher->fileIO.readPath = text.toUtf8().constData();
}
void SceneSwitcher::on_writePathLineEdit_textChanged(const QString &text)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
if (text.isEmpty()) {
switcher->fileIO.writeEnabled = false;
return;
}
switcher->fileIO.writeEnabled = true;
switcher->fileIO.writePath = text.toUtf8().constData();
}
void SceneSwitcher::on_browseButton_2_clicked()
{
QString path = QFileDialog::getOpenFileName(
this, tr("Select a file to read from ..."), QDir::currentPath(),
tr("Any files (*.*)"));
if (!path.isEmpty())
ui->readPathLineEdit->setText(path);
}
void SwitcherData::writeSceneInfoToFile()
{
if (!fileIO.writeEnabled || fileIO.writePath.empty())
return;
obs_source_t *currentSource = obs_frontend_get_current_scene();
QFile file(QString::fromStdString(fileIO.writePath));
if (file.open(QIODevice::WriteOnly)) {
const char *msg = obs_source_get_name(currentSource);
file.write(msg, qstrlen(msg));
file.close();
}
obs_source_release(currentSource);
}
void SwitcherData::checkSwitchInfoFromFile(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (!fileIO.readEnabled || fileIO.readPath.empty())
return;
QFile file(QString::fromStdString(fileIO.readPath));
if (file.open(QIODevice::ReadOnly)) {
QTextStream in(&file);
QString sceneStr = in.readLine();
obs_source_t *sceneRead =
obs_get_source_by_name(sceneStr.toUtf8().constData());
if (sceneRead) {
obs_weak_source_t *sceneReadWs =
obs_source_get_weak_source(sceneRead);
match = true;
scene = sceneReadWs;
transition = nullptr;
obs_weak_source_release(sceneReadWs);
obs_source_release(sceneRead);
}
file.close();
}
}
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;
}
std::string getRemoteData(std::string &url)
{
CURL *curl;
CURLcode res;
std::string readBuffer;
curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
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 (QString::compare(s1s, s2s, Qt::CaseSensitive) != 0) {
return false;
}
}
return true;
}
bool checkRemoteFileContent(FileSwitch &s)
{
std::string data = getRemoteData(s.file);
bool ret = false;
if (s.useRegex) {
ret = QString::fromStdString(data).contains(
QRegularExpression(QString::fromStdString(s.text)));
return ret;
}
QString t = QString::fromStdString(s.text);
QString d = QString::fromStdString(data);
return compareIgnoringLineEnding(t, d);
}
bool checkLocalFileContent(FileSwitch &s)
{
bool equal = false;
QString t = QString::fromStdString(s.text);
QFile file(QString::fromStdString(s.file));
if (!file.open(QIODevice::ReadOnly))
return false;
if (s.useTime) {
QDateTime newLastMod = QFileInfo(file).lastModified();
if (s.lastMod == newLastMod)
return false;
s.lastMod = newLastMod;
}
if (s.useRegex) {
QTextStream in(&file);
QRegExp rx(t);
equal = rx.exactMatch(in.readAll());
} else {
QTextStream in(&file);
QString filedata = in.readAll();
equal = compareIgnoringLineEnding(filedata, t);
}
file.close();
return equal;
}
void SwitcherData::checkFileContent(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
for (FileSwitch &s : fileSwitches) {
bool equal = false;
if (s.remote) {
equal = checkRemoteFileContent(s);
} else {
equal = checkLocalFileContent(s);
}
if (equal) {
scene = s.scene;
transition = s.transition;
match = true;
if (verbose)
blog(LOG_INFO,
"Advanced Scene Switcher file match");
break;
}
}
}
void SceneSwitcher::on_browseButton_3_clicked()
{
QString path = QFileDialog::getOpenFileName(
this, tr("Select a file to read from ..."), QDir::currentPath(),
tr("Any files (*.*)"));
if (!path.isEmpty())
ui->filePathLineEdit->setText(path);
}
void SceneSwitcher::on_fileType_currentIndexChanged(int idx)
{
if (idx == -1)
return;
if (idx == LOCAL_FILE_IDX) {
ui->browseButton_3->setDisabled(false);
ui->fileContentTimeCheckBox->setDisabled(false);
ui->remoteFileWarningLabel->hide();
}
if (idx == REMOTE_FILE_IDX) {
ui->browseButton_3->setDisabled(true);
ui->fileContentTimeCheckBox->setDisabled(true);
ui->remoteFileWarningLabel->show();
}
}
void SceneSwitcher::on_fileAdd_clicked()
{
QString sceneName = ui->fileScenes->currentText();
QString transitionName = ui->fileTransitions->currentText();
QString fileName = ui->filePathLineEdit->text();
QString text = ui->fileTextEdit->toPlainText();
bool remote = (ui->fileType->currentIndex() == REMOTE_FILE_IDX);
bool useRegex = ui->fileContentRegExCheckBox->isChecked();
bool useTime = ui->fileContentTimeCheckBox->isChecked();
if (sceneName.isEmpty() || transitionName.isEmpty() ||
fileName.isEmpty() || text.isEmpty())
return;
OBSWeakSource source = GetWeakSourceByQString(sceneName);
OBSWeakSource transition = GetWeakTransitionByQString(transitionName);
QString switchText = MakeFileSwitchName(
sceneName, transitionName, fileName, text, useRegex, useTime);
QVariant v = QVariant::fromValue(switchText);
QListWidgetItem *item =
new QListWidgetItem(switchText, ui->fileScenesList);
item->setData(Qt::UserRole, v);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->fileSwitches.emplace_back(source, transition,
fileName.toUtf8().constData(),
text.toUtf8().constData(), remote,
useRegex, useTime);
}
void SceneSwitcher::on_fileRemove_clicked()
{
QListWidgetItem *item = ui->fileScenesList->currentItem();
if (!item)
return;
int idx = ui->fileScenesList->currentRow();
if (idx == -1)
return;
{
std::lock_guard<std::mutex> lock(switcher->m);
auto &switches = switcher->fileSwitches;
switches.erase(switches.begin() + idx);
}
qDeleteAll(ui->fileScenesList->selectedItems());
}
void SceneSwitcher::on_fileScenesList_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
return;
std::lock_guard<std::mutex> lock(switcher->m);
if (switcher->fileSwitches.size() <= idx)
return;
FileSwitch s = switcher->fileSwitches[idx];
std::string sceneName = GetWeakSourceName(s.scene);
std::string transitionName = GetWeakSourceName(s.transition);
ui->fileScenes->setCurrentText(sceneName.c_str());
ui->fileTransitions->setCurrentText(transitionName.c_str());
ui->fileTextEdit->setPlainText(s.text.c_str());
ui->filePathLineEdit->setText(s.file.c_str());
if (s.remote)
ui->fileType->setCurrentIndex(REMOTE_FILE_IDX);
else
ui->fileType->setCurrentIndex(LOCAL_FILE_IDX);
ui->fileContentRegExCheckBox->setChecked(s.useRegex);
ui->fileContentTimeCheckBox->setChecked(s.useTime);
}
void SceneSwitcher::on_fileUp_clicked()
{
int index = ui->fileScenesList->currentRow();
if (index != -1 && index != 0) {
ui->fileScenesList->insertItem(
index - 1, ui->fileScenesList->takeItem(index));
ui->fileScenesList->setCurrentRow(index - 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->fileSwitches.begin() + index,
switcher->fileSwitches.begin() + index - 1);
}
}
void SceneSwitcher::on_fileDown_clicked()
{
int index = ui->fileScenesList->currentRow();
if (index != -1 && index != ui->fileScenesList->count() - 1) {
ui->fileScenesList->insertItem(
index + 1, ui->fileScenesList->takeItem(index));
ui->fileScenesList->setCurrentRow(index + 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->fileSwitches.begin() + index,
switcher->fileSwitches.begin() + index + 1);
}
}
void SwitcherData::saveFileSwitches(obs_data_t *obj)
{
obs_data_array_t *fileArray = obs_data_array_create();
for (FileSwitch &s : switcher->fileSwitches) {
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_set_string(array_obj, "file", s.file.c_str());
obs_data_set_string(array_obj, "text", s.text.c_str());
obs_data_set_bool(array_obj, "remote", s.remote);
obs_data_set_bool(array_obj, "useRegex", s.useRegex);
obs_data_set_bool(array_obj, "useTime", s.useTime);
obs_data_array_push_back(fileArray, array_obj);
obs_source_release(source);
obs_source_release(transition);
}
obs_data_release(array_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());
}
void SwitcherData::loadFileSwitches(obs_data_t *obj)
{
switcher->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);
const char *scene = obs_data_get_string(array_obj, "scene");
const char *transition =
obs_data_get_string(array_obj, "transition");
const char *file = obs_data_get_string(array_obj, "file");
const char *text = obs_data_get_string(array_obj, "text");
bool remote = obs_data_get_bool(array_obj, "remote");
bool useRegex = obs_data_get_bool(array_obj, "useRegex");
bool useTime = obs_data_get_bool(array_obj, "useTime");
switcher->fileSwitches.emplace_back(
GetWeakSourceByName(scene),
GetWeakTransitionByName(transition), file, text, remote,
useRegex, useTime);
obs_data_release(array_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");
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");
}
void SceneSwitcher::setupFileTab()
{
populateSceneSelection(ui->fileScenes, false);
populateTransitionSelection(ui->fileTransitions);
ui->fileType->addItem("local");
ui->fileType->addItem("remote");
ui->remoteFileWarningLabel->setText(
"Note that the scene switcher will try to access the remote location every " +
QString::number(switcher->interval) + "ms");
for (auto &s : switcher->fileSwitches) {
std::string sceneName = GetWeakSourceName(s.scene);
std::string transitionName = GetWeakSourceName(s.transition);
QString listText = MakeFileSwitchName(
sceneName.c_str(), transitionName.c_str(),
s.file.c_str(), s.text.c_str(), s.useRegex, s.useTime);
QListWidgetItem *item =
new QListWidgetItem(listText, ui->fileScenesList);
item->setData(Qt::UserRole, listText);
}
ui->readPathLineEdit->setText(
QString::fromStdString(switcher->fileIO.readPath.c_str()));
ui->readFileCheckBox->setChecked(switcher->fileIO.readEnabled);
ui->writePathLineEdit->setText(
QString::fromStdString(switcher->fileIO.writePath.c_str()));
if (ui->readFileCheckBox->checkState()) {
ui->browseButton_2->setDisabled(false);
ui->readPathLineEdit->setDisabled(false);
} else {
ui->browseButton_2->setDisabled(true);
ui->readPathLineEdit->setDisabled(true);
}
}

View File

@@ -1,16 +1,19 @@
#include <obs-module.h>
#include <QFileDialog>
#include <QTextStream>
#include <QMessageBox>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
#include "headers/version.h"
void SceneSwitcher::on_close_clicked()
constexpr auto tab_count = 16;
QMetaObject::Connection inactivePluse;
void AdvSceneSwitcher::on_close_clicked()
{
done(0);
}
void SceneSwitcher::UpdateNonMatchingScene(const QString &name)
void AdvSceneSwitcher::UpdateNonMatchingScene(const QString &name)
{
obs_source_t *scene = obs_get_source_by_name(name.toUtf8().constData());
obs_weak_source_t *ws = obs_source_get_weak_source(scene);
@@ -21,77 +24,126 @@ void SceneSwitcher::UpdateNonMatchingScene(const QString &name)
obs_source_release(scene);
}
void SceneSwitcher::on_noMatchDontSwitch_clicked()
void AdvSceneSwitcher::on_noMatchDontSwitch_clicked()
{
if (loading)
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->switchIfNotMatching = NO_SWITCH;
ui->noMatchSwitchScene->setEnabled(false);
ui->randomDisabledWarning->setVisible(true);
}
void SceneSwitcher::on_noMatchSwitch_clicked()
void AdvSceneSwitcher::on_noMatchSwitch_clicked()
{
if (loading)
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->switchIfNotMatching = SWITCH;
ui->noMatchSwitchScene->setEnabled(true);
UpdateNonMatchingScene(ui->noMatchSwitchScene->currentText());
ui->randomDisabledWarning->setVisible(true);
}
void SceneSwitcher::on_noMatchRandomSwitch_clicked()
void AdvSceneSwitcher::on_noMatchRandomSwitch_clicked()
{
if (loading)
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->switchIfNotMatching = RANDOM_SWITCH;
ui->noMatchSwitchScene->setEnabled(false);
ui->randomDisabledWarning->setVisible(false);
}
void SceneSwitcher::on_startupBehavior_currentIndexChanged(int index)
void AdvSceneSwitcher::on_noMatchDelay_valueChanged(double i)
{
if (loading)
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->noMatchDelay = i;
}
void AdvSceneSwitcher::on_startupBehavior_currentIndexChanged(int index)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->startupBehavior = (StartupBehavior)index;
}
void SceneSwitcher::on_noMatchSwitchScene_currentTextChanged(const QString &text)
void AdvSceneSwitcher::on_autoStartEvent_currentIndexChanged(int index)
{
if (loading)
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->autoStartEvent = static_cast<AutoStartEvent>(index);
}
void AdvSceneSwitcher::on_noMatchSwitchScene_currentTextChanged(
const QString &text)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
UpdateNonMatchingScene(text);
}
void SceneSwitcher::on_checkInterval_valueChanged(int value)
void AdvSceneSwitcher::on_cooldownTime_valueChanged(double i)
{
if (loading)
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->cooldown = i;
}
void AdvSceneSwitcher::on_checkInterval_valueChanged(int value)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->interval = value;
}
void SceneSwitcher::SetStarted()
void AdvSceneSwitcher::SetStarted()
{
ui->toggleStartButton->setText("Stop");
ui->pluginRunningText->setText("Active");
ui->toggleStartButton->setText(
obs_module_text("AdvSceneSwitcher.generalTab.status.stop"));
ui->pluginRunningText->setText(
obs_module_text("AdvSceneSwitcher.status.active"));
ui->pluginRunningText->disconnect(inactivePluse);
currentStatusActive = true;
}
void SceneSwitcher::SetStopped()
void AdvSceneSwitcher::SetStopped()
{
ui->toggleStartButton->setText("Start");
ui->pluginRunningText->setText("Inactive");
ui->toggleStartButton->setText(
obs_module_text("AdvSceneSwitcher.generalTab.status.start"));
ui->pluginRunningText->setText(
obs_module_text("AdvSceneSwitcher.status.inactive"));
inactivePluse = PulseWidget(ui->pluginRunningText, QColor(Qt::red),
QColor(0, 0, 0, 0), "QLabel ");
currentStatusActive = false;
}
void SceneSwitcher::on_toggleStartButton_clicked()
void AdvSceneSwitcher::on_toggleStartButton_clicked()
{
if (switcher->th && switcher->th->isRunning()) {
switcher->Stop();
@@ -102,229 +154,283 @@ void SceneSwitcher::on_toggleStartButton_clicked()
}
}
void SceneSwitcher::closeEvent(QCloseEvent *)
void AdvSceneSwitcher::closeEvent(QCloseEvent *)
{
obs_frontend_save();
}
void SceneSwitcher::on_autoStopScenes_currentTextChanged(const QString &text)
void AdvSceneSwitcher::on_verboseLogging_stateChanged(int state)
{
if (loading)
if (loading) {
return;
std::lock_guard<std::mutex> lock(switcher->m);
UpdateAutoStopScene(text);
}
void SceneSwitcher::on_autoStopSceneCheckBox_stateChanged(int state)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
if (!state) {
ui->autoStopScenes->setDisabled(true);
switcher->autoStopEnable = false;
} else {
ui->autoStopScenes->setDisabled(false);
switcher->autoStopEnable = true;
if (!switcher->autoStopScene)
UpdateAutoStopScene(ui->autoStopScenes->currentText());
}
}
void SceneSwitcher::UpdateAutoStopScene(const QString &name)
{
obs_source_t *scene = obs_get_source_by_name(name.toUtf8().constData());
obs_weak_source_t *ws = obs_source_get_weak_source(scene);
switcher->autoStopScene = ws;
obs_weak_source_release(ws);
obs_source_release(scene);
}
void SwitcherData::autoStopStreamAndRecording()
{
obs_source_t *currentSource = obs_frontend_get_current_scene();
obs_weak_source_t *ws = obs_source_get_weak_source(currentSource);
if (ws && autoStopScene == ws) {
if (obs_frontend_streaming_active())
obs_frontend_streaming_stop();
if (obs_frontend_recording_active())
obs_frontend_recording_stop();
}
obs_source_release(currentSource);
obs_weak_source_release(ws);
}
void SceneSwitcher::on_verboseLogging_stateChanged(int state)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
switcher->verbose = state;
}
void SceneSwitcher::on_exportSettings_clicked()
void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
{
QString directory = QFileDialog::getSaveFileName(
this, tr("Export Advanced Scene Switcher settings to file ..."),
QDir::currentPath(), tr("Text files (*.txt)"));
if (directory.isEmpty())
if (loading) {
return;
}
switcher->disableHints = state;
}
void AdvSceneSwitcher::AskBackup(obs_data_t *obj)
{
bool backupSettings = DisplayMessage(
obs_module_text("AdvSceneSwitcher.askBackup"), true);
if (!backupSettings) {
return;
}
QString directory = QFileDialog::getSaveFileName(
nullptr,
obs_module_text(
"AdvSceneSwitcher.generalTab.saveOrLoadsettings.importWindowTitle"),
QDir::currentPath(),
obs_module_text(
"AdvSceneSwitcher.generalTab.saveOrLoadsettings.textType"));
if (directory.isEmpty()) {
return;
}
QFile file(directory);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return;
}
obs_data_save_json(obj, file.fileName().toUtf8().constData());
}
void AdvSceneSwitcher::on_exportSettings_clicked()
{
QString directory = QFileDialog::getSaveFileName(
this,
tr(obs_module_text(
"AdvSceneSwitcher.generalTab.saveOrLoadsettings.exportWindowTitle")),
QDir::currentPath(),
tr(obs_module_text(
"AdvSceneSwitcher.generalTab.saveOrLoadsettings.textType")));
if (directory.isEmpty()) {
return;
}
QFile file(directory);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return;
}
obs_data_t *obj = obs_data_create();
switcher->saveWindowTitleSwitches(obj);
switcher->saveScreenRegionSwitches(obj);
switcher->savePauseSwitches(obj);
switcher->saveSceneRoundTripSwitches(obj);
switcher->saveSceneTransitions(obj);
switcher->saveIdleSwitches(obj);
switcher->saveExecutableSwitches(obj);
switcher->saveRandomSwitches(obj);
switcher->saveFileSwitches(obj);
switcher->saveMediaSwitches(obj);
switcher->saveTimeSwitches(obj);
switcher->saveGeneralSettings(obj);
switcher->saveSettings(obj);
obs_data_save_json(obj, file.fileName().toUtf8().constData());
obs_data_release(obj);
}
void SceneSwitcher::on_importSettings_clicked()
void AdvSceneSwitcher::on_importSettings_clicked()
{
// Scene switcher could be stuck in a sequence
// so it needs to be stopped before importing new settings
bool start = !switcher->stop;
switcher->Stop();
std::lock_guard<std::mutex> lock(switcher->m);
QString directory = QFileDialog::getOpenFileName(
this,
tr("Import Advanced Scene Switcher settings from file ..."),
QDir::currentPath(), tr("Text files (*.txt)"));
if (directory.isEmpty())
tr(obs_module_text(
"AdvSceneSwitcher.generalTab.saveOrLoadsettings.importWindowTitle")),
QDir::currentPath(),
tr(obs_module_text(
"AdvSceneSwitcher.generalTab.saveOrLoadsettings.textType")));
if (directory.isEmpty()) {
return;
}
QFile file(directory);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return;
}
obs_data_t *obj = obs_data_create_from_json_file(
file.fileName().toUtf8().constData());
if (!obj) {
QMessageBox Msgbox;
Msgbox.setText(
"Advanced Scene Switcher failed to import settings");
Msgbox.exec();
(void)DisplayMessage(obs_module_text(
"AdvSceneSwitcher.generalTab.saveOrLoadsettings.loadFail"));
return;
}
switcher->loadWindowTitleSwitches(obj);
switcher->loadScreenRegionSwitches(obj);
switcher->loadPauseSwitches(obj);
switcher->loadSceneRoundTripSwitches(obj);
switcher->loadSceneTransitions(obj);
switcher->loadIdleSwitches(obj);
switcher->loadExecutableSwitches(obj);
switcher->loadRandomSwitches(obj);
switcher->loadFileSwitches(obj);
switcher->loadMediaSwitches(obj);
switcher->loadTimeSwitches(obj);
switcher->loadGeneralSettings(obj);
switcher->loadSettings(obj);
obs_data_release(obj);
QMessageBox Msgbox;
Msgbox.setText(
"Advanced Scene Switcher settings imported successfully");
Msgbox.exec();
(void)DisplayMessage(obs_module_text(
"AdvSceneSwitcher.generalTab.saveOrLoadsettings.loadSuccess"));
close();
// Restart scene switcher if it was active
if (start) {
switcher->Start();
}
}
int findTabIndex(QTabBar *bar, int pos)
int findTabIndex(QTabWidget *tabWidget, int pos)
{
int at = -1;
QString tabName = "";
switch (pos) {
case 0:
tabName = "General";
tabName = "generalTab";
break;
case 1:
tabName = "Transition";
tabName = "transitionsTab";
break;
case 2:
tabName = "Pause";
tabName = "pauseTab";
break;
case 3:
tabName = "Title";
tabName = "windowTitleTab";
break;
case 4:
tabName = "Executable";
tabName = "executableTab";
break;
case 5:
tabName = "Region";
tabName = "screenRegionTab";
break;
case 6:
tabName = "Media";
tabName = "mediaTab";
break;
case 7:
tabName = "File";
tabName = "fileTab";
break;
case 8:
tabName = "Random";
tabName = "randomTab";
break;
case 9:
tabName = "Time";
tabName = "timeTab";
break;
case 10:
tabName = "Idle";
tabName = "idleTab";
break;
case 11:
tabName = "Sequence";
tabName = "sceneSequenceTab";
break;
case 12:
tabName = "audioTab";
break;
case 13:
tabName = "videoTab";
break;
case 14:
tabName = "sceneGroupTab";
break;
case 15:
tabName = "sceneTriggerTab";
break;
}
for (int i = 0; i < bar->count(); ++i) {
if (bar->tabText(i).compare(tabName) == 0) {
at = i;
break;
}
QWidget *page = tabWidget->findChild<QWidget *>(tabName);
if (page) {
at = tabWidget->indexOf(page);
}
if (at == -1)
blog(LOG_INFO, "Advanced Scene Switcher failed to find tab %s",
if (at == -1) {
blog(LOG_INFO, "failed to find tab %s",
tabName.toUtf8().constData());
}
return at;
}
void SceneSwitcher::setTabOrder()
void AdvSceneSwitcher::setTabOrder()
{
QTabBar *bar = ui->tabWidget->tabBar();
for (int i = 0; i < bar->count(); ++i) {
int curPos = findTabIndex(bar, switcher->tabOrder[i]);
int curPos = findTabIndex(ui->tabWidget, switcher->tabOrder[i]);
if (i != curPos && curPos != -1)
if (i != curPos && curPos != -1) {
bar->moveTab(curPos, i);
}
}
connect(bar, &QTabBar::tabMoved, this, &SceneSwitcher::on_tabMoved);
connect(bar, &QTabBar::tabMoved, this, &AdvSceneSwitcher::on_tabMoved);
}
void SceneSwitcher::on_tabMoved(int from, int to)
void AdvSceneSwitcher::on_tabMoved(int from, int to)
{
if (loading)
if (loading) {
return;
}
std::swap(switcher->tabOrder[from], switcher->tabOrder[to]);
}
void AdvSceneSwitcher::on_tabWidget_currentChanged(int index)
{
UNUSED_PARAMETER(index);
switcher->showFrame = false;
clearFrames(ui->screenRegionSwitches);
SetShowFrames();
}
void SwitcherData::loadSettings(obs_data_t *obj)
{
if (!obj) {
return;
}
loadSceneGroups(obj);
loadWindowTitleSwitches(obj);
loadScreenRegionSwitches(obj);
loadPauseSwitches(obj);
loadSceneSequenceSwitches(obj);
loadSceneTransitions(obj);
loadIdleSwitches(obj);
loadExecutableSwitches(obj);
loadRandomSwitches(obj);
loadFileSwitches(obj);
loadMediaSwitches(obj);
loadTimeSwitches(obj);
loadAudioSwitches(obj);
loadVideoSwitches(obj);
loadSceneTriggers(obj);
loadGeneralSettings(obj);
loadHotkeys(obj);
}
void SwitcherData::saveSettings(obs_data_t *obj)
{
if (!obj) {
return;
}
saveSceneGroups(obj);
saveWindowTitleSwitches(obj);
saveScreenRegionSwitches(obj);
savePauseSwitches(obj);
saveSceneSequenceSwitches(obj);
saveSceneTransitions(obj);
saveIdleSwitches(obj);
saveExecutableSwitches(obj);
saveRandomSwitches(obj);
saveFileSwitches(obj);
saveMediaSwitches(obj);
saveTimeSwitches(obj);
saveAudioSwitches(obj);
saveVideoSwitches(obj);
saveSceneTriggers(obj);
saveGeneralSettings(obj);
saveHotkeys(obj);
saveVersion(obj, g_GIT_SHA1);
}
void SwitcherData::saveGeneralSettings(obs_data_t *obj)
{
obs_data_set_int(obj, "interval", switcher->interval);
@@ -335,17 +441,18 @@ void SwitcherData::saveGeneralSettings(obs_data_t *obj)
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_double(obj, "cooldown", switcher->cooldown);
obs_data_set_bool(obj, "active", !switcher->stop);
obs_data_set_int(obj, "startup_behavior", switcher->startupBehavior);
std::string autoStopSceneName =
GetWeakSourceName(switcher->autoStopScene);
obs_data_set_bool(obj, "autoStopEnable", switcher->autoStopEnable);
obs_data_set_string(obj, "autoStopSceneName",
autoStopSceneName.c_str());
obs_data_set_int(obj, "autoStartEvent",
static_cast<int>(switcher->autoStartEvent));
obs_data_set_bool(obj, "verbose", switcher->verbose);
obs_data_set_bool(obj, "disableHints", switcher->disableHints);
obs_data_set_int(obj, "priority0",
switcher->functionNamesByPriority[0]);
@@ -363,9 +470,19 @@ void SwitcherData::saveGeneralSettings(obs_data_t *obj)
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, "threadPriority", switcher->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", switcher->tabOrder[0]);
obs_data_set_int(obj, "transitionTabPos", switcher->tabOrder[1]);
obs_data_set_int(obj, "pauseTabPos", switcher->tabOrder[2]);
@@ -378,11 +495,15 @@ void SwitcherData::saveGeneralSettings(obs_data_t *obj)
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]);
}
void SwitcherData::loadGeneralSettings(obs_data_t *obj)
{
obs_data_set_default_int(obj, "interval", DEFAULT_INTERVAL);
obs_data_set_default_int(obj, "interval", default_interval);
switcher->interval = obs_data_get_int(obj, "interval");
obs_data_set_default_int(obj, "switch_if_not_matching", NO_SWITCH);
@@ -392,30 +513,35 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
obs_data_get_string(obj, "non_matching_scene");
switcher->nonMatchingScene =
GetWeakSourceByName(nonMatchingScene.c_str());
switcher->noMatchDelay = obs_data_get_double(obj, "noMatchDelay");
switcher->cooldown = obs_data_get_double(obj, "cooldown");
switcher->stop = !obs_data_get_bool(obj, "active");
switcher->startupBehavior =
(StartupBehavior)obs_data_get_int(obj, "startup_behavior");
if (switcher->startupBehavior == START)
if (switcher->startupBehavior == START) {
switcher->stop = false;
if (switcher->startupBehavior == STOP)
}
if (switcher->startupBehavior == STOP) {
switcher->stop = true;
}
std::string autoStopScene =
obs_data_get_string(obj, "autoStopSceneName");
switcher->autoStopEnable = obs_data_get_bool(obj, "autoStopEnable");
switcher->autoStopScene = GetWeakSourceByName(autoStopScene.c_str());
switcher->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");
obs_data_set_default_int(obj, "priority0", DEFAULT_PRIORITY_0);
obs_data_set_default_int(obj, "priority1", DEFAULT_PRIORITY_1);
obs_data_set_default_int(obj, "priority2", DEFAULT_PRIORITY_2);
obs_data_set_default_int(obj, "priority3", DEFAULT_PRIORITY_3);
obs_data_set_default_int(obj, "priority4", DEFAULT_PRIORITY_4);
obs_data_set_default_int(obj, "priority5", DEFAULT_PRIORITY_5);
obs_data_set_default_int(obj, "priority6", DEFAULT_PRIORITY_6);
obs_data_set_default_int(obj, "priority7", DEFAULT_PRIORITY_7);
obs_data_set_default_int(obj, "priority0", default_priority_0);
obs_data_set_default_int(obj, "priority1", default_priority_1);
obs_data_set_default_int(obj, "priority2", default_priority_2);
obs_data_set_default_int(obj, "priority3", default_priority_3);
obs_data_set_default_int(obj, "priority4", default_priority_4);
obs_data_set_default_int(obj, "priority5", default_priority_5);
obs_data_set_default_int(obj, "priority6", default_priority_6);
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"));
@@ -433,15 +559,21 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
(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[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);
}
obs_data_set_default_int(obj, "threadPriority",
@@ -460,6 +592,10 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
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);
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "generalTabPos")));
@@ -485,12 +621,116 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
(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")));
if (!tabOrderValid()) {
resetTabOrder();
}
}
void SceneSwitcher::setupGeneralTab()
bool SwitcherData::tabOrderValid()
{
auto tmp = tabOrder;
std::sort(tmp.begin(), tmp.end());
auto it = std::unique(tmp.begin(), tmp.end());
return it == tmp.end();
}
void SwitcherData::resetTabOrder()
{
tabOrder = std::vector<int>(tab_count);
std::iota(switcher->tabOrder.begin(), switcher->tabOrder.end(), 0);
}
void SwitcherData::checkNoMatchSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition, int &sleep)
{
if (match) {
noMatchCount = 0;
return;
}
if ((noMatchCount * interval) / 1000.0 < noMatchDelay) {
noMatchCount++;
return;
}
if (switchIfNotMatching == SWITCH && nonMatchingScene) {
match = true;
scene = nonMatchingScene;
transition = nullptr;
}
if (switchIfNotMatching == RANDOM_SWITCH) {
checkRandom(match, scene, transition, sleep);
}
}
void SwitcherData::checkSwitchCooldown(bool &match)
{
if (!match || cooldown == 0.) {
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;
}
match = false;
vblog(LOG_INFO, "cooldown active - ignoring match");
}
void populateStartupBehavior(QComboBox *cb)
{
cb->addItem(obs_module_text(
"AdvSceneSwitcher.generalTab.status.onStartup.asLastRun"));
cb->addItem(obs_module_text(
"AdvSceneSwitcher.generalTab.status.onStartup.alwaysStart"));
cb->addItem(obs_module_text(
"AdvSceneSwitcher.generalTab.status.onStartup.doNotStart"));
}
void populateAutoStartEventSelection(QComboBox *cb)
{
cb->addItem(obs_module_text(
"AdvSceneSwitcher.generalTab.status.autoStart.never"));
cb->addItem(obs_module_text(
"AdvSceneSwitcher.generalTab.status.autoStart.recording"));
cb->addItem(obs_module_text(
"AdvSceneSwitcher.generalTab.status.autoStart.streaming"));
cb->addItem(obs_module_text(
"AdvSceneSwitcher.generalTab.status.autoStart.recordingAndStreaming"));
}
void AdvSceneSwitcher::updateStatus()
{
if (switcher->th && switcher->th->isRunning()) {
if (currentStatusActive) {
return;
}
SetStarted();
} else {
if (!currentStatusActive) {
return;
}
SetStopped();
}
}
void AdvSceneSwitcher::setupGeneralTab()
{
populateSceneSelection(ui->noMatchSwitchScene, false);
populateSceneSelection(ui->autoStopScenes, false);
if (switcher->switchIfNotMatching == SWITCH) {
ui->noMatchSwitch->setChecked(true);
@@ -504,46 +744,60 @@ void SceneSwitcher::setupGeneralTab()
}
ui->noMatchSwitchScene->setCurrentText(
GetWeakSourceName(switcher->nonMatchingScene).c_str());
ui->noMatchDelay->setValue(switcher->noMatchDelay);
ui->noMatchDelay->setToolTip(obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMetDelayTooltip"));
ui->checkInterval->setValue(switcher->interval);
ui->autoStopSceneCheckBox->setChecked(switcher->autoStopEnable);
ui->autoStopScenes->setCurrentText(
GetWeakSourceName(switcher->autoStopScene).c_str());
if (ui->autoStopSceneCheckBox->checkState()) {
ui->autoStopScenes->setDisabled(false);
} else {
ui->autoStopScenes->setDisabled(true);
}
ui->cooldownTime->setValue(switcher->cooldown);
ui->cooldownTime->setToolTip(obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint"));
ui->verboseLogging->setChecked(switcher->verbose);
ui->uiHintsDisable->setChecked(switcher->disableHints);
for (int p : switcher->functionNamesByPriority) {
std::string s = "";
switch (p) {
case READ_FILE_FUNC:
s = "File Content";
case read_file_func:
s = obs_module_text(
"AdvSceneSwitcher.generalTab.priority.fileContent");
break;
case ROUND_TRIP_FUNC:
s = "Scene Sequence";
case round_trip_func:
s = obs_module_text(
"AdvSceneSwitcher.generalTab.priority.sceneSequence");
break;
case IDLE_FUNC:
s = "Idle Detection";
case idle_func:
s = obs_module_text(
"AdvSceneSwitcher.generalTab.priority.idleDetection");
break;
case EXE_FUNC:
s = "Executable";
case exe_func:
s = obs_module_text(
"AdvSceneSwitcher.generalTab.priority.executable");
break;
case SCREEN_REGION_FUNC:
s = "Screen Region";
case screen_region_func:
s = obs_module_text(
"AdvSceneSwitcher.generalTab.priority.screenRegion");
break;
case WINDOW_TITLE_FUNC:
s = "Window Title";
case window_title_func:
s = obs_module_text(
"AdvSceneSwitcher.generalTab.priority.windowTitle");
break;
case MEDIA_FUNC:
s = "Media";
case media_func:
s = obs_module_text(
"AdvSceneSwitcher.generalTab.priority.media");
break;
case TIME_FUNC:
s = "Time";
case time_func:
s = obs_module_text(
"AdvSceneSwitcher.generalTab.priority.time");
break;
case audio_func:
s = obs_module_text(
"AdvSceneSwitcher.generalTab.priority.audio");
break;
case video_func:
s = obs_module_text(
"AdvSceneSwitcher.generalTab.priority.video");
break;
}
QString text(s.c_str());
@@ -552,7 +806,7 @@ void SceneSwitcher::setupGeneralTab()
item->setData(Qt::UserRole, text);
}
for (int i = 0; i < switcher->threadPriorities.size(); ++i) {
for (int i = 0; i < (int)switcher->threadPriorities.size(); ++i) {
ui->threadPriority->addItem(
switcher->threadPriorities[i].name.c_str());
ui->threadPriority->setItemData(
@@ -565,15 +819,22 @@ void SceneSwitcher::setupGeneralTab()
}
}
ui->startupBehavior->addItem(
"Start the scene switcher if it was running");
ui->startupBehavior->addItem("Always start the scene switcher");
ui->startupBehavior->addItem("Do not start the scene switcher");
populateStartupBehavior(ui->startupBehavior);
ui->startupBehavior->setCurrentIndex(switcher->startupBehavior);
if (switcher->th && switcher->th->isRunning())
populateAutoStartEventSelection(ui->autoStartEvent);
ui->autoStartEvent->setCurrentIndex(
static_cast<int>(switcher->autoStartEvent));
if (switcher->th && switcher->th->isRunning()) {
SetStarted();
else
} else {
SetStopped();
}
// Updates the UI status element if the status changed externally
// (e.g. via hotkeys)
QTimer *statusTimer = new QTimer(this);
connect(statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
statusTimer->start(1000);
}

View File

@@ -1,9 +1,4 @@
#pragma once
#include <QDialog>
#include <memory>
#include <vector>
#include <string>
#ifdef BUILD_OUT_OF_TREE
#include "../../forms/ui_advanced-scene-switcher.h"
#else
@@ -11,48 +6,44 @@
#endif
#include "switcher-data-structs.hpp"
#define blog(level, msg, ...) blog(level, "[adv-ss] " msg, ##__VA_ARGS__)
#define vblog(level, msg, ...) \
if (verbose) { \
blog(level, msg, ##__VA_ARGS__); \
}
class QCloseEvent;
/*******************************************************************************
* Advanced Scene Switcher window
*******************************************************************************/
class SceneSwitcher : public QDialog {
class AdvSceneSwitcher : public QDialog {
Q_OBJECT
public:
std::unique_ptr<Ui_SceneSwitcher> ui;
std::unique_ptr<Ui_AdvSceneSwitcher> ui;
bool loading = true;
bool currentStatusActive = false;
SceneSwitcher(QWidget *parent);
AdvSceneSwitcher(QWidget *parent);
void closeEvent(QCloseEvent *event) override;
void SetStarted();
void SetStopped();
int FindByData(const QString &window);
int ScreenRegionFindByData(const QString &region);
int PauseScenesFindByData(const QString &scene);
int PauseWindowsFindByData(const QString &window);
void SetShowFrames();
void SetHideFrames();
void clearFrames(QListWidget *list);
int IgnoreWindowsFindByData(const QString &window);
int SceneRoundTripFindByData(const QString &scene1);
int SceneTransitionsFindByData(const QString &scene1,
const QString &scene2);
int DefaultTransitionsFindByData(const QString &scene);
int executableFindByData(const QString &exe);
int IgnoreIdleWindowsFindByData(const QString &window);
int randomFindByData(const QString &scene);
int timeFindByData(const QString &timeStr);
void UpdateNonMatchingScene(const QString &name);
void UpdateAutoStopScene(const QString &name);
void UpdateIdleDataTransition(const QString &name);
void UpdateIdleDataScene(const QString &name);
void OpenSequenceExtendEdit(SequenceWidget *sw);
void SetEditSceneGroup(SceneGroup &sg);
void loadUI();
void populateSceneSelection(QComboBox *sel, bool addPrevious);
void populateTransitionSelection(QComboBox *sel);
void populateWindowSelection(QComboBox *sel);
void setupGeneralTab();
void setupTitleTab();
void setupExecutableTab();
@@ -65,69 +56,116 @@ public:
void setupMediaTab();
void setupFileTab();
void setupTimeTab();
void setupAudioTab();
void setupSceneGroupTab();
void setupTriggerTab();
void setupVideoTab();
void setTabOrder();
static bool DisplayMessage(QString msg, bool question = false);
static void AskBackup(obs_data_t *obj);
static void addSelectionEntry(QComboBox *sel, const char *description,
bool selectable = false,
const char *tooltip = "");
static void populateSceneSelection(QComboBox *sel,
bool addPrevious = false,
bool addSceneGroup = false,
bool addSelect = true,
std::string selectText = "",
bool selectable = false);
static void populateTransitionSelection(QComboBox *sel,
bool addCurrent = true,
bool addSelect = true,
bool selectable = false);
static void populateWindowSelection(QComboBox *sel,
bool addSelect = true);
static void populateAudioSelection(QComboBox *sel,
bool addSelect = true);
static void populateVideoSelection(QComboBox *sel,
bool addScenes = true,
bool addSelect = true);
static void populateMediaSelection(QComboBox *sel,
bool addSelect = true);
static void populateProcessSelection(QComboBox *sel,
bool addSelect = true);
QMetaObject::Connection PulseWidget(QWidget *widget, QColor endColor,
QColor = QColor(0, 0, 0, 0),
QString specifier = "QLabel ");
void listAddClicked(QListWidget *list, SwitchWidget *newWidget,
QPushButton *addButton = nullptr,
QMetaObject::Connection *addHighlight = nullptr);
bool listMoveUp(QListWidget *list);
bool listMoveDown(QListWidget *list);
signals:
void SceneGroupAdded(const QString &name);
void SceneGroupRemoved(const QString &name);
void SceneGroupRenamed(const QString &oldName, const QString newName);
public slots:
void on_switches_currentRowChanged(int idx);
void on_up_clicked();
void on_down_clicked();
void on_add_clicked();
void on_remove_clicked();
void on_windowUp_clicked();
void on_windowDown_clicked();
void on_windowAdd_clicked();
void on_windowRemove_clicked();
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 on_startupBehavior_currentIndexChanged(int index);
void on_autoStartEvent_currentIndexChanged(int index);
void on_noMatchSwitchScene_currentTextChanged(const QString &text);
void on_checkInterval_valueChanged(int value);
void on_toggleStartButton_clicked();
void on_tabMoved(int from, int to);
void on_tabWidget_currentChanged(int index);
void on_screenRegions_currentRowChanged(int idx);
void on_screenRegionSwitches_currentRowChanged(int idx);
void on_showFrame_clicked();
void on_screenRegionAdd_clicked();
void on_screenRegionRemove_clicked();
void on_screenRegionUp_clicked();
void on_screenRegionDown_clicked();
void on_pauseScenes_currentRowChanged(int idx);
void on_pauseScenesAdd_clicked();
void on_pauseScenesRemove_clicked();
void on_pauseWindows_currentRowChanged(int idx);
void on_pauseWindowsAdd_clicked();
void on_pauseWindowsRemove_clicked();
void on_pauseUp_clicked();
void on_pauseDown_clicked();
void on_pauseAdd_clicked();
void on_pauseRemove_clicked();
void on_ignoreWindows_currentRowChanged(int idx);
void on_ignoreWindowsAdd_clicked();
void on_ignoreWindowsRemove_clicked();
void on_sceneRoundTrips_currentRowChanged(int idx);
void on_sceneRoundTripAdd_clicked();
void on_sceneRoundTripRemove_clicked();
void on_sceneRoundTripSave_clicked();
void on_sceneRoundTripLoad_clicked();
void on_sceneRoundTripUp_clicked();
void on_sceneRoundTripDown_clicked();
void on_sceneRoundTripDelayUnits_currentIndexChanged(int index);
void on_autoStopSceneCheckBox_stateChanged(int state);
void on_autoStopScenes_currentTextChanged(const QString &text);
void on_sceneSequenceAdd_clicked();
void on_sceneSequenceRemove_clicked();
void on_sceneSequenceUp_clicked();
void on_sceneSequenceDown_clicked();
void on_sceneSequenceSave_clicked();
void on_sceneSequenceLoad_clicked();
void on_sequenceEdit_clicked();
void on_sceneSequenceSwitches_itemDoubleClicked(QListWidgetItem *item);
void on_verboseLogging_stateChanged(int state);
void on_uiHintsDisable_stateChanged(int state);
void on_exportSettings_clicked();
void on_importSettings_clicked();
void on_sceneTransitions_currentRowChanged(int idx);
void on_transitionsAdd_clicked();
void on_transitionsRemove_clicked();
void on_defaultTransitions_currentRowChanged(int idx);
void on_transitionsUp_clicked();
void on_transitionsDown_clicked();
void on_defaultTransitionsAdd_clicked();
void on_defaultTransitionsRemove_clicked();
void on_defaultTransitionsUp_clicked();
void on_defaultTransitionsDown_clicked();
void on_transitionOverridecheckBox_stateChanged(int state);
void on_browseButton_clicked();
void on_readFileCheckBox_stateChanged(int state);
void on_fileType_currentIndexChanged(int idx);
void on_readPathLineEdit_textChanged(const QString &text);
void on_writePathLineEdit_textChanged(const QString &text);
void on_browseButton_2_clicked();
@@ -136,81 +174,100 @@ public slots:
void on_executableDown_clicked();
void on_executableAdd_clicked();
void on_executableRemove_clicked();
void on_executables_currentRowChanged(int idx);
void on_idleCheckBox_stateChanged(int state);
void on_idleTransitions_currentTextChanged(const QString &text);
void on_idleScenes_currentTextChanged(const QString &text);
void on_idleSpinBox_valueChanged(int i);
void on_ignoreIdleWindows_currentRowChanged(int idx);
void on_ignoreIdleAdd_clicked();
void on_ignoreIdleRemove_clicked();
void on_randomAdd_clicked();
void on_randomRemove_clicked();
void on_randomScenesList_currentRowChanged(int idx);
void on_fileAdd_clicked();
void on_fileRemove_clicked();
void on_fileScenesList_currentRowChanged(int idx);
void on_browseButton_3_clicked();
void on_fileSwitches_currentRowChanged(int idx);
void on_fileUp_clicked();
void on_fileDown_clicked();
void on_mediaSwitches_currentRowChanged(int idx);
void on_mediaAdd_clicked();
void on_mediaRemove_clicked();
void on_mediaUp_clicked();
void on_mediaDown_clicked();
void on_mediaTimeRestrictions_currentIndexChanged(int idx);
void on_timeSwitches_currentRowChanged(int idx);
void on_timeAdd_clicked();
void on_timeRemove_clicked();
void on_timeUp_clicked();
void on_timeDown_clicked();
void on_audioAdd_clicked();
void on_audioRemove_clicked();
void on_audioUp_clicked();
void on_audioDown_clicked();
void on_audioFallback_toggled(bool on);
void on_videoAdd_clicked();
void on_videoRemove_clicked();
void on_videoUp_clicked();
void on_videoDown_clicked();
void on_getScreenshot_clicked();
void on_sceneGroupAdd_clicked();
void on_sceneGroupRemove_clicked();
void on_sceneGroupUp_clicked();
void on_sceneGroupDown_clicked();
void on_sceneGroupName_editingFinished();
void on_sceneGroups_currentRowChanged(int idx);
void on_sceneGroupSceneAdd_clicked();
void on_sceneGroupSceneRemove_clicked();
void on_sceneGroupSceneUp_clicked();
void on_sceneGroupSceneDown_clicked();
void on_triggerAdd_clicked();
void on_triggerRemove_clicked();
void on_triggerUp_clicked();
void on_triggerDown_clicked();
void on_priorityUp_clicked();
void on_priorityDown_clicked();
void on_threadPriority_currentTextChanged(const QString &text);
void updateScreenRegionCursorPos();
void updateStatus();
void on_close_clicked();
private:
};
/********************************************************************************
/******************************************************************************
* Windowtitle helper
********************************************************************************/
******************************************************************************/
void GetWindowList(std::vector<std::string> &windows);
void GetWindowList(QStringList &windows); // Overloaded
void GetWindowList(QStringList &windows);
void GetCurrentWindowTitle(std::string &title);
bool isFullscreen(std::string &title);
bool isMaximized(std::string &title);
/********************************************************************************
/******************************************************************************
* Screenregion helper
********************************************************************************/
******************************************************************************/
std::pair<int, int> getCursorPos();
/********************************************************************************
/******************************************************************************
* Idle detection helper
********************************************************************************/
******************************************************************************/
int secondsSinceLastInput();
/********************************************************************************
/******************************************************************************
* Executable helper
********************************************************************************/
******************************************************************************/
void GetProcessList(QStringList &processes);
bool isInFocus(const QString &executable);
/********************************************************************************
/******************************************************************************
* Sceneswitch helper
********************************************************************************/
struct obs_weak_source;
typedef struct obs_weak_source obs_weak_source_t;
typedef struct transitionData {
std::string name = "";
int duration = 0;
} transitionData;
******************************************************************************/
void setNextTransition(OBSWeakSource &targetScene, obs_source_t *currentSource,
OBSWeakSource &transition,
@@ -218,29 +275,11 @@ void setNextTransition(OBSWeakSource &targetScene, obs_source_t *currentSource,
void overwriteTransitionOverride(obs_weak_source_t *sceneWs,
obs_source_t *transition, transitionData &td);
void restoreTransitionOverride(obs_source_t *scene, transitionData td);
void switchScene(OBSWeakSource &scene, OBSWeakSource &transition,
bool &transitionOverrideOverride,
std::unique_lock<std::mutex> &lock);
bool &transitionOverrideOverride);
/********************************************************************************
* Hotkey helper
********************************************************************************/
#define TOGGLE_HOTKEY_PATH "hotkey_toggle.txt"
#define STOP_HOTKEY_PATH "hotkey_stop.txt"
#define START_HOTKEY_PATH "hotkey_start.txt"
void stopHotkeyFunc(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey,
bool pressed);
void startHotkeyFunc(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey,
bool pressed);
void startStopToggleHotkeyFunc(void *data, obs_hotkey_id id,
obs_hotkey_t *hotkey, bool pressed);
void loadKeybinding(obs_hotkey_id hotkeyId, std::string path);
/********************************************************************************
/******************************************************************************
* Main SwitcherData
********************************************************************************/
******************************************************************************/
struct SwitcherData;
extern SwitcherData *switcher;

View File

@@ -0,0 +1,26 @@
#pragma once
#include <curl/curl.h>
#include <QLibrary>
#if defined(WIN32)
constexpr auto curl_library_name = "libcurl.dll";
#elif __APPLE__
#define curl_library_name "libcurl.4.dylib"
#else
#define curl_library_name "libcurl.so.4"
#endif
typedef CURL *(*initFunction)(void);
typedef CURLcode (*setOptFunction)(CURL *, CURLoption, ...);
typedef CURLcode (*performFunction)(CURL *);
typedef void (*cleanupFunction)(CURL *);
extern initFunction f_curl_init;
extern setOptFunction f_curl_setopt;
extern performFunction f_curl_perform;
extern cleanupFunction f_curl_cleanup;
extern QLibrary *loaded_curl_lib;
bool resolveCurl();
bool loadCurl();

108
src/headers/scene-group.hpp Normal file
View File

@@ -0,0 +1,108 @@
#pragma once
#include <vector>
#include <QDialog>
#include <QLabel>
#include <QLineEdit>
#include <QComboBox>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QCheckBox>
#include <obs.hpp>
auto constexpr invalid_scene_group_name = "invalid-scene-group";
enum class AdvanceCondition {
Count,
Time,
Random,
};
struct SceneGroup {
OBSWeakSource getCurrentScene();
OBSWeakSource getNextScene();
OBSWeakSource getNextSceneCount();
OBSWeakSource getNextSceneTime();
OBSWeakSource getNextSceneRandom();
void advanceIdx();
std::string name = invalid_scene_group_name;
AdvanceCondition type = AdvanceCondition::Count;
std::vector<OBSWeakSource> scenes = {};
int count = 1;
double time = 0;
bool repeat = false;
size_t currentIdx = 0;
int currentCount = -1;
std::chrono::high_resolution_clock::time_point lastAdvTime;
int lastRandomScene = -1;
inline SceneGroup(){};
inline SceneGroup(std::string name_) : name(name_){};
inline SceneGroup(std::string name_, AdvanceCondition type_,
std::vector<OBSWeakSource> scenes_, int count_,
double time_, bool repeat_)
: name(name_),
type(type_),
scenes(scenes_),
count(count_),
time(time_),
repeat(repeat_)
{
}
};
enum class SwitchTargetType {
Scene,
SceneGroup,
};
class SceneGroupEditWidget : public QWidget {
Q_OBJECT
public:
SceneGroupEditWidget();
void SetEditSceneGroup(SceneGroup *sg);
void ShowCurrentTypeEdit();
private slots:
void TypeChanged(int type);
void CountChanged(int count);
void TimeChanged(double time);
void RepeatChanged(int state);
private:
QComboBox *type;
QWidget *timeEdit;
QWidget *countEdit;
QSpinBox *count;
QDoubleSpinBox *time;
QLabel *random;
QCheckBox *repeat;
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,73 @@
#pragma once
#include "switch-generic.hpp"
enum class sceneTriggerType {
NONE = 0,
SCENE_ACTIVE = 1,
SCENE_INACTIVE = 2,
SCENE_LEAVE = 3,
};
enum class sceneTriggerAction {
NONE = 0,
START_RECORDING,
PAUSE_RECORDING,
UNPAUSE_RECORDING,
STOP_RECORDING,
START_STREAMING,
STOP_STREAMING,
START_REPLAY_BUFFER,
STOP_REPLAY_BUFFER,
MUTE_SOURCE,
UNMUTE_SOURCE,
START_SWITCHER,
STOP_SWITCHER,
};
struct SceneTrigger : SceneSwitcherEntry {
static bool pause;
sceneTriggerType triggerType = sceneTriggerType::NONE;
sceneTriggerAction triggerAction = sceneTriggerAction::NONE;
double duration = 0;
OBSWeakSource audioSource = nullptr;
const char *getType() { return "trigger"; }
void save(obs_data_t *obj);
void load(obs_data_t *obj);
bool checkMatch(OBSWeakSource currentScene,
OBSWeakSource previousScene);
void performAction();
void logMatch();
};
class SceneTriggerWidget : public SwitchWidget {
Q_OBJECT
public:
SceneTriggerWidget(QWidget *parent, SceneTrigger *s);
SceneTrigger *getSwitchData();
void setSwitchData(SceneTrigger *s);
static void swapSwitchData(SceneTriggerWidget *s1,
SceneTriggerWidget *s2);
private slots:
void TriggerTypeChanged(int index);
void TriggerActionChanged(int index);
void DurationChanged(double dur);
void AudioSourceChanged(const QString &text);
private:
QComboBox *triggers;
QComboBox *actions;
QDoubleSpinBox *duration;
QComboBox *audioSources;
SceneTrigger *switchData;
};

View File

@@ -0,0 +1,96 @@
#pragma once
#include <QSpinBox>
#include "switch-generic.hpp"
#include "volume-control.hpp"
constexpr auto audio_func = 8;
constexpr auto default_priority_8 = audio_func;
typedef enum {
ABOVE,
BELOW,
} audioCondition;
struct AudioSwitch : virtual SceneSwitcherEntry {
static bool pause;
OBSWeakSource audioSource = nullptr;
int volumeThreshold = 0;
audioCondition condition = ABOVE;
double duration = 0;
unsigned int matchCount = 0;
float peak = -1;
obs_volmeter_t *volmeter = nullptr;
const char *getType() { return "audio"; }
bool initialized();
bool valid();
void save(obs_data_t *obj);
void load(obs_data_t *obj);
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();
AudioSwitch(){};
AudioSwitch(const AudioSwitch &other);
AudioSwitch(AudioSwitch &&other);
~AudioSwitch();
AudioSwitch &operator=(const AudioSwitch &other);
AudioSwitch &operator=(AudioSwitch &&other) noexcept;
friend void swap(AudioSwitch &first, AudioSwitch &second);
};
struct AudioSwitchFallback : virtual SceneSwitcherEntry {
const char *getType() { return "audio_fallback"; }
void save(obs_data_t *obj);
void load(obs_data_t *obj);
bool enable = false;
double duration = 0;
unsigned int matchCount = 0;
};
class AudioSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
AudioSwitchWidget(QWidget *parent, AudioSwitch *s);
void UpdateVolmeterSource();
AudioSwitch *getSwitchData();
void setSwitchData(AudioSwitch *s);
static void swapSwitchData(AudioSwitchWidget *as1,
AudioSwitchWidget *as2);
private slots:
void SourceChanged(const QString &text);
void VolumeThresholdChanged(int vol);
void ConditionChanged(int cond);
void DurationChanged(double dur);
private:
QComboBox *audioSources;
QComboBox *condition;
QSpinBox *audioVolumeThreshold;
QDoubleSpinBox *duration;
VolControl *volMeter;
AudioSwitch *switchData;
};
class AudioSwitchFallbackWidget : public SwitchWidget {
Q_OBJECT
public:
AudioSwitchFallbackWidget(QWidget *parent, AudioSwitchFallback *s);
private slots:
void DurationChanged(double dur);
private:
QDoubleSpinBox *duration;
AudioSwitchFallback *switchData;
};

View File

@@ -0,0 +1,38 @@
#pragma once
#include <QCheckBox>
#include "switch-generic.hpp"
constexpr auto exe_func = 3;
constexpr auto default_priority_3 = exe_func;
struct ExecutableSwitch : SceneSwitcherEntry {
static bool pause;
QString exe = "";
bool inFocus = false;
const char *getType() { return "exec"; }
void save(obs_data_t *obj);
void load(obs_data_t *obj);
};
class ExecutableSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
ExecutableSwitchWidget(QWidget *parent, ExecutableSwitch *s);
ExecutableSwitch *getSwitchData();
void setSwitchData(ExecutableSwitch *s);
static void swapSwitchData(ExecutableSwitchWidget *s1,
ExecutableSwitchWidget *s2);
private slots:
void ProcessChanged(const QString &text);
void FocusChanged(int state);
private:
QComboBox *processes;
QCheckBox *requiresFocus;
ExecutableSwitch *switchData;
};

View File

@@ -0,0 +1,73 @@
#pragma once
#include "switch-generic.hpp"
#include <QPlainTextEdit>
#include <obs-module.h>
constexpr auto read_file_func = 0;
constexpr auto default_priority_0 = read_file_func;
typedef enum {
LOCAL,
REMOTE,
} file_type;
struct FileSwitch : SceneSwitcherEntry {
static bool pause;
std::string file = obs_module_text("AdvSceneSwitcher.enterPath");
std::string text = obs_module_text("AdvSceneSwitcher.enterText");
bool remote = false;
bool useRegex = false;
bool useTime = false;
bool onlyMatchIfChanged = false;
QDateTime lastMod;
size_t lastHash = 0;
const char *getType() { return "file"; }
void save(obs_data_t *obj);
void load(obs_data_t *obj);
};
class FileSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
FileSwitchWidget(QWidget *parent, FileSwitch *s);
FileSwitch *getSwitchData();
void setSwitchData(FileSwitch *s);
static void swapSwitchData(FileSwitchWidget *as1,
FileSwitchWidget *as2);
private slots:
void FileTypeChanged(int index);
void FilePathChanged();
void MatchTextChanged();
void UseRegexChanged(int state);
void CheckModificationDateChanged(int state);
void CheckFileContentChanged(int state);
void BrowseButtonClicked();
private:
QComboBox *fileType;
QLineEdit *filePath;
QPushButton *browseButton;
QPlainTextEdit *matchText;
QCheckBox *useRegex;
QCheckBox *checkModificationDate;
QCheckBox *checkFileContent;
FileSwitch *switchData;
};
struct FileIOData {
bool readEnabled = false;
std::string readPath;
bool writeEnabled = false;
std::string writePath;
};
static inline QString MakeFileSwitchName(const QString &scene,
const QString &transition,
const QString &fileName,
const QString &text, bool useRegex,
bool useTime, bool onlyMatchIfChanged);

View File

@@ -0,0 +1,90 @@
#pragma once
#include <obs.hpp>
#include <QComboBox>
struct SceneSwitcherEntry {
SwitchTargetType targetType = SwitchTargetType::Scene;
SceneGroup *group = nullptr;
OBSWeakSource scene = nullptr;
OBSWeakSource transition = nullptr;
bool usePreviousScene = false;
bool useCurrentTransition = false;
virtual const char *getType() = 0;
virtual bool initialized();
virtual bool valid();
virtual void logMatchScene();
virtual void logMatchSceneGroup();
virtual void logMatch();
virtual OBSWeakSource getScene();
virtual void save(obs_data_t *obj,
const char *targetTypeSaveName = "targetType",
const char *targetSaveName = "target",
const char *transitionSaveName = "transition");
virtual void load(obs_data_t *obj,
const char *targetTypeLoadName = "targetType",
const char *targetLoadName = "target",
const char *transitionLoadName = "transition");
inline SceneSwitcherEntry() {}
inline SceneSwitcherEntry(OBSWeakSource scene_,
OBSWeakSource transition_,
bool usePreviousScene_ = false)
: scene(scene_),
transition(transition_),
usePreviousScene(usePreviousScene_)
{
}
inline SceneSwitcherEntry(SceneGroup *group_, OBSWeakSource transition_,
bool usePreviousScene_ = false)
: group(group_),
transition(transition_),
usePreviousScene(usePreviousScene_)
{
}
inline SceneSwitcherEntry(SwitchTargetType targetType_,
SceneGroup *group_, OBSWeakSource scene_,
OBSWeakSource transition_,
bool usePreviousScene_ = false)
: targetType(targetType_),
group(group_),
scene(scene_),
transition(transition_),
usePreviousScene(usePreviousScene_)
{
}
virtual ~SceneSwitcherEntry() {}
};
class SwitchWidget : public QWidget {
Q_OBJECT
public:
SwitchWidget(QWidget *parent, SceneSwitcherEntry *s,
bool usePreviousScene = true, bool addSceneGroup = false,
bool addCurrentTransition = true);
virtual SceneSwitcherEntry *getSwitchData();
virtual void setSwitchData(SceneSwitcherEntry *s);
void showSwitchData();
static void swapSwitchData(SwitchWidget *s1, SwitchWidget *s2);
protected slots:
void SceneChanged(const QString &text);
void TransitionChanged(const QString &text);
void SceneGroupAdd(const QString &name);
void SceneGroupRemove(const QString &name);
void SceneGroupRename(const QString &oldName, const QString &newName);
protected:
bool loading = true;
QComboBox *scenes;
QComboBox *transitions;
SceneSwitcherEntry *switchData;
};

View File

@@ -0,0 +1,32 @@
#pragma once
#include "switch-generic.hpp"
constexpr auto default_idle_time = 60;
constexpr auto idle_func = 2;
constexpr auto default_priority_2 = idle_func;
struct IdleData : SceneSwitcherEntry {
static bool pause;
bool idleEnable = false;
int time = default_idle_time;
bool alreadySwitched = false;
const char *getType() { return "idle"; }
void save(obs_data_t *obj);
void load(obs_data_t *obj);
};
class IdleWidget : public SwitchWidget {
Q_OBJECT
public:
IdleWidget(QWidget *parent, IdleData *s);
private slots:
void DurationChanged(int dur);
private:
QSpinBox *duration;
IdleData *switchData;
};

View File

@@ -0,0 +1,79 @@
#pragma once
#include "switch-generic.hpp"
constexpr auto media_func = 6;
constexpr auto default_priority_6 = media_func;
typedef enum {
TIME_RESTRICTION_NONE,
TIME_RESTRICTION_SHORTER,
TIME_RESTRICTION_LONGER,
TIME_RESTRICTION_REMAINING_SHORTER,
TIME_RESTRICTION_REMAINING_LONGER,
} time_restriction;
struct MediaSwitch : SceneSwitcherEntry {
static bool pause;
OBSWeakSource source = nullptr;
obs_media_state state = OBS_MEDIA_STATE_NONE;
bool anyState = false;
time_restriction restriction = TIME_RESTRICTION_NONE;
int64_t time = 0;
// Trigger scene change only once even if media state might trigger repeatedly
bool matched = false;
std::atomic_bool stopped = {false};
std::atomic_bool ended = {false};
// Workaround to enable use of "ended" to specify end of VLC playlist
bool previousStateEnded = false;
bool playedToEnd = false;
const char *getType() { return "media"; }
bool initialized();
bool valid();
void save(obs_data_t *obj);
void load(obs_data_t *obj);
void clearSignalHandler();
void resetSignalHandler();
static void MediaStopped(void *data, calldata_t *);
static void MediaEnded(void *data, calldata_t *);
inline MediaSwitch(){};
MediaSwitch(const MediaSwitch &other);
MediaSwitch(MediaSwitch &&other);
~MediaSwitch();
MediaSwitch &operator=(const MediaSwitch &other);
MediaSwitch &operator=(MediaSwitch &&other) noexcept;
friend void swap(MediaSwitch &first, MediaSwitch &second);
};
class MediaSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
MediaSwitchWidget(QWidget *parent, MediaSwitch *s);
MediaSwitch *getSwitchData();
void setSwitchData(MediaSwitch *s);
static void swapSwitchData(MediaSwitchWidget *s1,
MediaSwitchWidget *s2);
private slots:
void SourceChanged(const QString &text);
void StateChanged(int index);
void TimeRestrictionChanged(int index);
void TimeChanged(int time);
private:
QComboBox *mediaSources;
QComboBox *states;
QComboBox *timeRestrictions;
QSpinBox *time;
MediaSwitch *switchData;
};

View File

@@ -0,0 +1,65 @@
#pragma once
#include "switch-generic.hpp"
enum class PauseType {
Scene,
Window,
};
enum class PauseTarget {
All,
Transition,
Window,
Executable,
Region,
Media,
File,
Random,
Time,
Idle,
Sequence,
Audio,
Video,
};
struct PauseEntry : SceneSwitcherEntry {
PauseType pauseType = PauseType::Scene;
PauseTarget pauseTarget = PauseTarget::All;
std::string window = "";
const char *getType() { return "pause"; }
bool valid();
inline PauseEntry(){};
inline PauseEntry(OBSWeakSource scene_, PauseType pauseType_,
PauseTarget pauseTarget_, const std::string &window_)
: SceneSwitcherEntry(scene_, nullptr),
pauseType(pauseType_),
pauseTarget(pauseTarget_),
window(window_)
{
}
};
class PauseEntryWidget : public SwitchWidget {
Q_OBJECT
public:
PauseEntryWidget(QWidget *parent, PauseEntry *s);
PauseEntry *getSwitchData();
void setSwitchData(PauseEntry *s);
static void swapSwitchData(PauseEntryWidget *s1, PauseEntryWidget *s2);
private slots:
void PauseTypeChanged(int index);
void PauseTargetChanged(int index);
void WindowChanged(const QString &text);
private:
QComboBox *pauseTypes;
QComboBox *pauseTargets;
QComboBox *windows;
PauseEntry *switchData;
};

View File

@@ -0,0 +1,31 @@
#pragma once
#include "switch-generic.hpp"
struct RandomSwitch : SceneSwitcherEntry {
static bool pause;
double delay = 0.0;
const char *getType() { return "random"; }
void save(obs_data_t *obj);
void load(obs_data_t *obj);
};
class RandomSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
RandomSwitchWidget(QWidget *parent, RandomSwitch *s);
RandomSwitch *getSwitchData();
void setSwitchData(RandomSwitch *s);
static void swapSwitchData(RandomSwitchWidget *s1,
RandomSwitchWidget *s2);
private slots:
void DelayChanged(double d);
private:
QDoubleSpinBox *delay;
RandomSwitch *switchData;
};

View File

@@ -0,0 +1,51 @@
#pragma once
#include <QSpinBox>
#include "switch-generic.hpp"
constexpr auto screen_region_func = 4;
constexpr auto default_priority_4 = screen_region_func;
struct ScreenRegionSwitch : SceneSwitcherEntry {
static bool pause;
OBSWeakSource excludeScene = nullptr;
int minX = 0, minY = 0, maxX = 0, maxY = 0;
const char *getType() { return "region"; }
void save(obs_data_t *obj);
void load(obs_data_t *obj);
};
class ScreenRegionWidget : public SwitchWidget {
Q_OBJECT
public:
ScreenRegionWidget(QWidget *parent, ScreenRegionSwitch *s);
ScreenRegionSwitch *getSwitchData();
void setSwitchData(ScreenRegionSwitch *s);
static void swapSwitchData(ScreenRegionWidget *s1,
ScreenRegionWidget *s2);
void showFrame();
void hideFrame();
private slots:
void ExcludeSceneChanged(const QString &text);
void MinXChanged(int pos);
void MinYChanged(int pos);
void MaxXChanged(int pos);
void MaxYChanged(int pos);
private:
QComboBox *excludeScenes;
QSpinBox *minX;
QSpinBox *minY;
QSpinBox *maxX;
QSpinBox *maxY;
QFrame helperFrame;
ScreenRegionSwitch *switchData;
void drawFrame();
};

View File

@@ -0,0 +1,90 @@
#pragma once
#include <QVBoxLayout>
#include "switch-generic.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;
bool interruptible = false;
unsigned int matchCount = 0;
// nullptr marks start point and reaching end of extended sequence
SceneSequenceSwitch *activeSequence = nullptr;
std::unique_ptr<SceneSequenceSwitch> extendedSequence = nullptr;
const char *getType() { return "sequence"; }
bool initialized();
bool valid();
void save(obs_data_t *obj, bool saveExt = true);
void load(obs_data_t *obj, bool saveExt = true);
bool reduce();
SceneSequenceSwitch *extend();
bool checkMatch(OBSWeakSource currentScene, int &linger,
SceneSequenceSwitch *root = nullptr);
bool checkDurationMatchInterruptible();
void prepareUninterruptibleMatch(OBSWeakSource currentScene,
int &linger);
void advanceActiveSequence();
void logAdvanceSequence();
void logSequenceCanceled();
};
class SequenceWidget : public SwitchWidget {
Q_OBJECT
public:
SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
bool extendSequence = false, bool editExtendMode = false,
bool showExtendText = true);
SceneSequenceSwitch *getSwitchData();
void setSwitchData(SceneSequenceSwitch *s);
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 StartSceneChanged(const QString &text);
void InterruptibleChanged(int state);
void ExtendClicked();
void ReduceClicked();
protected:
QDoubleSpinBox *delay;
QComboBox *delayUnits;
QComboBox *startScenes;
QCheckBox *interruptible;
QVBoxLayout *extendSequenceLayout;
QPushButton *extend;
QPushButton *reduce;
// I would prefer having a list of only widgets of type editExtendMode
// but I am not sure how to best implement that using a QListWidget.
//
// So use edit button to bring up edit widget and
// add a label to disaplay current extended sequence state.
QLabel *extendText;
SceneSequenceSwitch *switchData;
};

View File

@@ -0,0 +1,50 @@
#pragma once
#include <QTimeEdit>
#include "switch-generic.hpp"
constexpr auto time_func = 7;
constexpr auto default_priority_7 = time_func;
typedef enum {
ANY_DAY = 0,
MONDAY = 1,
TUSEDAY = 2,
WEDNESDAY = 3,
THURSDAY = 4,
FRIDAY = 5,
SATURDAY = 6,
SUNDAY = 7,
LIVE = 8
} timeTrigger;
struct TimeSwitch : SceneSwitcherEntry {
static bool pause;
timeTrigger trigger = ANY_DAY;
QTime time = QTime(0, 0);
const char *getType() { return "time"; }
void save(obs_data_t *obj);
void load(obs_data_t *obj);
};
class TimeSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
TimeSwitchWidget(QWidget *parent, TimeSwitch *s);
TimeSwitch *getSwitchData();
void setSwitchData(TimeSwitch *s);
static void swapSwitchData(TimeSwitchWidget *s1, TimeSwitchWidget *s2);
private slots:
void TriggerChanged(int index);
void TimeChanged(const QTime &time);
private:
QComboBox *triggers;
QTimeEdit *time;
TimeSwitch *switchData;
};

View File

@@ -0,0 +1,67 @@
#pragma once
#include "switch-generic.hpp"
struct SceneTransition : SceneSwitcherEntry {
OBSWeakSource scene2 = nullptr;
const char *getType() { return "transition"; }
bool initialized();
bool valid();
inline SceneTransition(){};
inline SceneTransition(OBSWeakSource scene_, OBSWeakSource scene2_,
OBSWeakSource transition_)
: SceneSwitcherEntry(scene_, transition_), scene2(scene2_)
{
}
};
struct DefaultSceneTransition : SceneSwitcherEntry {
static bool pause;
const char *getType() { return "def_transition"; }
bool checkMatch(OBSWeakSource currentScene);
void setTransition();
inline DefaultSceneTransition(){};
inline DefaultSceneTransition(OBSWeakSource scene_,
OBSWeakSource transition_)
: SceneSwitcherEntry(scene_, transition_)
{
}
};
class TransitionSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
TransitionSwitchWidget(QWidget *parent, SceneTransition *s);
SceneTransition *getSwitchData();
void setSwitchData(SceneTransition *s);
static void swapSwitchData(TransitionSwitchWidget *s1,
TransitionSwitchWidget *s2);
private slots:
void Scene2Changed(const QString &text);
private:
QComboBox *scenes2;
SceneTransition *switchData;
};
class DefTransitionSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
DefTransitionSwitchWidget(QWidget *parent, DefaultSceneTransition *s);
DefaultSceneTransition *getSwitchData();
void setSwitchData(DefaultSceneTransition *s);
static void swapSwitchData(DefTransitionSwitchWidget *s1,
DefTransitionSwitchWidget *s2);
private:
DefaultSceneTransition *switchData;
};

View File

@@ -0,0 +1,98 @@
#pragma once
#include <QSpinBox>
#include "switch-generic.hpp"
constexpr auto video_func = 9;
constexpr auto default_priority_9 = video_func;
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;
};
struct VideoSwitch : virtual SceneSwitcherEntry {
static bool pause;
videoSwitchType condition = videoSwitchType::MATCH;
OBSWeakSource videoSource = nullptr;
std::string file = obs_module_text("AdvSceneSwitcher.enterPath");
double duration = 0;
bool ignoreInactiveSource = false;
std::unique_ptr<AdvSSScreenshotObj> screenshotData = nullptr;
std::chrono::high_resolution_clock::time_point previousTime{};
QImage matchImage;
std::chrono::milliseconds currentMatchDuration{};
const char *getType() { return "video"; }
bool initialized();
bool valid();
void save(obs_data_t *obj);
void load(obs_data_t *obj);
void getScreenshot();
bool loadImageFromFile();
bool checkMatch();
VideoSwitch(){};
friend void swap(VideoSwitch &first, VideoSwitch &second);
};
class VideoSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
VideoSwitchWidget(QWidget *parent, VideoSwitch *s);
VideoSwitch *getSwitchData();
void setSwitchData(VideoSwitch *s);
static void swapSwitchData(VideoSwitchWidget *as1,
VideoSwitchWidget *as2);
void UpdatePreviewTooltip();
void SetFilePath(const QString &text);
private slots:
void SourceChanged(const QString &text);
void ConditionChanged(int cond);
void DurationChanged(double dur);
void FilePathChanged();
void BrowseButtonClicked();
void IgnoreInactiveChanged(int state);
private:
QComboBox *videoSources;
QComboBox *condition;
QDoubleSpinBox *duration;
QLineEdit *filePath;
QPushButton *browseButton;
QCheckBox *ignoreInactiveSource;
VideoSwitch *switchData;
};

View File

@@ -0,0 +1,43 @@
#pragma once
#include "switch-generic.hpp"
constexpr auto window_title_func = 5;
constexpr auto default_priority_5 = window_title_func;
struct WindowSwitch : SceneSwitcherEntry {
static bool pause;
std::string window = "";
bool fullscreen = false;
bool maximized = false;
bool focus = true;
const char *getType() { return "window"; }
void save(obs_data_t *obj);
void load(obs_data_t *obj);
};
class WindowSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
WindowSwitchWidget(QWidget *parent, WindowSwitch *s);
WindowSwitch *getSwitchData();
void setSwitchData(WindowSwitch *s);
static void swapSwitchData(WindowSwitchWidget *s1,
WindowSwitchWidget *s2);
private slots:
void WindowChanged(const QString &text);
void FullscreenChanged(int state);
void MaximizedChanged(int state);
void FocusChanged(int state);
private:
QComboBox *windows;
QCheckBox *fullscreen;
QCheckBox *maximized;
QCheckBox *focused;
WindowSwitch *switchData;
};

View File

@@ -1,260 +1,48 @@
#pragma once
#include <condition_variable>
#include <chrono>
#include <string>
#include <vector>
#include <regex>
#include <deque>
#include <mutex>
#include <fstream>
#include <QDateTime>
#include <QThread>
#include "utility.hpp"
#include <curl/curl.h>
#define DEFAULT_INTERVAL 300
#include "scene-group.hpp"
#include "scene-trigger.hpp"
#include "switch-audio.hpp"
#include "switch-executable.hpp"
#include "switch-file.hpp"
#include "switch-idle.hpp"
#include "switch-media.hpp"
#include "switch-pause.hpp"
#include "switch-random.hpp"
#include "switch-screen-region.hpp"
#include "switch-time.hpp"
#include "switch-transitions.hpp"
#include "switch-window.hpp"
#include "switch-sequence.hpp"
#include "switch-video.hpp"
#define DEFAULT_IDLE_TIME 60
#define PREVIOUS_SCENE_NAME "Previous Scene"
#define READ_FILE_FUNC 0
#define ROUND_TRIP_FUNC 1
#define IDLE_FUNC 2
#define EXE_FUNC 3
#define SCREEN_REGION_FUNC 4
#define WINDOW_TITLE_FUNC 5
#define MEDIA_FUNC 6
#define TIME_FUNC 7
#define DEFAULT_PRIORITY_0 READ_FILE_FUNC
#define DEFAULT_PRIORITY_1 ROUND_TRIP_FUNC
#define DEFAULT_PRIORITY_2 IDLE_FUNC
#define DEFAULT_PRIORITY_3 EXE_FUNC
#define DEFAULT_PRIORITY_4 SCREEN_REGION_FUNC
#define DEFAULT_PRIORITY_5 WINDOW_TITLE_FUNC
#define DEFAULT_PRIORITY_6 MEDIA_FUNC
#define DEFAULT_PRIORITY_7 TIME_FUNC
/********************************************************************************
* Data structs for each scene switching method
********************************************************************************/
struct WindowSceneSwitch {
OBSWeakSource scene;
std::string window;
OBSWeakSource transition;
bool fullscreen;
bool focus;
inline WindowSceneSwitch(OBSWeakSource scene_, const char *window_,
OBSWeakSource transition_, bool fullscreen_,
bool focus_)
: scene(scene_),
window(window_),
transition(transition_),
fullscreen(fullscreen_),
focus(focus_)
{
}
};
struct ExecutableSceneSwitch {
OBSWeakSource mScene;
OBSWeakSource mTransition;
QString mExe;
bool mInFocus;
inline ExecutableSceneSwitch(OBSWeakSource scene,
OBSWeakSource transition,
const QString &exe, bool inFocus)
: mScene(scene),
mTransition(transition),
mExe(exe),
mInFocus(inFocus)
{
}
};
struct ScreenRegionSwitch {
OBSWeakSource scene;
OBSWeakSource transition;
int minX, minY, maxX, maxY;
std::string regionStr;
inline ScreenRegionSwitch(OBSWeakSource scene_,
OBSWeakSource transition_, int minX_,
int minY_, int maxX_, int maxY_,
std::string regionStr_)
: scene(scene_),
transition(transition_),
minX(minX_),
minY(minY_),
maxX(maxX_),
maxY(maxY_),
regionStr(regionStr_)
{
}
};
struct SceneRoundTripSwitch {
OBSWeakSource scene1;
OBSWeakSource scene2;
OBSWeakSource transition;
double delay;
bool usePreviousScene;
std::string sceneRoundTripStr;
inline SceneRoundTripSwitch(OBSWeakSource scene1_,
OBSWeakSource scene2_,
OBSWeakSource transition_, double delay_,
bool usePreviousScene_, std::string str)
: scene1(scene1_),
scene2(scene2_),
transition(transition_),
delay(delay_),
usePreviousScene(usePreviousScene_),
sceneRoundTripStr(str)
{
}
};
struct RandomSwitch {
OBSWeakSource scene;
OBSWeakSource transition;
double delay;
std::string randomSwitchStr;
inline RandomSwitch(OBSWeakSource scene_, OBSWeakSource transition_,
double delay_, std::string str)
: scene(scene_),
transition(transition_),
delay(delay_),
randomSwitchStr(str)
{
}
};
struct SceneTransition {
OBSWeakSource scene1;
OBSWeakSource scene2;
OBSWeakSource transition;
std::string sceneTransitionStr;
inline SceneTransition(OBSWeakSource scene1_, OBSWeakSource scene2_,
OBSWeakSource transition_,
std::string sceneTransitionStr_)
: scene1(scene1_),
scene2(scene2_),
transition(transition_),
sceneTransitionStr(sceneTransitionStr_)
{
}
};
struct DefaultSceneTransition {
OBSWeakSource scene;
OBSWeakSource transition;
std::string sceneTransitionStr;
inline DefaultSceneTransition(OBSWeakSource scene_,
OBSWeakSource transition_,
std::string sceneTransitionStr_)
: scene(scene_),
transition(transition_),
sceneTransitionStr(sceneTransitionStr_)
{
}
};
struct FileSwitch {
OBSWeakSource scene;
OBSWeakSource transition;
std::string file;
std::string text;
bool remote = false;
bool useRegex = false;
bool useTime = false;
QDateTime lastMod;
inline FileSwitch(OBSWeakSource scene_, OBSWeakSource transition_,
const char *file_, const char *text_, bool remote_,
bool useRegex_, bool useTime_)
: scene(scene_),
transition(transition_),
file(file_),
text(text_),
remote(remote_),
useRegex(useRegex_),
useTime(useTime_),
lastMod()
{
}
};
struct FileIOData {
bool readEnabled = false;
std::string readPath;
bool writeEnabled = false;
std::string writePath;
};
struct IdleData {
bool idleEnable = false;
int time = DEFAULT_IDLE_TIME;
OBSWeakSource scene;
OBSWeakSource transition;
bool usePreviousScene;
bool alreadySwitched = false;
};
struct MediaSwitch {
OBSWeakSource scene;
OBSWeakSource source;
OBSWeakSource transition;
obs_media_state state;
int64_t time;
time_restriction restriction;
bool matched;
bool usePreviousScene;
std::string mediaSwitchStr;
inline MediaSwitch(OBSWeakSource scene_, OBSWeakSource source_,
OBSWeakSource transition_, obs_media_state state_,
time_restriction restriction_, uint64_t time_,
bool usePreviousScene_, std::string mediaSwitchStr_)
: scene(scene_),
source(source_),
transition(transition_),
state(state_),
restriction(restriction_),
time(time_),
usePreviousScene(usePreviousScene_),
mediaSwitchStr(mediaSwitchStr_)
{
}
};
struct TimeSwitch {
OBSWeakSource scene;
OBSWeakSource transition;
QTime time;
bool matched;
bool usePreviousScene;
std::string timeSwitchStr;
inline TimeSwitch(OBSWeakSource scene_, OBSWeakSource transition_,
QTime time_, bool usePreviousScene_,
std::string timeSwitchStr_)
: scene(scene_),
transition(transition_),
time(time_),
usePreviousScene(usePreviousScene_),
timeSwitchStr(timeSwitchStr_)
{
}
};
constexpr auto default_interval = 300;
constexpr auto previous_scene_name = "Previous Scene";
constexpr auto current_transition_name = "Current Transition";
typedef enum { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 } NoMatch;
typedef enum { PERSIST = 0, START = 1, STOP = 2 } StartupBehavior;
enum class AutoStartEvent {
NEVER,
RECORDING,
STREAMING,
RECORINDG_OR_STREAMING,
};
typedef struct transitionData {
std::string name = "";
int duration = 0;
} transitionData;
class SwitcherThread;
/********************************************************************************
@@ -270,55 +58,71 @@ struct SwitcherData {
std::condition_variable transitionCv;
bool stop = false;
bool verbose = false;
bool disableHints = false;
bool showFrame = false;
bool tansitionOverrideOverride = false;
int interval = DEFAULT_INTERVAL;
int interval = default_interval;
obs_source_t *waitScene = NULL; //scene during which wait started
OBSWeakSource previousScene = NULL;
OBSWeakSource PreviousScene2 = NULL;
obs_source_t *waitScene = nullptr;
OBSWeakSource previousScene = nullptr;
OBSWeakSource previousSceneHelper = nullptr;
OBSWeakSource lastRandomScene;
OBSWeakSource nonMatchingScene;
NoMatch switchIfNotMatching = NO_SWITCH;
double noMatchDelay;
double noMatchCount = 0;
StartupBehavior startupBehavior = PERSIST;
AutoStartEvent autoStartEvent = AutoStartEvent::NEVER;
std::vector<WindowSceneSwitch> windowSwitches;
double cooldown = 0.;
std::chrono::high_resolution_clock::time_point lastMatchTime;
std::deque<WindowSwitch> windowSwitches;
std::vector<std::string> ignoreIdleWindows;
std::string lastTitle;
std::vector<ScreenRegionSwitch> screenRegionSwitches;
std::vector<OBSWeakSource> pauseScenesSwitches;
std::vector<std::string> pauseWindowsSwitches;
std::deque<ScreenRegionSwitch> screenRegionSwitches;
std::vector<std::string> ignoreWindowsSwitches;
std::vector<SceneRoundTripSwitch> sceneRoundTripSwitches;
int sceneRoundTripUnitMultiplier = 1;
bool uninterruptibleSceneSequenceActive = false;
std::deque<SceneSequenceSwitch> sceneSequenceSwitches;
std::vector<RandomSwitch> randomSwitches;
std::deque<RandomSwitch> randomSwitches;
IdleData idleData;
FileIOData fileIO;
IdleData idleData;
std::vector<FileSwitch> fileSwitches;
std::deque<FileSwitch> fileSwitches;
CURL *curl = nullptr;
std::vector<ExecutableSceneSwitch> executableSwitches;
std::deque<ExecutableSwitch> executableSwitches;
bool autoStopEnable = false;
OBSWeakSource autoStopScene;
std::deque<SceneTrigger> sceneTriggers;
std::vector<SceneTransition> sceneTransitions;
std::vector<DefaultSceneTransition> defaultSceneTransitions;
std::deque<SceneTransition> sceneTransitions;
std::deque<DefaultSceneTransition> defaultSceneTransitions;
std::vector<MediaSwitch> mediaSwitches;
std::deque<MediaSwitch> mediaSwitches;
std::vector<TimeSwitch> timeSwitches;
std::deque<PauseEntry> pauseEntries;
std::deque<TimeSwitch> timeSwitches;
QDateTime liveTime;
std::deque<AudioSwitch> audioSwitches;
AudioSwitchFallback audioFallback;
std::deque<VideoSwitch> videoSwitches;
std::deque<SceneGroup> sceneGroups;
std::vector<int> functionNamesByPriority = std::vector<int>{
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_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};
struct ThreadPrio {
std::string name;
@@ -349,19 +153,31 @@ struct SwitcherData {
std::vector<int> tabOrder;
bool hotkeysRegistered = false;
obs_hotkey_id startHotkey = 0;
obs_hotkey_id stopHotkey = 0;
obs_hotkey_id toggleHotkey = 0;
bool versionChanged(obs_data_t *obj, std::string currentVersion);
void Thread();
void Start();
void Stop();
bool sceneChangedDuringWait();
bool prioFuncsValid();
bool tabOrderValid();
void resetTabOrder();
void writeSceneInfoToFile();
void setDefaultSceneTransitions();
void autoStopStreamAndRecording();
bool checkPause();
void checkSceneRoundTrip(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition,
std::unique_lock<std::mutex> &lock);
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,
@@ -380,11 +196,24 @@ struct SwitcherData {
OBSWeakSource &transition);
void checkTimeSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkAudioSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkAudioSwitchFallback(OBSWeakSource &scene,
OBSWeakSource &transition);
void checkVideoSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkNoMatchSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition, int &sleep);
void checkSwitchCooldown(bool &match);
void checkTriggers();
bool checkPause();
void checkDefaultSceneTransitions();
void saveSettings(obs_data_t *obj);
void saveWindowTitleSwitches(obs_data_t *obj);
void saveScreenRegionSwitches(obs_data_t *obj);
void savePauseSwitches(obs_data_t *obj);
void saveSceneRoundTripSwitches(obs_data_t *obj);
void saveSceneSequenceSwitches(obs_data_t *obj);
void saveSceneTransitions(obs_data_t *obj);
void saveIdleSwitches(obs_data_t *obj);
void saveExecutableSwitches(obs_data_t *obj);
@@ -392,12 +221,20 @@ struct SwitcherData {
void saveFileSwitches(obs_data_t *obj);
void saveMediaSwitches(obs_data_t *obj);
void saveTimeSwitches(obs_data_t *obj);
void saveAudioSwitches(obs_data_t *obj);
void saveSceneGroups(obs_data_t *obj);
void saveSceneTriggers(obs_data_t *obj);
void saveVideoSwitches(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 loadWindowTitleSwitches(obs_data_t *obj);
void loadScreenRegionSwitches(obs_data_t *obj);
void loadPauseSwitches(obs_data_t *obj);
void loadSceneRoundTripSwitches(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);
void loadExecutableSwitches(obs_data_t *obj);
@@ -405,117 +242,15 @@ struct SwitcherData {
void loadFileSwitches(obs_data_t *obj);
void loadMediaSwitches(obs_data_t *obj);
void loadTimeSwitches(obs_data_t *obj);
void loadAudioSwitches(obs_data_t *obj);
void loadSceneGroups(obs_data_t *obj);
void loadSceneTriggers(obs_data_t *obj);
void loadVideoSwitches(obs_data_t *obj);
void loadGeneralSettings(obs_data_t *obj);
void loadHotkeys(obs_data_t *obj);
void Prune()
{
for (size_t i = 0; i < windowSwitches.size(); i++) {
WindowSceneSwitch &s = windowSwitches[i];
if (!WeakSourceValid(s.scene) ||
!WeakSourceValid(s.transition))
windowSwitches.erase(windowSwitches.begin() +
i--);
}
void Prune();
if (nonMatchingScene && !WeakSourceValid(nonMatchingScene)) {
switchIfNotMatching = NO_SWITCH;
nonMatchingScene = nullptr;
}
for (size_t i = 0; i < randomSwitches.size(); i++) {
RandomSwitch &s = randomSwitches[i];
if (!WeakSourceValid(s.scene) ||
!WeakSourceValid(s.transition))
randomSwitches.erase(randomSwitches.begin() +
i--);
}
for (size_t i = 0; i < screenRegionSwitches.size(); i++) {
ScreenRegionSwitch &s = screenRegionSwitches[i];
if (!WeakSourceValid(s.scene) ||
!WeakSourceValid(s.transition))
screenRegionSwitches.erase(
screenRegionSwitches.begin() + i--);
}
for (size_t i = 0; i < pauseScenesSwitches.size(); i++) {
OBSWeakSource &scene = pauseScenesSwitches[i];
if (!WeakSourceValid(scene))
pauseScenesSwitches.erase(
pauseScenesSwitches.begin() + i--);
}
for (size_t i = 0; i < sceneRoundTripSwitches.size(); i++) {
SceneRoundTripSwitch &s = sceneRoundTripSwitches[i];
if (!WeakSourceValid(s.scene1) ||
(!s.usePreviousScene &&
!WeakSourceValid(s.scene2)) ||
!WeakSourceValid(s.transition))
sceneRoundTripSwitches.erase(
sceneRoundTripSwitches.begin() + i--);
}
if (!WeakSourceValid(autoStopScene)) {
autoStopScene = nullptr;
autoStopEnable = false;
}
for (size_t i = 0; i < sceneTransitions.size(); i++) {
SceneTransition &s = sceneTransitions[i];
if (!WeakSourceValid(s.scene1) ||
!WeakSourceValid(s.scene2) ||
!WeakSourceValid(s.transition))
sceneTransitions.erase(
sceneTransitions.begin() + i--);
}
for (size_t i = 0; i < defaultSceneTransitions.size(); i++) {
DefaultSceneTransition &s = defaultSceneTransitions[i];
if (!WeakSourceValid(s.scene) ||
!WeakSourceValid(s.transition))
defaultSceneTransitions.erase(
defaultSceneTransitions.begin() + i--);
}
for (size_t i = 0; i < executableSwitches.size(); i++) {
ExecutableSceneSwitch &s = executableSwitches[i];
if (!WeakSourceValid(s.mScene) ||
!WeakSourceValid(s.mTransition))
executableSwitches.erase(
executableSwitches.begin() + i--);
}
for (size_t i = 0; i < fileSwitches.size(); i++) {
FileSwitch &s = fileSwitches[i];
if (!WeakSourceValid(s.scene) ||
!WeakSourceValid(s.transition))
fileSwitches.erase(fileSwitches.begin() + i--);
}
for (size_t i = 0; i < timeSwitches.size(); i++) {
TimeSwitch &s = timeSwitches[i];
if ((!s.usePreviousScene &&
!WeakSourceValid(s.scene)) ||
!WeakSourceValid(s.transition))
timeSwitches.erase(timeSwitches.begin() + i--);
}
if (!idleData.usePreviousScene &&
!WeakSourceValid(idleData.scene) ||
!WeakSourceValid(idleData.transition)) {
idleData.idleEnable = false;
}
for (size_t i = 0; i < mediaSwitches.size(); i++) {
MediaSwitch &s = mediaSwitches[i];
if ((!s.usePreviousScene &&
!WeakSourceValid(s.scene)) ||
!WeakSourceValid(s.source) ||
!WeakSourceValid(s.transition))
mediaSwitches.erase(mediaSwitches.begin() +
i--);
}
}
inline ~SwitcherData() { Stop(); }
};

View File

@@ -1,184 +1,31 @@
#pragma once
#include <obs.hpp>
#include <QString>
#include <QLayout>
#include <QLabel>
#include <unordered_map>
#include <obs.hpp>
#include <obs-module.h>
#include <obs-frontend-api.h>
#include "switcher-data-structs.hpp"
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);
if (source)
if (source) {
obs_source_release(source);
}
return !!source;
}
static inline QString MakeSwitchName(const QString &scene, const QString &value,
const QString &transition, bool fullscreen,
bool focus)
{
QString name = QStringLiteral("[") + scene + QStringLiteral(", ") +
transition + QStringLiteral("]: ") + value;
if (fullscreen || focus) {
name += QStringLiteral(" (only if");
if (fullscreen)
name += QStringLiteral(" fullscreen");
if (fullscreen && focus)
name += QStringLiteral(" and");
if (focus)
name += QStringLiteral(" focused");
name += QStringLiteral(")");
}
return name;
}
static inline QString MakeSwitchNameExecutable(const QString &scene,
const QString &value,
const QString &transition,
bool inFocus)
{
if (!inFocus)
return QStringLiteral("[") + scene + QStringLiteral(", ") +
transition + QStringLiteral("]: ") + value;
return QStringLiteral("[") + scene + QStringLiteral(", ") + transition +
QStringLiteral("]: ") + value +
QStringLiteral(" (only if focused)");
}
static inline QString MakeScreenRegionSwitchName(const QString &scene,
const QString &transition,
int minX, int minY, int maxX,
int maxY)
{
return QStringLiteral("[") + scene + QStringLiteral(", ") + transition +
QStringLiteral("]: ") + QString::number(minX) +
QStringLiteral(", ") + QString::number(minY) +
QStringLiteral(" x ") + QString::number(maxX) +
QStringLiteral(", ") + QString::number(maxY);
}
static inline QString MakeSceneRoundTripSwitchName(const QString &scene1,
const QString &scene2,
const QString &transition,
double delay)
{
return scene1 + QStringLiteral(" -> wait for ") +
QString::number(delay) + QStringLiteral(" seconds -> ") +
scene2 + QStringLiteral(" (using ") + transition +
QStringLiteral(" transition)");
}
static inline QString MakeSceneTransitionName(const QString &scene1,
const QString &scene2,
const QString &transition)
{
return scene1 + QStringLiteral(" --- ") + transition +
QStringLiteral(" --> ") + scene2;
}
static inline QString MakeDefaultSceneTransitionName(const QString &scene,
const QString &transition)
{
return scene + QStringLiteral(" --> ") + transition;
}
static inline QString MakeRandomSwitchName(const QString &scene,
const QString &transition,
double &delay)
{
return QStringLiteral("[") + scene + QStringLiteral(", ") + transition +
QStringLiteral("]: ") + QString::number(delay) +
QStringLiteral(" seconds");
}
static inline QString MakeFileSwitchName(const QString &scene,
const QString &transition,
const QString &fileName,
const QString &text, bool useRegex,
bool useTime)
{
QString switchName = QStringLiteral("Switch to ") + scene +
QStringLiteral(" using ") + transition +
QStringLiteral(" if ") + fileName;
if (useTime)
switchName += QStringLiteral(" was modified and");
switchName += QStringLiteral(" contains");
if (useRegex)
switchName += QStringLiteral(" (RegEx): \n\"");
else
switchName += QStringLiteral(": \n\"");
if (text.length() > 30)
switchName += text.left(27) + QStringLiteral("...\"");
else
switchName += text + QStringLiteral("\"");
return switchName;
}
typedef enum {
TIME_RESTRICTION_NONE,
TIME_RESTRICTION_SHORTER,
TIME_RESTRICTION_LONGER,
TIME_RESTRICTION_REMAINING_SHORTER,
TIME_RESTRICTION_REMAINING_LONGER,
} time_restriction;
static inline QString
MakeMediaSwitchName(const QString &source, const QString &scene,
const QString &transition, obs_media_state state,
time_restriction restriction, uint64_t time)
{
QString switchName = QStringLiteral("Switch to ") + scene +
QStringLiteral(" using ") + transition +
QStringLiteral(" if ") + source +
QStringLiteral(" state is ");
if (state == OBS_MEDIA_STATE_NONE) {
switchName += QStringLiteral("none");
} else if (state == OBS_MEDIA_STATE_PLAYING) {
switchName += QStringLiteral("playing");
} else if (state == OBS_MEDIA_STATE_OPENING) {
switchName += QStringLiteral("opening");
} else if (state == OBS_MEDIA_STATE_BUFFERING) {
switchName += QStringLiteral("buffering");
} else if (state == OBS_MEDIA_STATE_PAUSED) {
switchName += QStringLiteral("paused");
} else if (state == OBS_MEDIA_STATE_STOPPED) {
switchName += QStringLiteral("stopped");
} else if (state == OBS_MEDIA_STATE_ENDED) {
switchName += QStringLiteral("ended");
} else if (state == OBS_MEDIA_STATE_ERROR) {
switchName += QStringLiteral("error");
}
if (restriction == TIME_RESTRICTION_SHORTER) {
switchName += QStringLiteral(" and time shorter than ");
} else if (restriction == TIME_RESTRICTION_LONGER) {
switchName += QStringLiteral(" and time longer than ");
} else if (restriction == TIME_RESTRICTION_REMAINING_SHORTER) {
switchName +=
QStringLiteral(" and time remaining shorter than ");
} else if (restriction == TIME_RESTRICTION_REMAINING_LONGER) {
switchName +=
QStringLiteral(" and time remaining longer than ");
}
if (restriction != TIME_RESTRICTION_NONE) {
switchName += std::to_string(time).c_str();
switchName += QStringLiteral(" ms");
}
return switchName;
}
static inline QString MakeTimeSwitchName(const QString &scene,
const QString &transition, QTime &time)
{
QString switchName = QStringLiteral("At ") + time.toString() +
QStringLiteral(" switch to ") + scene +
QStringLiteral(" using ") + transition;
return switchName;
}
static inline std::string GetWeakSourceName(obs_weak_source_t *weak_source)
{
std::string name;
@@ -192,6 +39,26 @@ 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;
@@ -250,3 +117,68 @@ static inline OBSWeakSource GetWeakTransitionByQString(const QString &name)
{
return GetWeakTransitionByName(name.toUtf8().constData());
}
static inline std::string
getNextDelim(std::string text,
std::unordered_map<std::string, QWidget *> placeholders)
{
size_t pos = std::string::npos;
std::string res = "";
for (const auto &ph : placeholders) {
size_t newPos = text.find(ph.first);
if (newPos <= pos) {
pos = newPos;
res = ph.first;
}
}
if (pos == std::string::npos) {
return "";
}
return res;
}
/**
* Populate layout with labels and widgets based on provided text
*
* @param text Text based on which labels are generated and widgets are placed.
* @param layout Layout in which the widgets and labels will be placed.
* @param placeholders Map containing a mapping of placeholder strings to widgets.
* @param addStretch Add addStretch() to layout.
*/
static inline void
placeWidgets(std::string text, QBoxLayout *layout,
std::unordered_map<std::string, QWidget *> placeholders,
bool addStretch = true)
{
std::vector<std::pair<std::string, QWidget *>> labelsWidgetsPairs;
std::string delim = getNextDelim(text, placeholders);
while (delim != "") {
size_t pos = text.find(delim);
if (pos != std::string::npos) {
labelsWidgetsPairs.emplace_back(text.substr(0, pos),
placeholders[delim]);
text.erase(0, pos + delim.length());
}
delim = getNextDelim(text, placeholders);
}
if (text != "") {
labelsWidgetsPairs.emplace_back(text, nullptr);
}
for (auto &lw : labelsWidgetsPairs) {
if (lw.first != "") {
layout->addWidget(new QLabel(lw.first.c_str()));
}
if (lw.second) {
layout->addWidget(lw.second);
}
}
if (addStretch) {
layout->addStretch();
}
}

1
src/headers/version.h Normal file
View File

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

View File

@@ -0,0 +1,245 @@
#pragma once
#include <obs.hpp>
#include <QWidget>
#include <QPaintEvent>
#include <QSharedPointer>
#include <QTimer>
#include <QMutex>
#include <QList>
class QPushButton;
class VolumeMeterTimer;
class VolumeMeter : public QWidget {
Q_OBJECT
Q_PROPERTY(QColor backgroundNominalColor READ getBackgroundNominalColor
WRITE setBackgroundNominalColor DESIGNABLE true)
Q_PROPERTY(QColor backgroundWarningColor READ getBackgroundWarningColor
WRITE setBackgroundWarningColor DESIGNABLE true)
Q_PROPERTY(QColor backgroundErrorColor READ getBackgroundErrorColor
WRITE setBackgroundErrorColor DESIGNABLE true)
Q_PROPERTY(QColor foregroundNominalColor READ getForegroundNominalColor
WRITE setForegroundNominalColor DESIGNABLE true)
Q_PROPERTY(QColor foregroundWarningColor READ getForegroundWarningColor
WRITE setForegroundWarningColor DESIGNABLE true)
Q_PROPERTY(QColor foregroundErrorColor READ getForegroundErrorColor
WRITE setForegroundErrorColor DESIGNABLE true)
Q_PROPERTY(QColor clipColor READ getClipColor WRITE setClipColor
DESIGNABLE true)
Q_PROPERTY(QColor magnitudeColor READ getMagnitudeColor WRITE
setMagnitudeColor DESIGNABLE true)
Q_PROPERTY(QColor majorTickColor READ getMajorTickColor WRITE
setMajorTickColor DESIGNABLE true)
Q_PROPERTY(QColor minorTickColor READ getMinorTickColor WRITE
setMinorTickColor DESIGNABLE true)
// Levels are denoted in dBFS.
Q_PROPERTY(qreal minimumLevel READ getMinimumLevel WRITE setMinimumLevel
DESIGNABLE true)
Q_PROPERTY(qreal warningLevel READ getWarningLevel WRITE setWarningLevel
DESIGNABLE true)
Q_PROPERTY(qreal errorLevel READ getErrorLevel WRITE setErrorLevel
DESIGNABLE true)
Q_PROPERTY(qreal clipLevel READ getClipLevel WRITE setClipLevel
DESIGNABLE true)
Q_PROPERTY(qreal minimumInputLevel READ getMinimumInputLevel WRITE
setMinimumInputLevel DESIGNABLE true)
// Rates are denoted in dB/second.
Q_PROPERTY(qreal peakDecayRate READ getPeakDecayRate WRITE
setPeakDecayRate DESIGNABLE true)
// Time in seconds for the VU meter to integrate over.
Q_PROPERTY(
qreal magnitudeIntegrationTime READ getMagnitudeIntegrationTime
WRITE setMagnitudeIntegrationTime DESIGNABLE true)
// Duration is denoted in seconds.
Q_PROPERTY(qreal peakHoldDuration READ getPeakHoldDuration WRITE
setPeakHoldDuration DESIGNABLE true)
Q_PROPERTY(qreal inputPeakHoldDuration READ getInputPeakHoldDuration
WRITE setInputPeakHoldDuration DESIGNABLE true)
private slots:
void ClipEnding();
private:
obs_volmeter_t *obs_volmeter;
static QWeakPointer<VolumeMeterTimer> updateTimer;
QSharedPointer<VolumeMeterTimer> updateTimerRef;
inline void resetLevels();
inline void handleChannelCofigurationChange();
inline bool detectIdle(uint64_t ts);
inline void calculateBallistics(uint64_t ts,
qreal timeSinceLastRedraw = 0.0);
inline void calculateBallisticsForChannel(int channelNr, uint64_t ts,
qreal timeSinceLastRedraw);
void paintInputMeter(QPainter &painter, int x, int y, int width,
int height, float peakHold);
void paintHMeter(QPainter &painter, int x, int y, int width, int height,
float magnitude, float peak, float peakHold);
void paintHTicks(QPainter &painter, int x, int y, int width,
int height);
void paintVMeter(QPainter &painter, int x, int y, int width, int height,
float magnitude, float peak, float peakHold);
void paintVTicks(QPainter &painter, int x, int y, int height);
QMutex dataMutex;
uint64_t currentLastUpdateTime = 0;
float currentMagnitude[MAX_AUDIO_CHANNELS];
float currentPeak[MAX_AUDIO_CHANNELS];
float currentInputPeak[MAX_AUDIO_CHANNELS];
QPixmap *tickPaintCache = nullptr;
int displayNrAudioChannels = 0;
float displayMagnitude[MAX_AUDIO_CHANNELS];
float displayPeak[MAX_AUDIO_CHANNELS];
float displayPeakHold[MAX_AUDIO_CHANNELS];
uint64_t displayPeakHoldLastUpdateTime[MAX_AUDIO_CHANNELS];
float displayInputPeakHold[MAX_AUDIO_CHANNELS];
uint64_t displayInputPeakHoldLastUpdateTime[MAX_AUDIO_CHANNELS];
QFont tickFont;
QColor backgroundNominalColor;
QColor backgroundWarningColor;
QColor backgroundErrorColor;
QColor foregroundNominalColor;
QColor foregroundWarningColor;
QColor foregroundErrorColor;
QColor clipColor;
QColor magnitudeColor;
QColor majorTickColor;
QColor minorTickColor;
qreal minimumLevel;
qreal warningLevel;
qreal errorLevel;
qreal clipLevel;
qreal minimumInputLevel;
qreal peakDecayRate;
qreal magnitudeIntegrationTime;
qreal peakHoldDuration;
qreal inputPeakHoldDuration;
uint64_t lastRedrawTime = 0;
int channels = 0;
bool clipping = false;
bool vertical;
public:
explicit VolumeMeter(QWidget *parent = nullptr,
obs_volmeter_t *obs_volmeter = nullptr,
bool vertical = false);
~VolumeMeter();
void setLevels(const float magnitude[MAX_AUDIO_CHANNELS],
const float peak[MAX_AUDIO_CHANNELS],
const float inputPeak[MAX_AUDIO_CHANNELS]);
QColor getBackgroundNominalColor() const;
void setBackgroundNominalColor(QColor c);
QColor getBackgroundWarningColor() const;
void setBackgroundWarningColor(QColor c);
QColor getBackgroundErrorColor() const;
void setBackgroundErrorColor(QColor c);
QColor getForegroundNominalColor() const;
void setForegroundNominalColor(QColor c);
QColor getForegroundWarningColor() const;
void setForegroundWarningColor(QColor c);
QColor getForegroundErrorColor() const;
void setForegroundErrorColor(QColor c);
QColor getClipColor() const;
void setClipColor(QColor c);
QColor getMagnitudeColor() const;
void setMagnitudeColor(QColor c);
QColor getMajorTickColor() const;
void setMajorTickColor(QColor c);
QColor getMinorTickColor() const;
void setMinorTickColor(QColor c);
qreal getMinimumLevel() const;
void setMinimumLevel(qreal v);
qreal getWarningLevel() const;
void setWarningLevel(qreal v);
qreal getErrorLevel() const;
void setErrorLevel(qreal v);
qreal getClipLevel() const;
void setClipLevel(qreal v);
qreal getMinimumInputLevel() const;
void setMinimumInputLevel(qreal v);
qreal getPeakDecayRate() const;
void setPeakDecayRate(qreal v);
qreal getMagnitudeIntegrationTime() const;
void setMagnitudeIntegrationTime(qreal v);
qreal getPeakHoldDuration() const;
void setPeakHoldDuration(qreal v);
qreal getInputPeakHoldDuration() const;
void setInputPeakHoldDuration(qreal v);
void setPeakMeterType(enum obs_peak_meter_type peakMeterType);
virtual void mousePressEvent(QMouseEvent *event) override;
protected:
void paintEvent(QPaintEvent *event) override;
};
class VolumeMeterTimer : public QTimer {
Q_OBJECT
public:
inline VolumeMeterTimer() : QTimer() {}
void AddVolControl(VolumeMeter *meter);
void RemoveVolControl(VolumeMeter *meter);
protected:
void timerEvent(QTimerEvent *event) override;
QList<VolumeMeter *> volumeMeters;
};
class QLabel;
class QSlider;
class MuteCheckBox;
class VolControl : public QWidget {
Q_OBJECT
private:
OBSSource source;
QLabel *nameLabel;
QLabel *volLabel;
VolumeMeter *volMeter;
QSlider *slider;
float levelTotal;
float levelCount;
obs_fader_t *obs_fader;
obs_volmeter_t *obs_volmeter;
bool vertical;
static void OBSVolumeChanged(void *param, float db);
static void OBSVolumeLevel(void *data,
const float magnitude[MAX_AUDIO_CHANNELS],
const float peak[MAX_AUDIO_CHANNELS],
const float inputPeak[MAX_AUDIO_CHANNELS]);
private slots:
void SliderChanged(int vol);
void updateText();
public:
explicit VolControl(OBSSource source, bool vertical = false);
~VolControl();
inline obs_source_t *GetSource() const { return source; }
QString GetName() const;
void SetName(const QString &newName);
void SetMeterDecayRate(qreal q);
void setPeakMeterType(enum obs_peak_meter_type peakMeterType);
void EnableSlider(bool enable);
QSlider *GetSlider() const;
};

View File

@@ -1,138 +1,108 @@
#include <obs-module.h>
#include <fstream>
#include <regex>
#include "headers/advanced-scene-switcher.hpp"
void startHotkeyFunc(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey,
bool pressed)
{
UNUSED_PARAMETER(id);
UNUSED_PARAMETER(data);
UNUSED_PARAMETER(hotkey);
if (pressed) {
if (!(switcher->th && switcher->th->isRunning()))
if (!(switcher->th && switcher->th->isRunning())) {
switcher->Start();
}
obs_data_array *hotkeyData = obs_hotkey_save(id);
if (hotkeyData != NULL) {
char *path = obs_module_config_path("");
std::ofstream file;
file.open(std::string(path).append(START_HOTKEY_PATH),
std::ofstream::trunc);
if (file.is_open()) {
size_t num = obs_data_array_count(hotkeyData);
for (size_t i = 0; i < num; i++) {
obs_data_t *data =
obs_data_array_item(hotkeyData, i);
std::string temp = obs_data_get_json(data);
obs_data_release(data);
file << temp;
}
file.close();
}
bfree(path);
}
obs_data_array_release(hotkeyData);
}
void stopHotkeyFunc(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey,
bool pressed)
{
UNUSED_PARAMETER(id);
UNUSED_PARAMETER(data);
UNUSED_PARAMETER(hotkey);
if (pressed) {
if (switcher->th && switcher->th->isRunning())
if (switcher->th && switcher->th->isRunning()) {
switcher->Stop();
}
obs_data_array *hotkeyData = obs_hotkey_save(id);
if (hotkeyData != NULL) {
char *path = obs_module_config_path("");
std::ofstream file;
file.open(std::string(path).append(STOP_HOTKEY_PATH),
std::ofstream::trunc);
if (file.is_open()) {
size_t num = obs_data_array_count(hotkeyData);
for (size_t i = 0; i < num; i++) {
obs_data_t *data =
obs_data_array_item(hotkeyData, i);
std::string temp = obs_data_get_json(data);
obs_data_release(data);
file << temp;
}
file.close();
}
bfree(path);
}
obs_data_array_release(hotkeyData);
}
void startStopToggleHotkeyFunc(void *data, obs_hotkey_id id,
obs_hotkey_t *hotkey, bool pressed)
{
UNUSED_PARAMETER(id);
UNUSED_PARAMETER(data);
UNUSED_PARAMETER(hotkey);
if (pressed) {
if (switcher->th && switcher->th->isRunning())
if (switcher->th && switcher->th->isRunning()) {
switcher->Stop();
else
} else {
switcher->Start();
}
obs_data_array *hotkeyData = obs_hotkey_save(id);
if (hotkeyData != NULL) {
char *path = obs_module_config_path("");
std::ofstream file;
file.open(std::string(path).append(TOGGLE_HOTKEY_PATH),
std::ofstream::trunc);
if (file.is_open()) {
size_t num = obs_data_array_count(hotkeyData);
for (size_t i = 0; i < num; i++) {
obs_data_t *data =
obs_data_array_item(hotkeyData, i);
std::string temp = obs_data_get_json(data);
obs_data_release(data);
file << temp;
}
file.close();
}
bfree(path);
}
obs_data_array_release(hotkeyData);
}
std::string loadConfigFile(std::string filename)
void registerHotkeys()
{
std::ifstream settingsFile;
char *path = obs_module_config_path("");
std::string value;
switcher->startHotkey = obs_hotkey_register_frontend(
"startSwitcherHotkey",
obs_module_text("AdvSceneSwitcher.hotkey.startSwitcherHotkey"),
startHotkeyFunc, NULL);
switcher->stopHotkey = obs_hotkey_register_frontend(
"stopSwitcherHotkey",
obs_module_text("AdvSceneSwitcher.hotkey.stopSwitcherHotkey"),
stopHotkeyFunc, NULL);
switcher->toggleHotkey = obs_hotkey_register_frontend(
"startStopToggleSwitcherHotkey",
obs_module_text(
"AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey"),
startStopToggleHotkeyFunc, NULL);
settingsFile.open(std::string(path).append(filename));
if (settingsFile.is_open()) {
settingsFile.seekg(0, std::ios::end);
value.reserve(settingsFile.tellg());
settingsFile.seekg(0, std::ios::beg);
value.assign((std::istreambuf_iterator<char>(settingsFile)),
std::istreambuf_iterator<char>());
settingsFile.close();
}
bfree(path);
return value;
switcher->hotkeysRegistered = true;
}
void loadKeybinding(obs_hotkey_id hotkeyId, std::string path)
void SwitcherData::saveHotkeys(obs_data_t *obj)
{
std::string bindings = loadConfigFile(path);
if (!bindings.empty()) {
obs_data_array_t *hotkeyData = obs_data_array_create();
obs_data_t *data = obs_data_create_from_json(bindings.c_str());
obs_data_array_insert(hotkeyData, 0, data);
obs_data_release(data);
obs_hotkey_load(hotkeyId, hotkeyData);
obs_data_array_release(hotkeyData);
}
obs_data_array_t *startHotkeyArrray =
obs_hotkey_save(switcher->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_set_array(obj, "stopHotkey", stopHotkeyArrray);
obs_data_array_release(stopHotkeyArrray);
obs_data_array_t *toggleHotkeyArrray =
obs_hotkey_save(switcher->toggleHotkey);
obs_data_set_array(obj, "toggleHotkey", toggleHotkeyArrray);
obs_data_array_release(toggleHotkeyArrray);
}
void SwitcherData::loadHotkeys(obs_data_t *obj)
{
if (!switcher->hotkeysRegistered) {
registerHotkeys();
}
obs_data_array_t *startHotkeyArrray =
obs_data_get_array(obj, "startHotkey");
obs_hotkey_load(switcher->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_data_array_release(stopHotkeyArrray);
obs_data_array_t *toggleHotkeyArrray =
obs_data_get_array(obj, "toggleHotkey");
obs_hotkey_load(switcher->toggleHotkey, toggleHotkeyArrray);
obs_data_array_release(toggleHotkeyArrray);
}

View File

@@ -247,6 +247,55 @@ std::pair<int, int> getCursorPos()
return pos;
}
bool isMaximized(std::string &title)
{
if (!ewmhIsSupported())
return false;
// Find switch in top level windows
std::vector<Window> windows = getTopLevelWindows();
for (auto &window : windows) {
XTextProperty text;
int status = XGetTextProperty(
disp(), window, &text,
XInternAtom(disp(), "_NET_WM_NAME", true));
if (status == 0)
status = XGetTextProperty(disp(), window, &text,
XInternAtom(disp(), "WM_NAME",
true));
char *name = reinterpret_cast<char *>(text.value);
if (status == 0 || name == nullptr)
continue;
// True if switch equals window
bool equals = (title == name);
// True if switch matches window
bool matches = QString::fromStdString(name).contains(
QRegularExpression(QString::fromStdString(title)));
// If found, check if switch is maximized
if (equals || matches) {
QStringList states = getStates(window);
if (!states.isEmpty()) {
// True if window is maximized vertically
bool vertical = states.contains(
"_NET_WM_STATE_MAXIMIZED_VERT");
// True if window is maximized horizontally
bool horizontal = states.contains(
"_NET_WM_STATE_MAXIMIZED_HORZ");
return (vertical && horizontal);
}
break;
}
}
return false;
}
bool isFullscreen(std::string &title)
{
if (!ewmhIsSupported())
@@ -282,14 +331,8 @@ bool isFullscreen(std::string &title)
// True if window is fullscreen
bool fullscreen = states.contains(
"_NET_WM_STATE_FULLSCREEN");
// True if window is maximized vertically
bool vertical = states.contains(
"_NET_WM_STATE_MAXIMIZED_VERT");
// True if window is maximized horizontally
bool horizontal = states.contains(
"_NET_WM_STATE_MAXIMIZED_HORZ");
return (fullscreen || (vertical && horizontal));
return (fullscreen);
}
break;
@@ -343,4 +386,4 @@ int secondsSinceLastInput()
XCloseDisplay(display);
return idle_time;
}
}

View File

@@ -1,301 +0,0 @@
#include <obs-module.h>
#include "headers/advanced-scene-switcher.hpp"
void SceneSwitcher::on_mediaSwitches_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
return;
QListWidgetItem *item = ui->mediaSwitches->item(idx);
QString mediaSceneStr = item->data(Qt::UserRole).toString();
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->mediaSwitches) {
if (mediaSceneStr.compare(s.mediaSwitchStr.c_str()) == 0) {
QString sceneName = GetWeakSourceName(s.scene).c_str();
QString sourceName =
GetWeakSourceName(s.source).c_str();
QString transitionName =
GetWeakSourceName(s.transition).c_str();
ui->mediaScenes->setCurrentText(sceneName);
ui->mediaSources->setCurrentText(sourceName);
ui->mediaTransitions->setCurrentText(transitionName);
ui->mediaStates->setCurrentIndex(s.state);
ui->mediaTimeRestrictions->setCurrentIndex(
s.restriction);
ui->mediaTime->setValue(s.time);
break;
}
}
}
void SceneSwitcher::on_mediaAdd_clicked()
{
QString sourceName = ui->mediaSources->currentText();
QString sceneName = ui->mediaScenes->currentText();
QString transitionName = ui->mediaTransitions->currentText();
obs_media_state state =
(obs_media_state)ui->mediaStates->currentIndex();
time_restriction restriction =
(time_restriction)ui->mediaTimeRestrictions->currentIndex();
uint64_t time = ui->mediaTime->value();
if (sceneName.isEmpty() || transitionName.isEmpty() ||
sourceName.isEmpty())
return;
OBSWeakSource source = GetWeakSourceByQString(sourceName);
OBSWeakSource scene = GetWeakSourceByQString(sceneName);
OBSWeakSource transition = GetWeakTransitionByQString(transitionName);
QString switchText = MakeMediaSwitchName(sourceName, sceneName,
transitionName, state,
restriction, time);
QVariant v = QVariant::fromValue(switchText);
QListWidgetItem *item =
new QListWidgetItem(switchText, ui->mediaSwitches);
item->setData(Qt::UserRole, v);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->mediaSwitches.emplace_back(
scene, source, transition, state, restriction, time,
(sceneName == QString(PREVIOUS_SCENE_NAME)),
switchText.toUtf8().constData());
}
void SceneSwitcher::on_mediaRemove_clicked()
{
QListWidgetItem *item = ui->mediaSwitches->currentItem();
if (!item)
return;
std::string mediaStr =
item->data(Qt::UserRole).toString().toUtf8().constData();
{
std::lock_guard<std::mutex> lock(switcher->m);
auto &switches = switcher->mediaSwitches;
for (auto it = switches.begin(); it != switches.end(); ++it) {
auto &s = *it;
if (s.mediaSwitchStr == mediaStr) {
switches.erase(it);
break;
}
}
}
delete item;
}
void SceneSwitcher::on_mediaUp_clicked()
{
int index = ui->mediaSwitches->currentRow();
if (index != -1 && index != 0) {
ui->mediaSwitches->insertItem(
index - 1, ui->mediaSwitches->takeItem(index));
ui->mediaSwitches->setCurrentRow(index - 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->mediaSwitches.begin() + index,
switcher->mediaSwitches.begin() + index - 1);
}
}
void SceneSwitcher::on_mediaDown_clicked()
{
int index = ui->mediaSwitches->currentRow();
if (index != -1 && index != ui->mediaSwitches->count() - 1) {
ui->mediaSwitches->insertItem(
index + 1, ui->mediaSwitches->takeItem(index));
ui->mediaSwitches->setCurrentRow(index + 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->mediaSwitches.begin() + index,
switcher->mediaSwitches.begin() + index + 1);
}
}
void SceneSwitcher::on_mediaTimeRestrictions_currentIndexChanged(int idx)
{
if (idx == -1)
return;
if ((time_restriction)ui->mediaTimeRestrictions->currentIndex() ==
TIME_RESTRICTION_NONE) {
ui->mediaTime->setDisabled(true);
} else {
ui->mediaTime->setDisabled(false);
}
}
void SwitcherData::checkMediaSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
for (MediaSwitch &mediaSwitch : mediaSwitches) {
obs_source_t *source =
obs_weak_source_get_source(mediaSwitch.source);
auto duration = obs_source_media_get_duration(source);
auto time = obs_source_media_get_time(source);
obs_media_state state = obs_source_media_get_state(source);
bool matched =
state == mediaSwitch.state &&
(mediaSwitch.restriction == TIME_RESTRICTION_NONE ||
(mediaSwitch.restriction == TIME_RESTRICTION_LONGER &&
time > mediaSwitch.time) ||
(mediaSwitch.restriction == TIME_RESTRICTION_SHORTER &&
time < mediaSwitch.time) ||
(mediaSwitch.restriction ==
TIME_RESTRICTION_REMAINING_SHORTER &&
duration > time &&
duration - time < mediaSwitch.time) ||
(mediaSwitch.restriction ==
TIME_RESTRICTION_REMAINING_LONGER &&
duration > time &&
duration - time > mediaSwitch.time));
if (matched && !mediaSwitch.matched) {
match = true;
scene = (mediaSwitch.usePreviousScene)
? previousScene
: mediaSwitch.scene;
transition = mediaSwitch.transition;
if (verbose)
blog(LOG_INFO,
"Advanced Scene Switcher meida match");
}
mediaSwitch.matched = matched;
obs_source_release(source);
}
}
void SwitcherData::saveMediaSwitches(obs_data_t *obj)
{
obs_data_array_t *mediaArray = obs_data_array_create();
for (MediaSwitch &s : switcher->mediaSwitches) {
obs_data_t *array_obj = obs_data_create();
obs_source_t *source = obs_weak_source_get_source(s.source);
obs_source_t *sceneSource = obs_weak_source_get_source(s.scene);
obs_source_t *transition =
obs_weak_source_get_source(s.transition);
if ((s.usePreviousScene || sceneSource) && source &&
transition) {
const char *sourceName = obs_source_get_name(source);
const char *sceneName =
obs_source_get_name(sceneSource);
const char *transitionName =
obs_source_get_name(transition);
obs_data_set_string(array_obj, "source", sourceName);
obs_data_set_string(array_obj, "scene",
s.usePreviousScene
? PREVIOUS_SCENE_NAME
: sceneName);
obs_data_set_string(array_obj, "transition",
transitionName);
obs_data_set_int(array_obj, "state", s.state);
obs_data_set_int(array_obj, "restriction",
s.restriction);
obs_data_set_int(array_obj, "time", s.time);
obs_data_array_push_back(mediaArray, array_obj);
}
obs_source_release(source);
obs_source_release(sceneSource);
obs_source_release(transition);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "mediaSwitches", mediaArray);
obs_data_array_release(mediaArray);
}
void SwitcherData::loadMediaSwitches(obs_data_t *obj)
{
obs_data_array_t *mediaArray = obs_data_get_array(obj, "mediaSwitches");
switcher->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);
const char *source = obs_data_get_string(array_obj, "source");
const char *scene = obs_data_get_string(array_obj, "scene");
const char *transition =
obs_data_get_string(array_obj, "transition");
obs_media_state state =
(obs_media_state)obs_data_get_int(array_obj, "state");
time_restriction restriction =
(time_restriction)obs_data_get_int(array_obj,
"restriction");
uint64_t time = obs_data_get_int(array_obj, "time");
std::string mediaStr = MakeMediaSwitchName(source, scene,
transition, state,
restriction, time)
.toUtf8()
.constData();
switcher->mediaSwitches.emplace_back(
GetWeakSourceByName(scene), GetWeakSourceByName(source),
GetWeakTransitionByName(transition), state, restriction,
time, (strcmp(scene, PREVIOUS_SCENE_NAME) == 0),
mediaStr);
obs_data_release(array_obj);
}
obs_data_array_release(mediaArray);
}
void SceneSwitcher::setupMediaTab()
{
populateSceneSelection(ui->mediaScenes, true);
populateTransitionSelection(ui->mediaTransitions);
auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */
{
QComboBox *combo = reinterpret_cast<QComboBox *>(data);
if (strcmp(obs_source_get_id(source), "ffmpeg_source") == 0) {
const char *name = obs_source_get_name(source);
combo->addItem(name);
}
return true;
};
obs_enum_sources(sourceEnum, ui->mediaSources);
ui->mediaStates->addItem("None");
ui->mediaStates->addItem("Playing");
ui->mediaStates->addItem("Opening");
ui->mediaStates->addItem("Buffering");
ui->mediaStates->addItem("Paused");
ui->mediaStates->addItem("Stopped");
ui->mediaStates->addItem("Ended");
ui->mediaStates->addItem("Error");
ui->mediaTimeRestrictions->addItem("None");
ui->mediaTimeRestrictions->addItem("Time shorter");
ui->mediaTimeRestrictions->addItem("Time longer");
ui->mediaTimeRestrictions->addItem("Time remaining shorter");
ui->mediaTimeRestrictions->addItem("Time remaining longer");
for (auto &s : switcher->mediaSwitches) {
std::string sourceName = GetWeakSourceName(s.source);
std::string sceneName = (s.usePreviousScene)
? PREVIOUS_SCENE_NAME
: GetWeakSourceName(s.scene);
std::string transitionName = GetWeakSourceName(s.transition);
QString listText = MakeMediaSwitchName(
sourceName.c_str(), sceneName.c_str(),
transitionName.c_str(), s.state, s.restriction, s.time);
QListWidgetItem *item =
new QListWidgetItem(listText, ui->mediaSwitches);
item->setData(Qt::UserRole, listText);
}
}

View File

@@ -17,15 +17,16 @@ void GetWindowList(std::vector<std::string> &windows)
for (NSDictionary *app in apps) {
// Construct string from NSString accounting for nil
std::string name([[app objectForKey:@"kCGWindowName"]
UTF8String],
[[app objectForKey:@"kCGWindowName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
std::string owner([[app objectForKey:@"kCGWindowOwnerName"]
UTF8String],
[[app objectForKey:@"kCGWindowOwnerName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
UTF8String],
[[app objectForKey:@"kCGWindowName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
std::string owner(
[[app objectForKey:@"kCGWindowOwnerName"]
UTF8String],
[[app objectForKey:@"kCGWindowOwnerName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
// Check if name exists
if (!name.empty() &&
@@ -53,15 +54,16 @@ void GetWindowList(QStringList &windows)
for (NSDictionary *app in apps) {
// Construct string from NSString accounting for nil
std::string name([[app objectForKey:@"kCGWindowName"]
UTF8String],
[[app objectForKey:@"kCGWindowName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
std::string owner([[app objectForKey:@"kCGWindowOwnerName"]
UTF8String],
[[app objectForKey:@"kCGWindowOwnerName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
UTF8String],
[[app objectForKey:@"kCGWindowName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
std::string owner(
[[app objectForKey:@"kCGWindowOwnerName"]
UTF8String],
[[app objectForKey:@"kCGWindowOwnerName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
// Check if name exists
if (!name.empty() &&
@@ -126,6 +128,13 @@ std::pair<int, int> getCursorPos()
return pos;
}
// TODO:
// not implemented on MacOS as I cannot test it
bool isMaximized(std::string &title)
{
return false;
}
bool isFullscreen(std::string &title)
{
// Check for match
@@ -137,15 +146,16 @@ bool isFullscreen(std::string &title)
for (NSDictionary *app in apps) {
// Construct string from NSString accounting for nil
std::string name([[app objectForKey:@"kCGWindowName"]
UTF8String],
[[app objectForKey:@"kCGWindowName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
std::string owner([[app objectForKey:@"kCGWindowOwnerName"]
UTF8String],
[[app objectForKey:@"kCGWindowOwnerName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
UTF8String],
[[app objectForKey:@"kCGWindowName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
std::string owner(
[[app objectForKey:@"kCGWindowOwnerName"]
UTF8String],
[[app objectForKey:@"kCGWindowOwnerName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
// True if switch equals app
bool equals = (title == name || title == owner);

View File

@@ -1,322 +0,0 @@
#include "headers/advanced-scene-switcher.hpp"
void SceneSwitcher::on_pauseScenesAdd_clicked()
{
QString sceneName = ui->pauseScenesScenes->currentText();
if (sceneName.isEmpty())
return;
OBSWeakSource source = GetWeakSourceByQString(sceneName);
QVariant v = QVariant::fromValue(sceneName);
QList<QListWidgetItem *> items =
ui->pauseScenes->findItems(sceneName, Qt::MatchExactly);
if (items.size() == 0) {
QListWidgetItem *item =
new QListWidgetItem(sceneName, ui->pauseScenes);
item->setData(Qt::UserRole, v);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->pauseScenesSwitches.emplace_back(source);
ui->pauseScenes->sortItems();
}
}
void SceneSwitcher::on_pauseScenesRemove_clicked()
{
QListWidgetItem *item = ui->pauseScenes->currentItem();
if (!item)
return;
QString pauseScene = item->data(Qt::UserRole).toString();
{
std::lock_guard<std::mutex> lock(switcher->m);
auto &switches = switcher->pauseScenesSwitches;
for (auto it = switches.begin(); it != switches.end(); ++it) {
auto &s = *it;
if (s == GetWeakSourceByQString(pauseScene)) {
switches.erase(it);
break;
}
}
}
delete item;
}
void SceneSwitcher::on_pauseWindowsAdd_clicked()
{
QString windowName = ui->pauseWindowsWindows->currentText();
if (windowName.isEmpty())
return;
QVariant v = QVariant::fromValue(windowName);
QList<QListWidgetItem *> items =
ui->pauseWindows->findItems(windowName, Qt::MatchExactly);
if (items.size() == 0) {
QListWidgetItem *item =
new QListWidgetItem(windowName, ui->pauseWindows);
item->setData(Qt::UserRole, v);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->pauseWindowsSwitches.emplace_back(
windowName.toUtf8().constData());
ui->pauseWindows->sortItems();
}
}
void SceneSwitcher::on_pauseWindowsRemove_clicked()
{
QListWidgetItem *item = ui->pauseWindows->currentItem();
if (!item)
return;
QString windowName = item->data(Qt::UserRole).toString();
{
std::lock_guard<std::mutex> lock(switcher->m);
auto &switches = switcher->pauseWindowsSwitches;
for (auto it = switches.begin(); it != switches.end(); ++it) {
auto &s = *it;
if (s == windowName.toUtf8().constData()) {
switches.erase(it);
break;
}
}
}
delete item;
}
void SceneSwitcher::on_pauseScenes_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
return;
QListWidgetItem *item = ui->pauseScenes->item(idx);
QString scene = item->data(Qt::UserRole).toString();
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->pauseScenesSwitches) {
std::string name = GetWeakSourceName(s);
if (scene.compare(name.c_str()) == 0) {
ui->pauseScenesScenes->setCurrentText(name.c_str());
break;
}
}
}
void SceneSwitcher::on_pauseWindows_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
return;
QListWidgetItem *item = ui->pauseWindows->item(idx);
QString window = item->data(Qt::UserRole).toString();
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->pauseWindowsSwitches) {
if (window.compare(s.c_str()) == 0) {
ui->pauseWindowsWindows->setCurrentText(s.c_str());
break;
}
}
}
int SceneSwitcher::PauseScenesFindByData(const QString &scene)
{
int count = ui->pauseScenes->count();
int idx = -1;
for (int i = 0; i < count; i++) {
QListWidgetItem *item = ui->pauseScenes->item(i);
QString itemRegion = item->data(Qt::UserRole).toString();
if (itemRegion == scene) {
idx = i;
break;
}
}
return idx;
}
int SceneSwitcher::PauseWindowsFindByData(const QString &window)
{
int count = ui->pauseWindows->count();
int idx = -1;
for (int i = 0; i < count; i++) {
QListWidgetItem *item = ui->pauseWindows->item(i);
QString itemRegion = item->data(Qt::UserRole).toString();
if (itemRegion == window) {
idx = i;
break;
}
}
return idx;
}
bool SwitcherData::checkPause()
{
bool pause = false;
obs_source_t *currentSource = obs_frontend_get_current_scene();
obs_weak_source_t *ws = obs_source_get_weak_source(currentSource);
for (OBSWeakSource &s : pauseScenesSwitches) {
if (s == ws) {
pause = true;
break;
}
}
obs_source_release(currentSource);
obs_weak_source_release(ws);
std::string title;
if (!pause) {
//lock.unlock();
GetCurrentWindowTitle(title);
//lock.lock();
for (std::string &window : pauseWindowsSwitches) {
if (window == title) {
pause = true;
break;
}
}
}
if (!pause) {
//lock.unlock();
GetCurrentWindowTitle(title);
//lock.lock();
for (std::string &window : pauseWindowsSwitches) {
try {
bool matches = std::regex_match(
title, std::regex(window));
if (matches) {
pause = true;
break;
}
} catch (const std::regex_error &) {
}
}
}
if (verbose && pause)
blog(LOG_INFO, "Advanced Scene Switcher pause match");
return pause;
}
void SwitcherData::savePauseSwitches(obs_data_t *obj)
{
obs_data_array_t *pauseScenesArray = obs_data_array_create();
for (OBSWeakSource &scene : switcher->pauseScenesSwitches) {
obs_data_t *array_obj = obs_data_create();
obs_source_t *source = obs_weak_source_get_source(scene);
if (source) {
const char *n = obs_source_get_name(source);
obs_data_set_string(array_obj, "pauseScene", n);
obs_data_array_push_back(pauseScenesArray, array_obj);
obs_source_release(source);
}
obs_data_release(array_obj);
}
obs_data_set_array(obj, "pauseScenes", pauseScenesArray);
obs_data_array_release(pauseScenesArray);
obs_data_array_t *pauseWindowsArray = obs_data_array_create();
for (std::string &window : switcher->pauseWindowsSwitches) {
obs_data_t *array_obj = obs_data_create();
obs_data_set_string(array_obj, "pauseWindow", window.c_str());
obs_data_array_push_back(pauseWindowsArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "pauseWindows", pauseWindowsArray);
obs_data_array_release(pauseWindowsArray);
}
void SwitcherData::loadPauseSwitches(obs_data_t *obj)
{
switcher->pauseScenesSwitches.clear();
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->pauseScenesSwitches.emplace_back(
GetWeakSourceByName(scene));
obs_data_release(array_obj);
}
obs_data_array_release(pauseScenesArray);
switcher->pauseWindowsSwitches.clear();
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->pauseWindowsSwitches.emplace_back(window);
obs_data_release(array_obj);
}
obs_data_array_release(pauseWindowsArray);
}
void SceneSwitcher::setupPauseTab()
{
populateSceneSelection(ui->pauseScenesScenes, false);
populateWindowSelection(ui->pauseWindowsWindows);
for (auto &scene : switcher->pauseScenesSwitches) {
std::string sceneName = GetWeakSourceName(scene);
QString text = QString::fromStdString(sceneName);
QListWidgetItem *item =
new QListWidgetItem(text, ui->pauseScenes);
item->setData(Qt::UserRole, text);
}
for (auto &window : switcher->pauseWindowsSwitches) {
QString text = QString::fromStdString(window);
QListWidgetItem *item =
new QListWidgetItem(text, ui->pauseWindows);
item->setData(Qt::UserRole, text);
}
}

View File

@@ -1,215 +0,0 @@
#include "headers/advanced-scene-switcher.hpp"
#include <random>
void SceneSwitcher::on_randomScenesList_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
return;
QListWidgetItem *item = ui->randomScenesList->item(idx);
QString randomSceneStr = item->data(Qt::UserRole).toString();
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->randomSwitches) {
if (randomSceneStr.compare(s.randomSwitchStr.c_str()) == 0) {
QString sceneName = GetWeakSourceName(s.scene).c_str();
QString transitionName =
GetWeakSourceName(s.transition).c_str();
ui->randomScenes->setCurrentText(sceneName);
ui->randomSpinBox->setValue(s.delay);
ui->randomTransitions->setCurrentText(transitionName);
break;
}
}
}
int SceneSwitcher::randomFindByData(const QString &randomStr)
{
int count = ui->randomScenesList->count();
for (int i = 0; i < count; i++) {
QListWidgetItem *item = ui->randomScenesList->item(i);
QString str = item->data(Qt::UserRole).toString();
if (str == randomStr)
return i;
}
return -1;
}
void SceneSwitcher::on_randomAdd_clicked()
{
QString sceneName = ui->randomScenes->currentText();
QString transitionName = ui->randomTransitions->currentText();
double delay = ui->randomSpinBox->value();
if (sceneName.isEmpty())
return;
OBSWeakSource source = GetWeakSourceByQString(sceneName);
OBSWeakSource transition = GetWeakTransitionByQString(transitionName);
QString text = MakeRandomSwitchName(sceneName, transitionName, delay);
QVariant v = QVariant::fromValue(text);
int idx = randomFindByData(text);
if (idx == -1) {
std::lock_guard<std::mutex> lock(switcher->m);
switcher->randomSwitches.emplace_back(
source, transition, delay, text.toUtf8().constData());
QListWidgetItem *item =
new QListWidgetItem(text, ui->randomScenesList);
item->setData(Qt::UserRole, v);
} else {
QListWidgetItem *item = ui->randomScenesList->item(idx);
item->setText(text);
{
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->randomSwitches) {
if (s.scene == source) {
s.delay = delay;
s.transition = transition;
s.randomSwitchStr =
text.toUtf8().constData();
;
break;
}
}
}
ui->randomScenesList->sortItems();
}
}
void SceneSwitcher::on_randomRemove_clicked()
{
QListWidgetItem *item = ui->randomScenesList->currentItem();
if (!item)
return;
std::string text =
item->data(Qt::UserRole).toString().toUtf8().constData();
{
std::lock_guard<std::mutex> lock(switcher->m);
auto &switches = switcher->randomSwitches;
for (auto it = switches.begin(); it != switches.end(); ++it) {
auto &s = *it;
if (s.randomSwitchStr == text) {
switches.erase(it);
break;
}
}
}
delete item;
}
void SwitcherData::checkRandom(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition, int &delay)
{
if (randomSwitches.size() == 0)
return;
std::vector<RandomSwitch> rs(randomSwitches);
std::random_device rng;
std::mt19937 urng(rng());
std::shuffle(rs.begin(), rs.end(), urng);
for (RandomSwitch &r : rs) {
if (r.scene == lastRandomScene)
continue;
scene = r.scene;
transition = r.transition;
delay = (int)r.delay * 1000;
match = true;
lastRandomScene = r.scene;
if (verbose)
blog(LOG_INFO, "Advanced Scene Switcher random match");
break;
}
}
void SwitcherData::saveRandomSwitches(obs_data_t *obj)
{
obs_data_array_t *randomArray = obs_data_array_create();
for (RandomSwitch &s : switcher->randomSwitches) {
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_set_double(array_obj, "delay", s.delay);
obs_data_set_string(array_obj, "str",
s.randomSwitchStr.c_str());
obs_data_array_push_back(randomArray, array_obj);
obs_source_release(source);
obs_source_release(transition);
}
obs_data_release(array_obj);
}
obs_data_set_array(obj, "randomSwitches", randomArray);
obs_data_array_release(randomArray);
}
void SwitcherData::loadRandomSwitches(obs_data_t *obj)
{
switcher->randomSwitches.clear();
obs_data_array_t *randomArray =
obs_data_get_array(obj, "randomSwitches");
size_t count = obs_data_array_count(randomArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(randomArray, i);
const char *scene = obs_data_get_string(array_obj, "scene");
const char *transition =
obs_data_get_string(array_obj, "transition");
double delay = obs_data_get_double(array_obj, "delay");
const char *str = obs_data_get_string(array_obj, "str");
switcher->randomSwitches.emplace_back(
GetWeakSourceByName(scene),
GetWeakTransitionByName(transition), delay, str);
obs_data_release(array_obj);
}
obs_data_array_release(randomArray);
}
void SceneSwitcher::setupRandomTab()
{
populateSceneSelection(ui->randomScenes, false);
populateTransitionSelection(ui->randomTransitions);
for (auto &s : switcher->randomSwitches) {
std::string sceneName = GetWeakSourceName(s.scene);
std::string transitionName = GetWeakSourceName(s.transition);
QString text = MakeRandomSwitchName(
sceneName.c_str(), transitionName.c_str(), s.delay);
QListWidgetItem *item =
new QListWidgetItem(text, ui->randomScenesList);
item->setData(Qt::UserRole, text);
}
}

743
src/scene-group.cpp Normal file
View File

@@ -0,0 +1,743 @@
#include <QVBoxLayout>
#include <QDialogButtonBox>
#include <random>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
static QMetaObject::Connection addPulse;
SceneGroupEditWidget *typeEdit = nullptr;
void SceneGroup::advanceIdx()
{
currentIdx++;
if (currentIdx >= scenes.size()) {
if (repeat) {
currentIdx = 0;
} else {
currentIdx = scenes.size() - 1;
}
}
}
OBSWeakSource SceneGroup::getNextSceneCount()
{
currentCount++;
if (currentCount >= count) {
advanceIdx();
currentCount = 0;
}
return scenes[currentIdx];
}
OBSWeakSource SceneGroup::getNextSceneTime()
{
if (lastAdvTime.time_since_epoch().count() == 0) {
lastAdvTime = std::chrono::high_resolution_clock::now();
}
auto now = std::chrono::high_resolution_clock::now();
auto passedTime = std::chrono::duration_cast<std::chrono::milliseconds>(
now - lastAdvTime);
if (passedTime.count() >= time * 1000) {
advanceIdx();
lastAdvTime = now;
}
return scenes[currentIdx];
}
OBSWeakSource SceneGroup::getNextSceneRandom()
{
if (scenes.size() == 1) {
currentIdx = 0;
return scenes[currentIdx];
}
int rIdx = lastRandomScene;
do {
rIdx = rand() % scenes.size();
} while (rIdx == lastRandomScene);
lastRandomScene = rIdx;
currentIdx = rIdx;
return scenes[currentIdx];
}
OBSWeakSource SceneGroup::getCurrentScene()
{
if (scenes.size() < currentIdx + 1) {
return nullptr;
}
return scenes[currentIdx];
}
OBSWeakSource SceneGroup::getNextScene()
{
if (scenes.empty()) {
return nullptr;
}
switch (type) {
case AdvanceCondition::Count:
return getNextSceneCount();
break;
case AdvanceCondition::Time:
return getNextSceneTime();
break;
case AdvanceCondition::Random:
return getNextSceneRandom();
break;
}
blog(LOG_INFO, "unknown scene group type!");
return nullptr;
}
bool sceneGroupNameExists(std::string name)
{
obs_source_t *source = obs_get_source_by_name(name.c_str());
if (source) {
obs_source_release(source);
return true;
}
for (SceneGroup &sg : switcher->sceneGroups) {
if (sg.name == name) {
return true;
}
}
return (name == obs_module_text(
"AdvSceneSwitcher.selectPreviousScene") ||
(name == invalid_scene_group_name));
}
void AdvSceneSwitcher::on_sceneGroupAdd_clicked()
{
std::string name;
QString format{
obs_module_text("AdvSceneSwitcher.sceneGroupTab.defaultname")};
int i = 1;
QString placeHolderText = format.arg(i);
while ((sceneGroupNameExists(placeHolderText.toUtf8().constData()))) {
placeHolderText = format.arg(++i);
}
bool accepted = SGNameDialog::AskForName(
this, obs_module_text("AdvSceneSwitcher.sceneGroupTab.add"),
obs_module_text("AdvSceneSwitcher.sceneGroupTab.add"), name,
placeHolderText);
if (!accepted) {
return;
}
if (name.empty()) {
return;
}
if (sceneGroupNameExists(name)) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.sceneGroupTab.exists"));
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->sceneGroups.emplace_back(name);
}
QString text = QString::fromStdString(name);
QListWidgetItem *item = new QListWidgetItem(text, ui->sceneGroups);
item->setData(Qt::UserRole, text);
ui->sceneGroups->setCurrentItem(item);
ui->sceneGroupAdd->disconnect(addPulse);
ui->sceneGroupHelp->setVisible(false);
emit SceneGroupAdded(QString::fromStdString(name));
}
void AdvSceneSwitcher::on_sceneGroupRemove_clicked()
{
QListWidgetItem *item = ui->sceneGroups->currentItem();
if (!item) {
return;
}
QString name;
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->sceneGroups->currentRow();
auto &sg = switcher->sceneGroups;
name = QString::fromStdString(sg[idx].name);
sg.erase(sg.begin() + idx);
}
delete item;
emit SceneGroupRemoved(name);
}
void AdvSceneSwitcher::on_sceneGroupUp_clicked()
{
int index = ui->sceneGroups->currentRow();
if (index != -1 && index != 0) {
ui->sceneGroups->insertItem(index - 1,
ui->sceneGroups->takeItem(index));
ui->sceneGroups->setCurrentRow(index - 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->sceneGroups.begin() + index,
switcher->sceneGroups.begin() + index - 1);
}
}
void AdvSceneSwitcher::on_sceneGroupDown_clicked()
{
int index = ui->sceneGroups->currentRow();
if (index != -1 && index != ui->sceneGroups->count() - 1) {
ui->sceneGroups->insertItem(index + 1,
ui->sceneGroups->takeItem(index));
ui->sceneGroups->setCurrentRow(index + 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->sceneGroups.begin() + index,
switcher->sceneGroups.begin() + index + 1);
}
}
SceneGroup *getSelectedSG(Ui_AdvSceneSwitcher *ui)
{
SceneGroup *currentSG = nullptr;
QListWidgetItem *sgItem = ui->sceneGroups->currentItem();
if (!sgItem) {
return currentSG;
}
QString sgName = sgItem->data(Qt::UserRole).toString();
for (auto &sg : switcher->sceneGroups) {
if (sgName.compare(sg.name.c_str()) == 0) {
currentSG = &sg;
break;
}
}
return currentSG;
}
void AdvSceneSwitcher::on_sceneGroupName_editingFinished()
{
bool nameValid = true;
SceneGroup *currentSG = getSelectedSG(ui.get());
if (!currentSG) {
return;
}
QString newName = ui->sceneGroupName->text();
QString oldName = QString::fromStdString(currentSG->name);
if (newName.isEmpty() || newName == oldName) {
nameValid = false;
}
if (nameValid && sceneGroupNameExists(newName.toUtf8().constData())) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.sceneGroupTab.exists"));
nameValid = false;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
if (nameValid) {
currentSG->name = newName.toUtf8().constData();
QListWidgetItem *sgItem =
ui->sceneGroups->currentItem();
sgItem->setData(Qt::UserRole, newName);
sgItem->setText(newName);
} else {
ui->sceneGroupName->setText(oldName);
}
}
emit SceneGroupRenamed(oldName, newName);
}
void AdvSceneSwitcher::SetEditSceneGroup(SceneGroup &sg)
{
ui->sceneGroupName->setText(sg.name.c_str());
ui->sceneGroupScenes->clear();
for (auto &s : sg.scenes) {
QString sceneName =
QString::fromStdString(GetWeakSourceName(s));
QVariant v = QVariant::fromValue(sceneName);
QListWidgetItem *item =
new QListWidgetItem(sceneName, ui->sceneGroupScenes);
item->setData(Qt::UserRole, v);
}
ui->sceneGroupEdit->setDisabled(false);
typeEdit->SetEditSceneGroup(&sg);
if (sg.scenes.size() == 0) {
ui->sceneGroupScenesHelp->setVisible(true);
} else {
ui->sceneGroupScenesHelp->setVisible(false);
}
}
void AdvSceneSwitcher::on_sceneGroups_currentRowChanged(int idx)
{
if (loading) {
return;
}
if (idx == -1) {
ui->sceneGroupEdit->setDisabled(true);
return;
}
QListWidgetItem *item = ui->sceneGroups->item(idx);
QString sgName = item->data(Qt::UserRole).toString();
for (auto &sg : switcher->sceneGroups) {
if (sgName.compare(sg.name.c_str()) == 0) {
SetEditSceneGroup(sg);
break;
}
}
}
void AdvSceneSwitcher::on_sceneGroupSceneAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
SceneGroup *currentSG = getSelectedSG(ui.get());
if (!currentSG) {
return;
}
QString sceneName = ui->sceneGroupSceneSelection->currentText();
if (sceneName.isEmpty()) {
return;
}
OBSWeakSource source = GetWeakSourceByQString(sceneName);
if (!source) {
return;
}
QVariant v = QVariant::fromValue(sceneName);
QListWidgetItem *item =
new QListWidgetItem(sceneName, ui->sceneGroupScenes);
item->setData(Qt::UserRole, v);
currentSG->scenes.emplace_back(source);
ui->sceneGroupScenesHelp->setVisible(false);
}
void AdvSceneSwitcher::on_sceneGroupSceneRemove_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
SceneGroup *currentSG = getSelectedSG(ui.get());
if (!currentSG) {
return;
}
int idx = ui->sceneGroupScenes->currentRow();
if (idx == -1) {
return;
}
auto &scenes = currentSG->scenes;
scenes.erase(scenes.begin() + idx);
auto item = ui->sceneGroupScenes->currentItem();
delete item;
}
void AdvSceneSwitcher::on_sceneGroupSceneUp_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
SceneGroup *currentSG = getSelectedSG(ui.get());
if (!currentSG) {
return;
}
int index = ui->sceneGroupScenes->currentRow();
if (index != -1 && index != 0) {
ui->sceneGroupScenes->insertItem(
index - 1, ui->sceneGroupScenes->takeItem(index));
ui->sceneGroupScenes->setCurrentRow(index - 1);
iter_swap(currentSG->scenes.begin() + index,
currentSG->scenes.begin() + index - 1);
}
}
void AdvSceneSwitcher::on_sceneGroupSceneDown_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
SceneGroup *currentSG = getSelectedSG(ui.get());
if (!currentSG) {
return;
}
int index = ui->sceneGroupScenes->currentRow();
if (index != -1 && index != ui->sceneGroupScenes->count() - 1) {
ui->sceneGroupScenes->insertItem(
index + 1, ui->sceneGroupScenes->takeItem(index));
ui->sceneGroupScenes->setCurrentRow(index + 1);
iter_swap(currentSG->scenes.begin() + index,
currentSG->scenes.begin() + index + 1);
}
}
void SwitcherData::saveSceneGroups(obs_data_t *obj)
{
obs_data_array_t *sceneGroupArray = obs_data_array_create();
for (SceneGroup &sg : switcher->sceneGroups) {
obs_data_t *array_obj = obs_data_create();
obs_data_set_string(array_obj, "name", sg.name.c_str());
obs_data_set_int(array_obj, "type", static_cast<int>(sg.type));
obs_data_array_t *scenesArray = obs_data_array_create();
for (OBSWeakSource s : sg.scenes) {
obs_data_t *sceneArray_obj = obs_data_create();
obs_source_t *source = obs_weak_source_get_source(s);
if (source) {
const char *name = obs_source_get_name(source);
obs_data_set_string(sceneArray_obj, "scene",
name);
}
obs_source_release(source);
obs_data_array_push_back(scenesArray, sceneArray_obj);
obs_data_release(sceneArray_obj);
}
obs_data_set_array(array_obj, "scenes", scenesArray);
obs_data_array_release(scenesArray);
obs_data_set_int(array_obj, "count", sg.count);
obs_data_set_double(array_obj, "time", sg.time);
obs_data_set_bool(array_obj, "repeat", sg.repeat);
obs_data_array_push_back(sceneGroupArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "sceneGroups", sceneGroupArray);
obs_data_array_release(sceneGroupArray);
}
void SwitcherData::loadSceneGroups(obs_data_t *obj)
{
switcher->sceneGroups.clear();
obs_data_array_t *sceneGroupArray =
obs_data_get_array(obj, "sceneGroups");
size_t count = obs_data_array_count(sceneGroupArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(sceneGroupArray, i);
const char *name = obs_data_get_string(array_obj, "name");
AdvanceCondition type = static_cast<AdvanceCondition>(
obs_data_get_int(array_obj, "type"));
std::vector<OBSWeakSource> scenes;
obs_data_array_t *scenesArray =
obs_data_get_array(array_obj, "scenes");
size_t scenesCount = obs_data_array_count(scenesArray);
for (size_t j = 0; j < scenesCount; j++) {
obs_data_t *scenesArray_obj =
obs_data_array_item(scenesArray, j);
const char *scene =
obs_data_get_string(scenesArray_obj, "scene");
scenes.emplace_back(GetWeakSourceByName(scene));
obs_data_release(scenesArray_obj);
}
obs_data_array_release(scenesArray);
int count = obs_data_get_int(array_obj, "count");
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);
obs_data_release(array_obj);
}
obs_data_array_release(sceneGroupArray);
}
void AdvSceneSwitcher::setupSceneGroupTab()
{
populateSceneSelection(ui->sceneGroupSceneSelection);
for (auto &sg : switcher->sceneGroups) {
QString text = QString::fromStdString(sg.name);
QListWidgetItem *item =
new QListWidgetItem(text, ui->sceneGroups);
item->setData(Qt::UserRole, text);
}
if (switcher->sceneGroups.size() == 0) {
addPulse = PulseWidget(ui->sceneGroupAdd, QColor(Qt::green));
ui->sceneGroupHelp->setVisible(true);
} else {
ui->sceneGroupHelp->setVisible(false);
}
typeEdit = new SceneGroupEditWidget();
ui->sceneGroupTypeEdit->addWidget(typeEdit);
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(
obs_module_text("AdvSceneSwitcher.sceneGroupTab.type.count"));
list->addItem(
obs_module_text("AdvSceneSwitcher.sceneGroupTab.type.time"));
list->addItem(
obs_module_text("AdvSceneSwitcher.sceneGroupTab.type.random"));
}
SceneGroupEditWidget::SceneGroupEditWidget()
{
type = new QComboBox();
populateTypeSelection(type);
QWidget::connect(type, SIGNAL(currentIndexChanged(int)), this,
SLOT(TypeChanged(int)));
QHBoxLayout *typeLayout = new QHBoxLayout();
typeLayout->setContentsMargins(0, 0, 0, 0);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{type}}", type}};
placeWidgets(
obs_module_text("AdvSceneSwitcher.sceneGroupTab.edit.type"),
typeLayout, widgetPlaceholders);
countEdit = new QWidget();
count = new QSpinBox();
count->setMinimum(1);
count->setMaximum(999);
QWidget::connect(count, SIGNAL(valueChanged(int)), this,
SLOT(CountChanged(int)));
QHBoxLayout *countLayout = new QHBoxLayout(countEdit);
countLayout->setContentsMargins(0, 0, 0, 0);
widgetPlaceholders = {{"{{count}}", count}};
placeWidgets(
obs_module_text("AdvSceneSwitcher.sceneGroupTab.edit.count"),
countLayout, widgetPlaceholders);
timeEdit = new QWidget();
time = new QDoubleSpinBox();
time->setMinimum(0.0);
time->setMaximum(99999999.00);
time->setSuffix("s");
QWidget::connect(time, SIGNAL(valueChanged(double)), this,
SLOT(TimeChanged(double)));
QHBoxLayout *timeLayout = new QHBoxLayout(timeEdit);
timeLayout->setContentsMargins(0, 0, 0, 0);
widgetPlaceholders = {{"{{time}}", time}};
placeWidgets(
obs_module_text("AdvSceneSwitcher.sceneGroupTab.edit.time"),
timeLayout, widgetPlaceholders);
repeat = new QCheckBox(
obs_module_text("AdvSceneSwitcher.sceneGroupTab.edit.repeat"));
QWidget::connect(repeat, SIGNAL(stateChanged(int)), this,
SLOT(RepeatChanged(int)));
random = new QLabel(
obs_module_text("AdvSceneSwitcher.sceneGroupTab.edit.random"));
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->addLayout(typeLayout);
mainLayout->addWidget(countEdit);
mainLayout->addWidget(timeEdit);
mainLayout->addWidget(repeat);
mainLayout->addWidget(random);
setLayout(mainLayout);
countEdit->setVisible(false);
timeEdit->setVisible(false);
repeat->setVisible(false);
random->setVisible(false);
sceneGroup = nullptr;
}
void SceneGroupEditWidget::ShowCurrentTypeEdit()
{
if (!sceneGroup) {
return;
}
countEdit->setVisible(false);
timeEdit->setVisible(false);
repeat->setVisible(false);
random->setVisible(false);
switch (sceneGroup->type) {
case AdvanceCondition::Count:
countEdit->setVisible(true);
repeat->setVisible(true);
break;
case AdvanceCondition::Time:
timeEdit->setVisible(true);
repeat->setVisible(true);
break;
case AdvanceCondition::Random:
random->setVisible(true);
break;
}
}
void SceneGroupEditWidget::SetEditSceneGroup(SceneGroup *sg)
{
if (!sg) {
return;
}
sceneGroup = sg;
type->setCurrentIndex(static_cast<int>(sg->type));
count->setValue(sg->count);
time->setValue(sg->time);
repeat->setChecked(sg->repeat);
ShowCurrentTypeEdit();
}
void SceneGroupEditWidget::TypeChanged(int type)
{
if (!sceneGroup) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
sceneGroup->type = static_cast<AdvanceCondition>(type);
ShowCurrentTypeEdit();
}
void SceneGroupEditWidget::CountChanged(int count)
{
if (!sceneGroup) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
sceneGroup->count = count;
}
void SceneGroupEditWidget::TimeChanged(double time)
{
if (!sceneGroup) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
sceneGroup->time = time;
}
void SceneGroupEditWidget::RepeatChanged(int state)
{
if (!sceneGroup) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
sceneGroup->repeat = state;
}

View File

@@ -1,500 +0,0 @@
#include <QFileDialog>
#include <QTextStream>
#include <QMessageBox>
#include <obs.hpp>
#include "headers/advanced-scene-switcher.hpp"
#define SECONDS_INDEX 0
#define MINUTES_INDEX 1
#define HOURS_INDEX 2
void SceneSwitcher::on_sceneRoundTripDelayUnits_currentIndexChanged(int index)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
double val = ui->sceneRoundTripSpinBox->value();
switch (switcher->sceneRoundTripUnitMultiplier) {
case 1:
val /= pow(60, index);
break;
case 60:
val /= pow(60, index - 1);
break;
case 3600:
val /= pow(60, index - 2);
break;
}
ui->sceneRoundTripSpinBox->setValue(val);
switch (index) {
case SECONDS_INDEX:
switcher->sceneRoundTripUnitMultiplier = 1;
break;
case MINUTES_INDEX:
switcher->sceneRoundTripUnitMultiplier = 60;
break;
case HOURS_INDEX:
switcher->sceneRoundTripUnitMultiplier = 3600;
break;
}
}
void SceneSwitcher::on_sceneRoundTripAdd_clicked()
{
QString scene1Name = ui->sceneRoundTripScenes1->currentText();
QString scene2Name = ui->sceneRoundTripScenes2->currentText();
QString transitionName = ui->sceneRoundTripTransitions->currentText();
if (scene1Name.isEmpty() || scene2Name.isEmpty())
return;
double delay = ui->sceneRoundTripSpinBox->value() *
switcher->sceneRoundTripUnitMultiplier;
if (scene1Name == scene2Name)
return;
OBSWeakSource source1 = GetWeakSourceByQString(scene1Name);
OBSWeakSource source2 = GetWeakSourceByQString(scene2Name);
OBSWeakSource transition = GetWeakTransitionByQString(transitionName);
QString text = MakeSceneRoundTripSwitchName(scene1Name, scene2Name,
transitionName, delay);
QVariant v = QVariant::fromValue(text);
int idx = SceneRoundTripFindByData(scene1Name);
if (idx == -1) {
QListWidgetItem *item =
new QListWidgetItem(text, ui->sceneRoundTrips);
item->setData(Qt::UserRole, v);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->sceneRoundTripSwitches.emplace_back(
source1, source2, transition, delay,
(scene2Name == QString(PREVIOUS_SCENE_NAME)),
text.toUtf8().constData());
} else {
QListWidgetItem *item = ui->sceneRoundTrips->item(idx);
item->setText(text);
{
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->sceneRoundTripSwitches) {
if (s.scene1 == source1) {
s.scene2 = source2;
s.delay = delay;
s.transition = transition;
s.usePreviousScene =
(scene2Name ==
QString(PREVIOUS_SCENE_NAME));
s.sceneRoundTripStr =
text.toUtf8().constData();
break;
}
}
}
}
}
void SceneSwitcher::on_sceneRoundTripRemove_clicked()
{
QListWidgetItem *item = ui->sceneRoundTrips->currentItem();
if (!item)
return;
std::string text =
item->data(Qt::UserRole).toString().toUtf8().constData();
{
std::lock_guard<std::mutex> lock(switcher->m);
auto &switches = switcher->sceneRoundTripSwitches;
for (auto it = switches.begin(); it != switches.end(); ++it) {
auto &s = *it;
if (s.sceneRoundTripStr == text) {
switches.erase(it);
break;
}
}
}
delete item;
}
void SceneSwitcher::on_sceneRoundTripSave_clicked()
{
QString directory = QFileDialog::getSaveFileName(
this, tr("Save Scene Round Trip to file ..."),
QDir::currentPath(), tr("Text files (*.txt)"));
if (directory.isEmpty())
return;
QFile file(directory);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
return;
obs_data_t *obj = obs_data_create();
switcher->saveSceneRoundTripSwitches(obj);
obs_data_save_json(obj, file.fileName().toUtf8().constData());
obs_data_release(obj);
}
// to be removed once support for old file format is dropped
bool oldSceneRoundTripLoad(QFile *file)
{
QTextStream in(file);
std::vector<QString> lines;
std::vector<SceneRoundTripSwitch> newSceneRoundTripSwitch;
while (!in.atEnd()) {
QString line = in.readLine();
lines.push_back(line);
if (lines.size() == 5) {
OBSWeakSource scene1 = GetWeakSourceByQString(lines[0]);
OBSWeakSource scene2 = GetWeakSourceByQString(lines[1]);
OBSWeakSource transition =
GetWeakTransitionByQString(lines[4]);
if (WeakSourceValid(scene1) &&
((lines[1] == QString(PREVIOUS_SCENE_NAME)) ||
(WeakSourceValid(scene2))) &&
WeakSourceValid(transition)) {
newSceneRoundTripSwitch.emplace_back(
SceneRoundTripSwitch(
GetWeakSourceByQString(lines[0]),
GetWeakSourceByQString(lines[1]),
GetWeakTransitionByQString(
lines[4]),
lines[2].toDouble() / 1000.,
(lines[1] ==
QString(PREVIOUS_SCENE_NAME)),
lines[3].toStdString()));
}
lines.clear();
}
}
if (lines.size() != 0 || newSceneRoundTripSwitch.size() == 0)
return false;
switcher->sceneRoundTripSwitches.clear();
switcher->sceneRoundTripSwitches = newSceneRoundTripSwitch;
return true;
}
void SceneSwitcher::on_sceneRoundTripLoad_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
QString directory = QFileDialog::getOpenFileName(
this, tr("Select a file to read Scene Round Trip from ..."),
QDir::currentPath(), tr("Text files (*.txt)"));
if (directory.isEmpty())
return;
QFile file(directory);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return;
obs_data_t *obj = obs_data_create_from_json_file(
file.fileName().toUtf8().constData());
if (!obj) {
QMessageBox Msgbox;
QString txt =
"Advanced Scene Switcher failed to import settings!\n";
txt += "Falling back to old format.";
Msgbox.setText(txt);
Msgbox.exec();
if (oldSceneRoundTripLoad(&file)) {
QString txt =
"Advanced Scene Switcher settings imported successfully!\n";
txt += "Please resave settings as support for old format will be dropped in next version.";
Msgbox.setText(txt);
Msgbox.exec();
close();
} else {
Msgbox.setText(
"Advanced Scene Switcher failed to import settings!");
Msgbox.exec();
}
return;
}
switcher->loadSceneRoundTripSwitches(obj);
obs_data_release(obj);
QMessageBox Msgbox;
Msgbox.setText(
"Advanced Scene Switcher settings imported successfully!");
Msgbox.exec();
close();
}
void SwitcherData::checkSceneRoundTrip(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition,
std::unique_lock<std::mutex> &lock)
{
bool sceneRoundTripActive = false;
obs_source_t *currentSource = obs_frontend_get_current_scene();
obs_weak_source_t *ws = obs_source_get_weak_source(currentSource);
for (SceneRoundTripSwitch &s : sceneRoundTripSwitches) {
if (s.scene1 == ws) {
sceneRoundTripActive = true;
int dur = s.delay * 1000 - interval;
if (dur > 0) {
waitScene = currentSource;
if (verbose)
blog(LOG_INFO,
"Advanced Scene Switcher sequence sleep %d",
dur);
cv.wait_for(lock,
std::chrono::milliseconds(dur));
waitScene = nullptr;
}
obs_source_t *currentSource2 =
obs_frontend_get_current_scene();
// only switch if user hasn't changed scene manually
if (currentSource == currentSource2) {
match = true;
scene = (s.usePreviousScene) ? previousScene
: s.scene2;
transition = s.transition;
}
obs_source_release(currentSource2);
break;
}
}
obs_source_release(currentSource);
obs_weak_source_release(ws);
}
void SceneSwitcher::on_sceneRoundTrips_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
return;
QListWidgetItem *item = ui->sceneRoundTrips->item(idx);
QString sceneRoundTrip = item->text();
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->sceneRoundTripSwitches) {
if (sceneRoundTrip.compare(s.sceneRoundTripStr.c_str()) == 0) {
std::string scene1 = GetWeakSourceName(s.scene1);
std::string scene2 = GetWeakSourceName(s.scene2);
std::string transitionName =
GetWeakSourceName(s.transition);
double delay = s.delay /
switcher->sceneRoundTripUnitMultiplier;
ui->sceneRoundTripScenes1->setCurrentText(
scene1.c_str());
ui->sceneRoundTripScenes2->setCurrentText(
scene2.c_str());
ui->sceneRoundTripTransitions->setCurrentText(
transitionName.c_str());
ui->sceneRoundTripSpinBox->setValue(delay);
break;
}
}
}
int SceneSwitcher::SceneRoundTripFindByData(const QString &scene1)
{
QRegExp rx(scene1 + " ->.*");
int count = ui->sceneRoundTrips->count();
int idx = -1;
for (int i = 0; i < count; i++) {
QListWidgetItem *item = ui->sceneRoundTrips->item(i);
QString itemString = item->data(Qt::UserRole).toString();
if (rx.exactMatch(itemString)) {
idx = i;
break;
}
}
return idx;
}
void SceneSwitcher::on_sceneRoundTripUp_clicked()
{
int index = ui->sceneRoundTrips->currentRow();
if (index != -1 && index != 0) {
ui->sceneRoundTrips->insertItem(
index - 1, ui->sceneRoundTrips->takeItem(index));
ui->sceneRoundTrips->setCurrentRow(index - 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->sceneRoundTripSwitches.begin() + index,
switcher->sceneRoundTripSwitches.begin() + index - 1);
}
}
void SceneSwitcher::on_sceneRoundTripDown_clicked()
{
int index = ui->sceneRoundTrips->currentRow();
if (index != -1 && index != ui->sceneRoundTrips->count() - 1) {
ui->sceneRoundTrips->insertItem(
index + 1, ui->sceneRoundTrips->takeItem(index));
ui->sceneRoundTrips->setCurrentRow(index + 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->sceneRoundTripSwitches.begin() + index,
switcher->sceneRoundTripSwitches.begin() + index + 1);
}
}
void SwitcherData::saveSceneRoundTripSwitches(obs_data_t *obj)
{
obs_data_array_t *sceneRoundTripArray = obs_data_array_create();
for (SceneRoundTripSwitch &s : switcher->sceneRoundTripSwitches) {
obs_data_t *array_obj = obs_data_create();
obs_source_t *source1 = obs_weak_source_get_source(s.scene1);
obs_source_t *source2 = obs_weak_source_get_source(s.scene2);
obs_source_t *transition =
obs_weak_source_get_source(s.transition);
if (source1 && (s.usePreviousScene || 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, "sceneRoundTripScene1",
sceneName1);
obs_data_set_string(array_obj, "sceneRoundTripScene2",
s.usePreviousScene
? PREVIOUS_SCENE_NAME
: sceneName2);
obs_data_set_string(array_obj, "transition",
transitionName);
obs_data_set_double(array_obj, "delay", s.delay);
obs_data_set_string(array_obj, "sceneRoundTripStr",
s.sceneRoundTripStr.c_str());
obs_data_array_push_back(sceneRoundTripArray,
array_obj);
obs_source_release(source1);
obs_source_release(source2);
obs_source_release(transition);
}
obs_data_release(array_obj);
}
obs_data_set_array(obj, "sceneRoundTrip", sceneRoundTripArray);
obs_data_array_release(sceneRoundTripArray);
}
void SwitcherData::loadSceneRoundTripSwitches(obs_data_t *obj)
{
switcher->sceneRoundTripSwitches.clear();
obs_data_array_t *sceneRoundTripArray =
obs_data_get_array(obj, "sceneRoundTrip");
size_t count = obs_data_array_count(sceneRoundTripArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj =
obs_data_array_item(sceneRoundTripArray, i);
const char *scene1 =
obs_data_get_string(array_obj, "sceneRoundTripScene1");
const char *scene2 =
obs_data_get_string(array_obj, "sceneRoundTripScene2");
const char *transition =
obs_data_get_string(array_obj, "transition");
double delay = 0;
// To be removed in future version
// to be compatible with older versions
if (!obs_data_has_user_value(array_obj, "delay")) {
delay = double(obs_data_get_int(array_obj,
"sceneRoundTripDelay"));
delay = delay * 1000. +
double(obs_data_get_int(
array_obj, "sceneRoundTripDelayMs"));
delay /= 1000.;
} else {
delay = obs_data_get_double(array_obj, "delay");
}
std::string str = MakeSceneRoundTripSwitchName(
scene1, scene2, transition, delay)
.toUtf8()
.constData();
const char *sceneRoundTripStr = str.c_str();
switcher->sceneRoundTripSwitches.emplace_back(
GetWeakSourceByName(scene1),
GetWeakSourceByName(scene2),
GetWeakTransitionByName(transition), delay,
(strcmp(scene2, PREVIOUS_SCENE_NAME) == 0),
sceneRoundTripStr);
obs_data_release(array_obj);
}
obs_data_array_release(sceneRoundTripArray);
}
void SceneSwitcher::setupSequenceTab()
{
populateSceneSelection(ui->sceneRoundTripScenes1, false);
populateSceneSelection(ui->sceneRoundTripScenes2, true);
populateTransitionSelection(ui->sceneRoundTripTransitions);
double smallestDelay = double(switcher->interval) / 1000;
for (auto &s : switcher->sceneRoundTripSwitches) {
std::string sceneName1 = GetWeakSourceName(s.scene1);
std::string sceneName2 = (s.usePreviousScene)
? PREVIOUS_SCENE_NAME
: GetWeakSourceName(s.scene2);
std::string transitionName = GetWeakSourceName(s.transition);
QString text = MakeSceneRoundTripSwitchName(
sceneName1.c_str(), sceneName2.c_str(),
transitionName.c_str(), s.delay);
QListWidgetItem *item =
new QListWidgetItem(text, ui->sceneRoundTrips);
item->setData(Qt::UserRole, text);
if (s.delay < smallestDelay)
smallestDelay = s.delay;
}
(smallestDelay * 1000 < switcher->interval)
? ui->intervalWarning->setVisible(true)
: ui->intervalWarning->setVisible(false);
ui->sceneRoundTripDelayUnits->addItem("seconds");
ui->sceneRoundTripDelayUnits->addItem("minutes");
ui->sceneRoundTripDelayUnits->addItem("hours");
switch (switcher->sceneRoundTripUnitMultiplier) {
case 1:
ui->sceneRoundTripDelayUnits->setCurrentIndex(0);
break;
case 60:
ui->sceneRoundTripDelayUnits->setCurrentIndex(1);
break;
case 3600:
ui->sceneRoundTripDelayUnits->setCurrentIndex(2);
break;
default:
break;
}
}

View File

@@ -1,515 +0,0 @@
#include "headers/advanced-scene-switcher.hpp"
void SceneSwitcher::on_transitionsAdd_clicked()
{
QString scene1Name = ui->transitionsScene1->currentText();
QString scene2Name = ui->transitionsScene2->currentText();
QString transitionName = ui->transitionsTransitions->currentText();
if (scene1Name.isEmpty() || scene2Name.isEmpty())
return;
if (scene1Name == scene2Name)
return;
OBSWeakSource source1 = GetWeakSourceByQString(scene1Name);
OBSWeakSource source2 = GetWeakSourceByQString(scene2Name);
OBSWeakSource transition = GetWeakTransitionByQString(transitionName);
QString text =
MakeSceneTransitionName(scene1Name, scene2Name, transitionName);
QVariant v = QVariant::fromValue(text);
int idx = SceneTransitionsFindByData(scene1Name, scene2Name);
if (idx == -1) {
QListWidgetItem *item =
new QListWidgetItem(text, ui->sceneTransitions);
item->setData(Qt::UserRole, v);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->sceneTransitions.emplace_back(
source1, source2, transition,
text.toUtf8().constData());
} else {
QListWidgetItem *item = ui->sceneTransitions->item(idx);
item->setText(text);
{
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->sceneTransitions) {
if (s.scene1 == source1 &&
s.scene2 == source2) {
s.transition = transition;
s.sceneTransitionStr =
text.toUtf8().constData();
break;
}
}
}
ui->sceneTransitions->sortItems();
}
}
void SceneSwitcher::on_transitionsRemove_clicked()
{
QListWidgetItem *item = ui->sceneTransitions->currentItem();
if (!item)
return;
std::string text =
item->data(Qt::UserRole).toString().toUtf8().constData();
{
std::lock_guard<std::mutex> lock(switcher->m);
auto &switches = switcher->sceneTransitions;
for (auto it = switches.begin(); it != switches.end(); ++it) {
auto &s = *it;
if (s.sceneTransitionStr == text) {
switches.erase(it);
break;
}
}
}
delete item;
}
void SceneSwitcher::on_defaultTransitionsAdd_clicked()
{
QString sceneName = ui->defaultTransitionsScene->currentText();
QString transitionName =
ui->defaultTransitionsTransitions->currentText();
if (sceneName.isEmpty() || transitionName.isEmpty())
return;
OBSWeakSource source = GetWeakSourceByQString(sceneName);
OBSWeakSource transition = GetWeakTransitionByQString(transitionName);
QString text =
MakeDefaultSceneTransitionName(sceneName, transitionName);
QVariant v = QVariant::fromValue(text);
int idx = DefaultTransitionsFindByData(sceneName);
if (idx == -1) {
QListWidgetItem *item =
new QListWidgetItem(text, ui->defaultTransitions);
item->setData(Qt::UserRole, v);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->defaultSceneTransitions.emplace_back(
source, transition, text.toUtf8().constData());
} else {
QListWidgetItem *item = ui->defaultTransitions->item(idx);
item->setText(text);
{
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->defaultSceneTransitions) {
if (s.scene == source) {
s.transition = transition;
s.sceneTransitionStr =
text.toUtf8().constData();
break;
}
}
}
ui->defaultTransitions->sortItems();
}
}
void SceneSwitcher::on_defaultTransitionsRemove_clicked()
{
QListWidgetItem *item = ui->defaultTransitions->currentItem();
if (!item)
return;
std::string text =
item->data(Qt::UserRole).toString().toUtf8().constData();
{
std::lock_guard<std::mutex> lock(switcher->m);
auto &switches = switcher->defaultSceneTransitions;
for (auto it = switches.begin(); it != switches.end(); ++it) {
auto &s = *it;
if (s.sceneTransitionStr == text) {
switches.erase(it);
break;
}
}
}
delete item;
}
void SwitcherData::setDefaultSceneTransitions()
{
obs_source_t *currentSource = obs_frontend_get_current_scene();
obs_weak_source_t *ws = obs_source_get_weak_source(currentSource);
for (DefaultSceneTransition &s : defaultSceneTransitions) {
if (s.scene == ws) {
obs_source_t *transition =
obs_weak_source_get_source(s.transition);
//This might cancel the current transition
//There is no way to be sure when the previous transition finished
obs_frontend_set_current_transition(transition);
if (verbose)
blog(LOG_INFO,
"Advanced Scene Switcher default transition set");
obs_source_release(transition);
break;
}
}
obs_source_release(currentSource);
obs_weak_source_release(ws);
}
int SceneSwitcher::SceneTransitionsFindByData(const QString &scene1,
const QString &scene2)
{
QRegExp rx(scene1 + " --- .* --> " + scene2);
int count = ui->sceneTransitions->count();
int idx = -1;
for (int i = 0; i < count; i++) {
QListWidgetItem *item = ui->sceneTransitions->item(i);
QString itemString = item->data(Qt::UserRole).toString();
if (rx.exactMatch(itemString)) {
idx = i;
break;
}
}
return idx;
}
int SceneSwitcher::DefaultTransitionsFindByData(const QString &scene)
{
QRegExp rx(scene + " --> .*");
int count = ui->defaultTransitions->count();
int idx = -1;
for (int i = 0; i < count; i++) {
QListWidgetItem *item = ui->defaultTransitions->item(i);
QString itemString = item->data(Qt::UserRole).toString();
if (rx.exactMatch(itemString)) {
idx = i;
break;
}
}
return idx;
}
void SceneSwitcher::on_sceneTransitions_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
return;
QListWidgetItem *item = ui->sceneTransitions->item(idx);
QString sceneTransition = item->data(Qt::UserRole).toString();
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->sceneTransitions) {
if (sceneTransition.compare(s.sceneTransitionStr.c_str()) ==
0) {
std::string scene1 = GetWeakSourceName(s.scene1);
std::string scene2 = GetWeakSourceName(s.scene2);
std::string transitionName =
GetWeakSourceName(s.transition);
ui->transitionsScene1->setCurrentText(scene1.c_str());
ui->transitionsScene2->setCurrentText(scene2.c_str());
ui->transitionsTransitions->setCurrentText(
transitionName.c_str());
break;
}
}
}
void SceneSwitcher::on_defaultTransitions_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
return;
QListWidgetItem *item = ui->defaultTransitions->item(idx);
QString sceneTransition = item->data(Qt::UserRole).toString();
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->defaultSceneTransitions) {
if (sceneTransition.compare(s.sceneTransitionStr.c_str()) ==
0) {
std::string scene = GetWeakSourceName(s.scene);
std::string transitionName =
GetWeakSourceName(s.transition);
ui->defaultTransitionsScene->setCurrentText(
scene.c_str());
ui->defaultTransitionsTransitions->setCurrentText(
transitionName.c_str());
break;
}
}
}
void SceneSwitcher::on_transitionOverridecheckBox_stateChanged(int state)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
if (!state) {
switcher->tansitionOverrideOverride = false;
} else {
switcher->tansitionOverrideOverride = true;
}
}
obs_weak_source_t *getNextTransition(obs_weak_source_t *scene1,
obs_weak_source_t *scene2)
{
obs_weak_source_t *ws = nullptr;
if (scene1 && scene2) {
for (SceneTransition &t : switcher->sceneTransitions) {
if (t.scene1 == scene1 && t.scene2 == scene2)
ws = t.transition;
}
obs_weak_source_addref(ws);
}
return ws;
}
void overwriteTransitionOverride(obs_weak_source_t *sceneWs,
obs_source_t *transition, transitionData &td)
{
obs_source_t *scene = obs_weak_source_get_source(sceneWs);
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");
const char *name = obs_source_get_name(transition);
int duration = obs_frontend_get_transition_duration();
obs_data_set_string(data, "transition", name);
obs_data_set_int(data, "transition_duration", duration);
obs_data_release(data);
obs_source_release(scene);
}
void restoreTransitionOverride(obs_source_t *scene, transitionData td)
{
obs_data_t *data = obs_source_get_private_settings(scene);
obs_data_set_string(data, "transition", td.name.c_str());
obs_data_set_int(data, "transition_duration", td.duration);
obs_data_release(data);
}
void setNextTransition(OBSWeakSource &targetScene, obs_source_t *currentSource,
OBSWeakSource &transition,
bool &transitionOverrideOverride, transitionData &td)
{
obs_weak_source_t *currentScene =
obs_source_get_weak_source(currentSource);
obs_weak_source_t *nextTransitionWs =
getNextTransition(currentScene, targetScene);
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);
}
if (nextTransition) {
obs_frontend_set_current_transition(nextTransition);
}
if (transitionOverrideOverride)
overwriteTransitionOverride(targetScene, nextTransition, td);
obs_weak_source_release(nextTransitionWs);
obs_source_release(nextTransition);
}
void SwitcherData::saveSceneTransitions(obs_data_t *obj)
{
obs_data_array_t *sceneTransitionsArray = obs_data_array_create();
for (SceneTransition &s : switcher->sceneTransitions) {
obs_data_t *array_obj = obs_data_create();
obs_source_t *source1 = obs_weak_source_get_source(s.scene1);
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_set_string(array_obj, "Str",
s.sceneTransitionStr.c_str());
obs_data_array_push_back(sceneTransitionsArray,
array_obj);
obs_source_release(source1);
obs_source_release(source2);
obs_source_release(transition);
}
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) {
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_set_string(array_obj, "Str",
s.sceneTransitionStr.c_str());
obs_data_array_push_back(defaultTransitionsArray,
array_obj);
obs_source_release(source);
obs_source_release(transition);
}
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);
}
void SwitcherData::loadSceneTransitions(obs_data_t *obj)
{
switcher->sceneTransitions.clear();
obs_data_array_t *sceneTransitionsArray =
obs_data_get_array(obj, "sceneTransitions");
size_t count = obs_data_array_count(sceneTransitionsArray);
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");
const char *sceneTransitionsStr =
obs_data_get_string(array_obj, "Str");
switcher->sceneTransitions.emplace_back(
GetWeakSourceByName(scene1),
GetWeakSourceByName(scene2),
GetWeakTransitionByName(transition),
sceneTransitionsStr);
obs_data_release(array_obj);
}
obs_data_array_release(sceneTransitionsArray);
switcher->defaultSceneTransitions.clear();
obs_data_array_t *defaultTransitionsArray =
obs_data_get_array(obj, "defaultTransitions");
count = obs_data_array_count(defaultTransitionsArray);
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");
const char *sceneTransitionsStr =
obs_data_get_string(array_obj, "Str");
switcher->defaultSceneTransitions.emplace_back(
GetWeakSourceByName(scene),
GetWeakTransitionByName(transition),
sceneTransitionsStr);
obs_data_release(array_obj);
}
obs_data_array_release(defaultTransitionsArray);
switcher->tansitionOverrideOverride =
obs_data_get_bool(obj, "tansitionOverrideOverride");
}
void SceneSwitcher::setupTransitionsTab()
{
populateSceneSelection(ui->transitionsScene1, false);
populateSceneSelection(ui->transitionsScene2, false);
populateSceneSelection(ui->defaultTransitionsScene, false);
populateTransitionSelection(ui->transitionsTransitions);
populateTransitionSelection(ui->defaultTransitionsTransitions);
for (auto &s : switcher->sceneTransitions) {
std::string sceneName1 = GetWeakSourceName(s.scene1);
std::string sceneName2 = GetWeakSourceName(s.scene2);
std::string transitionName = GetWeakSourceName(s.transition);
QString text = MakeSceneTransitionName(sceneName1.c_str(),
sceneName2.c_str(),
transitionName.c_str());
QListWidgetItem *item =
new QListWidgetItem(text, ui->sceneTransitions);
item->setData(Qt::UserRole, text);
}
//(transitionDurationLongerThanInterval(switcher->interval))
// ? ui->transitionWarning->setVisible(true)
// : ui->transitionWarning->setVisible(false);
for (auto &s : switcher->defaultSceneTransitions) {
std::string sceneName = GetWeakSourceName(s.scene);
std::string transitionName = GetWeakSourceName(s.transition);
QString text = MakeDefaultSceneTransitionName(
sceneName.c_str(), transitionName.c_str());
QListWidgetItem *item =
new QListWidgetItem(text, ui->defaultTransitions);
item->setData(Qt::UserRole, text);
}
ui->transitionOverridecheckBox->setChecked(
switcher->tansitionOverrideOverride);
}

638
src/scene-trigger.cpp Normal file
View File

@@ -0,0 +1,638 @@
#include <thread>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
bool SceneTrigger::pause = false;
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::on_triggerAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->sceneTriggers.emplace_back();
listAddClicked(ui->sceneTriggers,
new SceneTriggerWidget(this,
&switcher->sceneTriggers.back()),
ui->triggerAdd, &addPulse);
ui->triggerHelp->setVisible(false);
}
void AdvSceneSwitcher::on_triggerRemove_clicked()
{
QListWidgetItem *item = ui->sceneTriggers->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->sceneTriggers->currentRow();
auto &switches = switcher->sceneTriggers;
switches.erase(switches.begin() + idx);
}
delete item;
}
void AdvSceneSwitcher::on_triggerUp_clicked()
{
int index = ui->sceneTriggers->currentRow();
if (!listMoveUp(ui->sceneTriggers)) {
return;
}
SceneTriggerWidget *s1 =
(SceneTriggerWidget *)ui->sceneTriggers->itemWidget(
ui->sceneTriggers->item(index));
SceneTriggerWidget *s2 =
(SceneTriggerWidget *)ui->sceneTriggers->itemWidget(
ui->sceneTriggers->item(index - 1));
SceneTriggerWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->sceneTriggers[index],
switcher->sceneTriggers[index - 1]);
}
void AdvSceneSwitcher::on_triggerDown_clicked()
{
int index = ui->sceneTriggers->currentRow();
if (!listMoveDown(ui->sceneTriggers)) {
return;
}
SceneTriggerWidget *s1 =
(SceneTriggerWidget *)ui->sceneTriggers->itemWidget(
ui->sceneTriggers->item(index));
SceneTriggerWidget *s2 =
(SceneTriggerWidget *)ui->sceneTriggers->itemWidget(
ui->sceneTriggers->item(index + 1));
SceneTriggerWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->sceneTriggers[index],
switcher->sceneTriggers[index + 1]);
}
void SceneTrigger::logMatch()
{
std::string sceneName = "";
std::string statusName = "";
std::string actionName = "";
switch (triggerType) {
case sceneTriggerType::NONE:
statusName = "NONE";
break;
case sceneTriggerType::SCENE_ACTIVE:
statusName = "SCENE ACTIVE";
break;
case sceneTriggerType::SCENE_INACTIVE:
statusName = "SCENE INACTIVE";
break;
case sceneTriggerType::SCENE_LEAVE:
statusName = "SCENE LEAVE";
break;
default:
break;
}
switch (triggerAction) {
case sceneTriggerAction::NONE:
actionName = "NONE";
break;
case sceneTriggerAction::START_RECORDING:
actionName = "START RECORDING";
break;
case sceneTriggerAction::PAUSE_RECORDING:
actionName = "PAUSE RECORDING";
break;
case sceneTriggerAction::UNPAUSE_RECORDING:
actionName = "UNPAUSE RECORDING";
break;
case sceneTriggerAction::STOP_RECORDING:
actionName = "STOP RECORDING";
break;
case sceneTriggerAction::START_STREAMING:
actionName = "START STREAMING";
break;
case sceneTriggerAction::STOP_STREAMING:
actionName = "STOP STREAMING";
break;
case sceneTriggerAction::START_REPLAY_BUFFER:
actionName = "START REPLAY BUFFER";
break;
case sceneTriggerAction::STOP_REPLAY_BUFFER:
actionName = "STOP REPLAY BUFFER";
break;
case sceneTriggerAction::MUTE_SOURCE:
actionName = "MUTE (" + GetWeakSourceName(audioSource) + ")";
break;
case sceneTriggerAction::UNMUTE_SOURCE:
actionName = "UNMUTE (" + GetWeakSourceName(audioSource) + ")";
break;
case sceneTriggerAction::START_SWITCHER:
actionName = "START SCENE SWITCHER";
break;
case sceneTriggerAction::STOP_SWITCHER:
actionName = "STOP SCENE SWITCHER";
break;
default:
actionName = "UNKOWN";
break;
}
blog(LOG_INFO,
"scene '%s' in status '%s' triggering action '%s' after %f seconds",
GetWeakSourceName(scene).c_str(), statusName.c_str(),
actionName.c_str(), duration);
}
void frontEndActionThread(sceneTriggerAction action, double delay)
{
long long mil = delay * 1000;
std::this_thread::sleep_for(std::chrono::milliseconds(mil));
switch (action) {
case sceneTriggerAction::NONE:
break;
case sceneTriggerAction::START_RECORDING:
obs_frontend_recording_start();
break;
case sceneTriggerAction::PAUSE_RECORDING:
obs_frontend_recording_pause(true);
break;
case sceneTriggerAction::UNPAUSE_RECORDING:
obs_frontend_recording_pause(false);
break;
case sceneTriggerAction::STOP_RECORDING:
obs_frontend_recording_stop();
break;
case sceneTriggerAction::START_STREAMING:
obs_frontend_streaming_start();
break;
case sceneTriggerAction::STOP_STREAMING:
obs_frontend_streaming_stop();
break;
case sceneTriggerAction::START_REPLAY_BUFFER:
obs_frontend_replay_buffer_start();
break;
case sceneTriggerAction::STOP_REPLAY_BUFFER:
obs_frontend_replay_buffer_stop();
break;
default:
blog(LOG_WARNING, "ignoring unexpected frontend action '%d'",
static_cast<int>(action));
break;
}
}
void muteThread(OBSWeakSource source, double delay, bool mute)
{
long long mil = delay * 1000;
std::this_thread::sleep_for(std::chrono::milliseconds(mil));
auto s = obs_weak_source_get_source(source);
obs_source_set_muted(s, mute);
obs_source_release(s);
}
void statusThread(double delay, bool stop)
{
long long mil = delay * 1000;
std::this_thread::sleep_for(std::chrono::milliseconds(mil));
if (stop) {
switcher->Stop();
} else {
switcher->Start();
}
}
bool isFrontendAction(sceneTriggerAction triggerAction)
{
return triggerAction == sceneTriggerAction::START_RECORDING ||
triggerAction == sceneTriggerAction::PAUSE_RECORDING ||
triggerAction == sceneTriggerAction::UNPAUSE_RECORDING ||
triggerAction == sceneTriggerAction::STOP_RECORDING ||
triggerAction == sceneTriggerAction::START_STREAMING ||
triggerAction == sceneTriggerAction::STOP_STREAMING ||
triggerAction == sceneTriggerAction::START_REPLAY_BUFFER ||
triggerAction == sceneTriggerAction::STOP_REPLAY_BUFFER;
}
bool isAudioAction(sceneTriggerAction t)
{
return t == sceneTriggerAction::MUTE_SOURCE ||
t == sceneTriggerAction::UNMUTE_SOURCE;
}
bool isSwitcherStatusAction(sceneTriggerAction t)
{
return t == sceneTriggerAction::START_SWITCHER ||
t == sceneTriggerAction::STOP_SWITCHER;
}
void SceneTrigger::performAction()
{
if (triggerAction == sceneTriggerAction::NONE) {
return;
}
std::thread t;
if (isFrontendAction(triggerAction)) {
t = std::thread(frontEndActionThread, triggerAction, duration);
} else if (isAudioAction(triggerAction)) {
bool mute = triggerAction == sceneTriggerAction::MUTE_SOURCE;
t = std::thread(muteThread, audioSource, duration, mute);
} else if (isSwitcherStatusAction(triggerAction)) {
bool stop = triggerAction == sceneTriggerAction::STOP_SWITCHER;
t = std::thread(statusThread, duration, stop);
} else {
blog(LOG_WARNING, "ignoring unkown action '%d'",
static_cast<int>(triggerAction));
}
t.detach();
}
bool SceneTrigger::checkMatch(OBSWeakSource currentScene,
OBSWeakSource previousScene)
{
switch (triggerType) {
case sceneTriggerType::NONE:
return false;
case sceneTriggerType::SCENE_ACTIVE:
return currentScene == scene;
case sceneTriggerType::SCENE_INACTIVE:
return currentScene != scene;
case sceneTriggerType::SCENE_LEAVE:
return previousScene == scene;
}
return false;
}
void SwitcherData::checkTriggers()
{
if (SceneTrigger::pause) {
return;
}
OBSSource source = obs_frontend_get_current_scene();
OBSWeakSource currentScene = obs_source_get_weak_source(source);
for (auto &t : sceneTriggers) {
if (stop && !isSwitcherStatusAction(t.triggerAction)) {
continue;
}
if (t.checkMatch(currentScene, previousScene)) {
t.logMatch();
t.performAction();
}
}
obs_source_release(source);
obs_weak_source_release(currentScene);
}
void SwitcherData::saveSceneTriggers(obs_data_t *obj)
{
obs_data_array_t *triggerArray = obs_data_array_create();
for (auto &s : switcher->sceneTriggers) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
obs_data_array_push_back(triggerArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "triggers", triggerArray);
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);
obs_data_array_t *triggerArray = obs_data_get_array(obj, "triggers");
size_t count = obs_data_array_count(triggerArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(triggerArray, i);
switcher->sceneTriggers.emplace_back();
sceneTriggers.back().load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(triggerArray);
}
void AdvSceneSwitcher::setupTriggerTab()
{
for (auto &s : switcher->sceneTriggers) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->sceneTriggers);
ui->sceneTriggers->addItem(item);
SceneTriggerWidget *sw = new SceneTriggerWidget(this, &s);
item->setSizeHint(sw->minimumSizeHint());
ui->sceneTriggers->setItemWidget(item, sw);
}
if (switcher->sceneTriggers.size() == 0) {
addPulse = PulseWidget(ui->triggerAdd, QColor(Qt::green));
ui->triggerHelp->setVisible(true);
} else {
ui->triggerHelp->setVisible(false);
}
}
void SceneTrigger::save(obs_data_t *obj)
{
obs_data_set_string(obj, "scene", GetWeakSourceName(scene).c_str());
obs_data_set_int(obj, "triggerType", static_cast<int>(triggerType));
obs_data_set_int(obj, "triggerAction", static_cast<int>(triggerAction));
obs_data_set_double(obj, "duration", duration);
obs_data_set_string(obj, "audioSource",
GetWeakSourceName(audioSource).c_str());
}
void SceneTrigger::load(obs_data_t *obj)
{
const char *sceneName = obs_data_get_string(obj, "scene");
scene = GetWeakSourceByName(sceneName);
triggerType = static_cast<sceneTriggerType>(
obs_data_get_int(obj, "triggerType"));
triggerAction = static_cast<sceneTriggerAction>(
obs_data_get_int(obj, "triggerAction"));
duration = obs_data_get_double(obj, "duration");
const char *audioSourceName = obs_data_get_string(obj, "audioSource");
audioSource = GetWeakSourceByName(audioSourceName);
}
static inline void populateTriggers(QComboBox *list)
{
AdvSceneSwitcher::addSelectionEntry(
list,
obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneInactive"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneLeave"));
}
inline void populateActions(QComboBox *list)
{
AdvSceneSwitcher::addSelectionEntry(
list,
obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.pauseRecording"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unpauseRecording"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopRecording"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher"));
}
SceneTriggerWidget::SceneTriggerWidget(QWidget *parent, SceneTrigger *s)
: SwitchWidget(parent, s, false, false)
{
triggers = new QComboBox();
actions = new QComboBox();
duration = new QDoubleSpinBox();
audioSources = new QComboBox();
duration->setMinimum(0.0);
duration->setMaximum(99.000000);
duration->setSuffix("s");
QWidget::connect(triggers, SIGNAL(currentIndexChanged(int)), this,
SLOT(TriggerTypeChanged(int)));
QWidget::connect(actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(TriggerActionChanged(int)));
QWidget::connect(duration, SIGNAL(valueChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(audioSources,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(AudioSourceChanged(const QString &)));
populateTriggers(triggers);
populateActions(actions);
AdvSceneSwitcher::populateAudioSelection(audioSources);
if (s) {
triggers->setCurrentIndex(static_cast<int>(s->triggerType));
actions->setCurrentIndex(static_cast<int>(s->triggerAction));
duration->setValue(s->duration);
audioSources->setCurrentText(
GetWeakSourceName(s->audioSource).c_str());
if (isAudioAction(s->triggerAction)) {
audioSources->show();
} else {
audioSources->hide();
}
}
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{triggers}}", triggers},
{"{{actions}}", actions},
{"{{audioSources}}", audioSources},
{"{{duration}}", duration},
{"{{scenes}}", scenes}};
placeWidgets(obs_module_text("AdvSceneSwitcher.sceneTriggerTab.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
switchData = s;
loading = false;
}
SceneTrigger *SceneTriggerWidget::getSwitchData()
{
return switchData;
}
void SceneTriggerWidget::setSwitchData(SceneTrigger *s)
{
switchData = s;
}
void SceneTriggerWidget::swapSwitchData(SceneTriggerWidget *s1,
SceneTriggerWidget *s2)
{
SwitchWidget::swapSwitchData(s1, s2);
SceneTrigger *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
void SceneTriggerWidget::TriggerTypeChanged(int index)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->triggerType = static_cast<sceneTriggerType>(index);
}
void SceneTriggerWidget::TriggerActionChanged(int index)
{
if (loading || !switchData) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
switchData->triggerAction =
static_cast<sceneTriggerAction>(index);
}
if (isAudioAction(switchData->triggerAction)) {
audioSources->show();
} else {
audioSources->hide();
}
}
void SceneTriggerWidget::DurationChanged(double dur)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->duration = dur;
}
void SceneTriggerWidget::AudioSourceChanged(const QString &text)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->audioSource = GetWeakSourceByQString(text);
}

View File

@@ -1,284 +0,0 @@
#include <QTimer>
#include "headers/advanced-scene-switcher.hpp"
void SwitcherData::checkScreenRegionSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
std::pair<int, int> cursorPos = getCursorPos();
int minRegionSize = 99999;
for (auto &s : screenRegionSwitches) {
if (cursorPos.first >= s.minX && cursorPos.second >= s.minY &&
cursorPos.first <= s.maxX && cursorPos.second <= s.maxY) {
int regionSize = (s.maxX - s.minX) + (s.maxY - s.minY);
if (regionSize < minRegionSize) {
match = true;
scene = s.scene;
transition = s.transition;
minRegionSize = regionSize;
if (verbose)
blog(LOG_INFO,
"Advanced Scene Switcher region match");
break;
}
}
}
}
void SceneSwitcher::updateScreenRegionCursorPos()
{
std::pair<int, int> position = getCursorPos();
ui->cursorXPosition->setText(QString::number(position.first));
ui->cursorYPosition->setText(QString::number(position.second));
}
void SceneSwitcher::on_screenRegionAdd_clicked()
{
QString sceneName = ui->screenRegionScenes->currentText();
QString transitionName = ui->screenRegionsTransitions->currentText();
if (sceneName.isEmpty())
return;
int minX = ui->screenRegionMinX->value();
int minY = ui->screenRegionMinY->value();
int maxX = ui->screenRegionMaxX->value();
int maxY = ui->screenRegionMaxY->value();
std::string regionStr =
std::to_string(minX) + ", " + std::to_string(minY) + " x " +
std::to_string(maxX) + ", " + std::to_string(maxY);
QString region = QString::fromStdString(regionStr);
OBSWeakSource source = GetWeakSourceByQString(sceneName);
OBSWeakSource transition = GetWeakTransitionByQString(transitionName);
QVariant v = QVariant::fromValue(region);
QString text = MakeScreenRegionSwitchName(sceneName, transitionName,
minX, minY, maxX, maxY);
int idx = ScreenRegionFindByData(region);
if (idx == -1) {
QListWidgetItem *item =
new QListWidgetItem(text, ui->screenRegions);
item->setData(Qt::UserRole, v);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->screenRegionSwitches.emplace_back(
source, transition, minX, minY, maxX, maxY, regionStr);
} else {
QListWidgetItem *item = ui->screenRegions->item(idx);
item->setText(text);
std::string curRegion = region.toUtf8().constData();
{
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->screenRegionSwitches) {
if (s.regionStr == curRegion) {
s.scene = source;
s.transition = transition;
break;
}
}
}
}
}
void SceneSwitcher::on_screenRegionRemove_clicked()
{
QListWidgetItem *item = ui->screenRegions->currentItem();
if (!item)
return;
std::string region =
item->data(Qt::UserRole).toString().toUtf8().constData();
{
std::lock_guard<std::mutex> lock(switcher->m);
auto &switches = switcher->screenRegionSwitches;
for (auto it = switches.begin(); it != switches.end(); ++it) {
auto &s = *it;
if (s.regionStr == region) {
switches.erase(it);
break;
}
}
}
delete item;
}
void SceneSwitcher::on_screenRegions_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
return;
QListWidgetItem *item = ui->screenRegions->item(idx);
QString region = item->data(Qt::UserRole).toString();
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->screenRegionSwitches) {
if (region.compare(s.regionStr.c_str()) == 0) {
std::string name = GetWeakSourceName(s.scene);
std::string transitionName =
GetWeakSourceName(s.transition);
ui->screenRegionScenes->setCurrentText(name.c_str());
ui->screenRegionsTransitions->setCurrentText(
transitionName.c_str());
ui->screenRegionMinX->setValue(s.minX);
ui->screenRegionMinY->setValue(s.minY);
ui->screenRegionMaxX->setValue(s.maxX);
ui->screenRegionMaxY->setValue(s.maxY);
break;
}
}
}
int SceneSwitcher::ScreenRegionFindByData(const QString &region)
{
int count = ui->screenRegions->count();
int idx = -1;
for (int i = 0; i < count; i++) {
QListWidgetItem *item = ui->screenRegions->item(i);
QString itemRegion = item->data(Qt::UserRole).toString();
if (itemRegion == region) {
idx = i;
break;
}
}
return idx;
}
void SceneSwitcher::on_screenRegionUp_clicked()
{
int index = ui->screenRegions->currentRow();
if (index != -1 && index != 0) {
ui->screenRegions->insertItem(
index - 1, ui->screenRegions->takeItem(index));
ui->screenRegions->setCurrentRow(index - 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->screenRegionSwitches.begin() + index,
switcher->screenRegionSwitches.begin() + index - 1);
}
}
void SceneSwitcher::on_screenRegionDown_clicked()
{
int index = ui->screenRegions->currentRow();
if (index != -1 && index != ui->screenRegions->count() - 1) {
ui->screenRegions->insertItem(
index + 1, ui->screenRegions->takeItem(index));
ui->screenRegions->setCurrentRow(index + 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->screenRegionSwitches.begin() + index,
switcher->screenRegionSwitches.begin() + index + 1);
}
}
void SwitcherData::saveScreenRegionSwitches(obs_data_t *obj)
{
obs_data_array_t *screenRegionArray = obs_data_array_create();
for (ScreenRegionSwitch &s : switcher->screenRegionSwitches) {
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, "screenRegionScene",
sceneName);
obs_data_set_string(array_obj, "transition",
transitionName);
obs_data_set_int(array_obj, "minX", s.minX);
obs_data_set_int(array_obj, "minY", s.minY);
obs_data_set_int(array_obj, "maxX", s.maxX);
obs_data_set_int(array_obj, "maxY", s.maxY);
obs_data_set_string(array_obj, "screenRegionStr",
s.regionStr.c_str());
obs_data_array_push_back(screenRegionArray, array_obj);
obs_source_release(source);
obs_source_release(transition);
}
obs_data_release(array_obj);
}
obs_data_set_array(obj, "screenRegion", screenRegionArray);
obs_data_array_release(screenRegionArray);
}
void SwitcherData::loadScreenRegionSwitches(obs_data_t *obj)
{
switcher->screenRegionSwitches.clear();
obs_data_array_t *screenRegionArray =
obs_data_get_array(obj, "screenRegion");
size_t count = obs_data_array_count(screenRegionArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj =
obs_data_array_item(screenRegionArray, i);
const char *scene =
obs_data_get_string(array_obj, "screenRegionScene");
const char *transition =
obs_data_get_string(array_obj, "transition");
int minX = obs_data_get_int(array_obj, "minX");
int minY = obs_data_get_int(array_obj, "minY");
int maxX = obs_data_get_int(array_obj, "maxX");
int maxY = obs_data_get_int(array_obj, "maxY");
std::string regionStr =
obs_data_get_string(array_obj, "screenRegionStr");
switcher->screenRegionSwitches.emplace_back(
GetWeakSourceByName(scene),
GetWeakTransitionByName(transition), minX, minY, maxX,
maxY, regionStr);
obs_data_release(array_obj);
}
obs_data_array_release(screenRegionArray);
}
void SceneSwitcher::setupRegionTab()
{
populateSceneSelection(ui->screenRegionScenes, false);
populateTransitionSelection(ui->screenRegionsTransitions);
for (auto &s : switcher->screenRegionSwitches) {
std::string sceneName = GetWeakSourceName(s.scene);
std::string transitionName = GetWeakSourceName(s.transition);
QString text = MakeScreenRegionSwitchName(
sceneName.c_str(), transitionName.c_str(), s.minX,
s.minY, s.maxX, s.maxY);
QListWidgetItem *item =
new QListWidgetItem(text, ui->screenRegions);
item->setData(Qt::UserRole, s.regionStr.c_str());
}
// screen region cursor position
QTimer *screenRegionTimer = new QTimer(this);
connect(screenRegionTimer, SIGNAL(timeout()), this,
SLOT(updateScreenRegionCursorPos()));
screenRegionTimer->start(1000);
}

642
src/switch-audio.cpp Normal file
View File

@@ -0,0 +1,642 @@
#include <float.h>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/volume-control.hpp"
#include "headers/utility.hpp"
bool AudioSwitch::pause = false;
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::on_audioAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->audioSwitches.emplace_back();
AudioSwitchWidget *sw =
new AudioSwitchWidget(this, &switcher->audioSwitches.back());
listAddClicked(ui->audioSwitches, sw, ui->audioAdd, &addPulse);
ui->audioHelp->setVisible(false);
}
void AdvSceneSwitcher::on_audioRemove_clicked()
{
QListWidgetItem *item = ui->audioSwitches->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->audioSwitches->currentRow();
auto &switches = switcher->audioSwitches;
switches.erase(switches.begin() + idx);
}
delete item;
}
void AdvSceneSwitcher::on_audioUp_clicked()
{
int index = ui->audioSwitches->currentRow();
if (!listMoveUp(ui->audioSwitches)) {
return;
}
AudioSwitchWidget *s1 =
(AudioSwitchWidget *)ui->audioSwitches->itemWidget(
ui->audioSwitches->item(index));
AudioSwitchWidget *s2 =
(AudioSwitchWidget *)ui->audioSwitches->itemWidget(
ui->audioSwitches->item(index - 1));
AudioSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->audioSwitches[index],
switcher->audioSwitches[index - 1]);
}
void AdvSceneSwitcher::on_audioDown_clicked()
{
int index = ui->audioSwitches->currentRow();
if (!listMoveDown(ui->audioSwitches)) {
return;
}
AudioSwitchWidget *s1 =
(AudioSwitchWidget *)ui->audioSwitches->itemWidget(
ui->audioSwitches->item(index));
AudioSwitchWidget *s2 =
(AudioSwitchWidget *)ui->audioSwitches->itemWidget(
ui->audioSwitches->item(index + 1));
AudioSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->audioSwitches[index],
switcher->audioSwitches[index + 1]);
}
void AdvSceneSwitcher::on_audioFallback_toggled(bool on)
{
if (loading || !switcher) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->audioFallback.enable = on;
}
void SwitcherData::checkAudioSwitchFallback(OBSWeakSource &scene,
OBSWeakSource &transition)
{
bool durationReached =
((unsigned long long)audioFallback.matchCount * interval) /
1000.0 >=
audioFallback.duration;
if (durationReached) {
scene = audioFallback.getScene();
transition = audioFallback.transition;
if (verbose) {
audioFallback.logMatch();
}
}
audioFallback.matchCount++;
}
void SwitcherData::checkAudioSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (AudioSwitch::pause) {
return;
}
bool fallbackChecked = false; // false if only one or no match
for (AudioSwitch &s : audioSwitches) {
if (!s.initialized()) {
continue;
}
obs_source_t *as = obs_weak_source_get_source(s.audioSource);
bool audioActive = obs_source_active(as);
obs_source_release(as);
// peak will have a value from -60 db to 0 db
bool volumeThresholdreached = false;
if (s.condition == ABOVE) {
volumeThresholdreached = ((double)s.peak + 60) * 1.7 >
s.volumeThreshold;
} else {
volumeThresholdreached = ((double)s.peak + 60) * 1.7 <
s.volumeThreshold;
}
// Reset for next check
s.peak = -FLT_MAX;
if (volumeThresholdreached) {
s.matchCount++;
} else {
s.matchCount = 0;
}
bool durationReached =
((unsigned long long)s.matchCount * interval) /
1000.0 >=
s.duration;
if (volumeThresholdreached && durationReached && audioActive) {
if (match) {
checkAudioSwitchFallback(scene, transition);
fallbackChecked = true;
break;
}
scene = s.getScene();
transition = s.transition;
match = true;
if (verbose) {
s.logMatch();
}
if (!audioFallback.enable) {
break;
}
}
}
if (!fallbackChecked) {
audioFallback.matchCount = 0;
}
}
void SwitcherData::saveAudioSwitches(obs_data_t *obj)
{
obs_data_array_t *audioArray = obs_data_array_create();
for (AudioSwitch &s : switcher->audioSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
obs_data_array_push_back(audioArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "audioSwitches", audioArray);
obs_data_array_release(audioArray);
audioFallback.save(obj);
}
void SwitcherData::loadAudioSwitches(obs_data_t *obj)
{
switcher->audioSwitches.clear();
obs_data_array_t *audioArray = obs_data_get_array(obj, "audioSwitches");
size_t count = obs_data_array_count(audioArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(audioArray, i);
switcher->audioSwitches.emplace_back();
audioSwitches.back().load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(audioArray);
audioFallback.load(obj);
}
void AdvSceneSwitcher::setupAudioTab()
{
for (auto &s : switcher->audioSwitches) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->audioSwitches);
ui->audioSwitches->addItem(item);
AudioSwitchWidget *sw = new AudioSwitchWidget(this, &s);
item->setSizeHint(sw->minimumSizeHint());
ui->audioSwitches->setItemWidget(item, sw);
}
if (switcher->audioSwitches.size() == 0) {
addPulse = PulseWidget(ui->audioAdd, QColor(Qt::green));
ui->audioHelp->setVisible(true);
} else {
ui->audioHelp->setVisible(false);
}
AudioSwitchFallbackWidget *fb =
new AudioSwitchFallbackWidget(this, &switcher->audioFallback);
ui->audioFallbackLayout->addWidget(fb);
ui->audioFallback->setChecked(switcher->audioFallback.enable);
}
void AudioSwitch::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);
AudioSwitch *s = static_cast<AudioSwitch *>(data);
for (int i = 1; i < MAX_AUDIO_CHANNELS; i++) {
if (peak[i] > s->peak) {
s->peak = peak[i];
}
}
}
obs_volmeter_t *AddVolmeterToSource(AudioSwitch *entry, obs_weak_source *source)
{
obs_volmeter_t *volmeter = obs_volmeter_create(OBS_FADER_LOG);
obs_volmeter_add_callback(volmeter, AudioSwitch::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;
}
void AudioSwitch::resetVolmeter()
{
obs_volmeter_remove_callback(volmeter, setVolumeLevel, this);
obs_volmeter_destroy(volmeter);
volmeter = AddVolmeterToSource(this, audioSource);
}
bool AudioSwitch::initialized()
{
return SceneSwitcherEntry::initialized() && audioSource;
}
bool AudioSwitch::valid()
{
return !initialized() ||
(SceneSwitcherEntry::valid() && WeakSourceValid(audioSource));
}
void AudioSwitch::save(obs_data_t *obj)
{
SceneSwitcherEntry::save(obj);
obs_data_set_string(obj, "audioSource",
GetWeakSourceName(audioSource).c_str());
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;
}
void AudioSwitch::load(obs_data_t *obj)
{
if (loadOldAudio(obj, this)) {
return;
}
SceneSwitcherEntry::load(obj);
const char *audioSourceName = obs_data_get_string(obj, "audioSource");
audioSource = GetWeakSourceByName(audioSourceName);
volumeThreshold = obs_data_get_int(obj, "volume");
condition = (audioCondition)obs_data_get_int(obj, "condition");
duration = obs_data_get_double(obj, "duration");
volmeter = AddVolmeterToSource(this, audioSource);
}
void AudioSwitchFallback::save(obs_data_t *obj)
{
SceneSwitcherEntry::save(obj, "audioFallbackTargetType",
"audioFallbackScene",
"audioFallbackTransition");
obs_data_set_bool(obj, "audioFallbackEnable", enable);
obs_data_set_double(obj, "audioFallbackDuration", duration);
}
void AudioSwitchFallback::load(obs_data_t *obj)
{
SceneSwitcherEntry::load(obj, "audioFallbackTargetType",
"audioFallbackScene",
"audioFallbackTransition");
enable = obs_data_get_bool(obj, "audioFallbackEnable");
duration = obs_data_get_double(obj, "audioFallbackDuration");
}
AudioSwitch::AudioSwitch(const AudioSwitch &other)
: SceneSwitcherEntry(other.targetType, other.group, other.scene,
other.transition, other.usePreviousScene),
audioSource(other.audioSource),
volumeThreshold(other.volumeThreshold),
condition(other.condition),
duration(other.duration)
{
volmeter = AddVolmeterToSource(this, other.audioSource);
}
AudioSwitch::AudioSwitch(AudioSwitch &&other)
: SceneSwitcherEntry(other.targetType, other.group, other.scene,
other.transition, other.usePreviousScene),
audioSource(other.audioSource),
volumeThreshold(other.volumeThreshold),
condition(other.condition),
duration(other.duration),
volmeter(other.volmeter)
{
other.volmeter = nullptr;
}
AudioSwitch::~AudioSwitch()
{
obs_volmeter_remove_callback(volmeter, setVolumeLevel, this);
obs_volmeter_destroy(volmeter);
}
AudioSwitch &AudioSwitch::operator=(const AudioSwitch &other)
{
AudioSwitch t(other);
swap(*this, t);
return *this = AudioSwitch(other);
}
AudioSwitch &AudioSwitch::operator=(AudioSwitch &&other) noexcept
{
if (this == &other) {
return *this;
}
swap(*this, other);
obs_volmeter_remove_callback(other.volmeter, setVolumeLevel, this);
obs_volmeter_destroy(other.volmeter);
other.volmeter = nullptr;
return *this;
}
void swap(AudioSwitch &first, AudioSwitch &second)
{
std::swap(first.targetType, second.targetType);
std::swap(first.group, second.group);
std::swap(first.scene, second.scene);
std::swap(first.transition, second.transition);
std::swap(first.usePreviousScene, second.usePreviousScene);
std::swap(first.audioSource, second.audioSource);
std::swap(first.volumeThreshold, second.volumeThreshold);
std::swap(first.condition, second.condition);
std::swap(first.duration, second.duration);
std::swap(first.peak, second.peak);
std::swap(first.volmeter, second.volmeter);
first.resetVolmeter();
second.resetVolmeter();
}
static inline void populateConditionSelection(QComboBox *list)
{
list->addItem(
obs_module_text("AdvSceneSwitcher.audioTab.condition.above"));
list->addItem(
obs_module_text("AdvSceneSwitcher.audioTab.condition.below"));
}
AudioSwitchWidget::AudioSwitchWidget(QWidget *parent, AudioSwitch *s)
: SwitchWidget(parent, s, true, true)
{
audioSources = new QComboBox();
condition = new QComboBox();
audioVolumeThreshold = new QSpinBox();
duration = new QDoubleSpinBox();
obs_source_t *source = nullptr;
if (s) {
source = obs_weak_source_get_source(s->audioSource);
}
volMeter = new VolControl(source);
obs_source_release(source);
audioVolumeThreshold->setSuffix("%");
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)),
volMeter->GetSlider(), SLOT(setValue(int)));
QWidget::connect(audioVolumeThreshold, SIGNAL(valueChanged(int)), this,
SLOT(VolumeThresholdChanged(int)));
QWidget::connect(condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(duration, SIGNAL(valueChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(audioSources,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(SourceChanged(const QString &)));
AdvSceneSwitcher::populateAudioSelection(audioSources);
populateConditionSelection(condition);
if (s) {
audioSources->setCurrentText(
GetWeakSourceName(s->audioSource).c_str());
audioVolumeThreshold->setValue(s->volumeThreshold);
condition->setCurrentIndex(s->condition);
duration->setValue(s->duration);
}
QHBoxLayout *switchLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{audioSources}}", audioSources},
{"{{volumeWidget}}", audioVolumeThreshold},
{"{{condition}}", condition},
{"{{duration}}", duration},
{"{{scenes}}", scenes},
{"{{transitions}}", transitions}};
placeWidgets(obs_module_text("AdvSceneSwitcher.audioTab.entry"),
switchLayout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(switchLayout);
mainLayout->addWidget(volMeter);
setLayout(mainLayout);
switchData = s;
loading = false;
}
AudioSwitch *AudioSwitchWidget::getSwitchData()
{
return switchData;
}
void AudioSwitchWidget::setSwitchData(AudioSwitch *s)
{
switchData = s;
}
void AudioSwitchWidget::swapSwitchData(AudioSwitchWidget *s1,
AudioSwitchWidget *s2)
{
SwitchWidget::swapSwitchData(s1, s2);
AudioSwitch *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
void AudioSwitchWidget::UpdateVolmeterSource()
{
delete volMeter;
obs_source_t *soruce =
obs_weak_source_get_source(switchData->audioSource);
volMeter = new VolControl(soruce);
obs_source_release(soruce);
QLayout *layout = this->layout();
layout->addWidget(volMeter);
QWidget::connect(volMeter->GetSlider(), SIGNAL(valueChanged(int)),
audioVolumeThreshold, SLOT(setValue(int)));
QWidget::connect(audioVolumeThreshold, SIGNAL(valueChanged(int)),
volMeter->GetSlider(), SLOT(setValue(int)));
// Slider will default to 0 so set it manually once
volMeter->GetSlider()->setValue(switchData->volumeThreshold);
}
void AudioSwitchWidget::SourceChanged(const QString &text)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->audioSource = GetWeakSourceByQString(text);
switchData->resetVolmeter();
UpdateVolmeterSource();
}
void AudioSwitchWidget::VolumeThresholdChanged(int vol)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->volumeThreshold = vol;
}
void AudioSwitchWidget::ConditionChanged(int cond)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->condition = (audioCondition)cond;
}
void AudioSwitchWidget::DurationChanged(double dur)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->duration = dur;
}
AudioSwitchFallbackWidget::AudioSwitchFallbackWidget(QWidget *parent,
AudioSwitchFallback *s)
: SwitchWidget(parent, s, true, true)
{
duration = new QDoubleSpinBox();
duration->setMinimum(0.0);
duration->setMaximum(99.000000);
duration->setSuffix("s");
QWidget::connect(duration, SIGNAL(valueChanged(double)), this,
SLOT(DurationChanged(double)));
if (s) {
duration->setValue(s->duration);
}
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", scenes},
{"{{duration}}", duration},
{"{{transitions}}", transitions}};
placeWidgets(
obs_module_text("AdvSceneSwitcher.audioTab.multiMatchfallback"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
switchData = s;
loading = false;
}
void AudioSwitchFallbackWidget::DurationChanged(double dur)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->duration = dur;
}

315
src/switch-executable.cpp Normal file
View File

@@ -0,0 +1,315 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
bool ExecutableSwitch::pause = false;
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::on_executableAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->executableSwitches.emplace_back();
listAddClicked(ui->executables,
new ExecutableSwitchWidget(
this, &switcher->executableSwitches.back()),
ui->executableAdd, &addPulse);
ui->exeHelp->setVisible(false);
}
void AdvSceneSwitcher::on_executableRemove_clicked()
{
QListWidgetItem *item = ui->executables->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->executables->currentRow();
auto &switches = switcher->executableSwitches;
switches.erase(switches.begin() + idx);
}
delete item;
}
void AdvSceneSwitcher::on_executableUp_clicked()
{
int index = ui->executables->currentRow();
if (!listMoveUp(ui->executables)) {
return;
}
ExecutableSwitchWidget *s1 =
(ExecutableSwitchWidget *)ui->executables->itemWidget(
ui->executables->item(index));
ExecutableSwitchWidget *s2 =
(ExecutableSwitchWidget *)ui->executables->itemWidget(
ui->executables->item(index - 1));
ExecutableSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->executableSwitches[index],
switcher->executableSwitches[index - 1]);
}
void AdvSceneSwitcher::on_executableDown_clicked()
{
int index = ui->executables->currentRow();
if (!listMoveDown(ui->executables)) {
return;
}
ExecutableSwitchWidget *s1 =
(ExecutableSwitchWidget *)ui->executables->itemWidget(
ui->executables->item(index));
ExecutableSwitchWidget *s2 =
(ExecutableSwitchWidget *)ui->executables->itemWidget(
ui->executables->item(index + 1));
ExecutableSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->executableSwitches[index],
switcher->executableSwitches[index + 1]);
}
void SwitcherData::checkExeSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (executableSwitches.size() == 0 || ExecutableSwitch::pause) {
return;
}
std::string title;
QStringList runningProcesses;
bool ignored = false;
// Check if current window is ignored
GetCurrentWindowTitle(title);
for (auto &window : ignoreWindowsSwitches) {
// True if ignored switch equals title
bool equals = (title == window);
// True if ignored switch matches title
bool matches = QString::fromStdString(title).contains(
QRegularExpression(QString::fromStdString(window)));
if (equals || matches) {
ignored = true;
title = lastTitle;
break;
}
}
lastTitle = title;
// Check for match
GetProcessList(runningProcesses);
for (ExecutableSwitch &s : executableSwitches) {
if (!s.initialized()) {
continue;
}
bool equals = runningProcesses.contains(s.exe);
bool matches = (runningProcesses.indexOf(
QRegularExpression(s.exe)) != -1);
bool focus = (!s.inFocus || isInFocus(s.exe));
// True if current window is ignored AND switch equals OR matches last window
bool ignore =
(ignored && (title == s.exe.toStdString() ||
QString::fromStdString(title).contains(
QRegularExpression(s.exe))));
if ((equals || matches) && (focus || ignore)) {
match = true;
scene = s.getScene();
transition = s.transition;
if (verbose) {
s.logMatch();
}
break;
}
}
}
void SwitcherData::saveExecutableSwitches(obs_data_t *obj)
{
obs_data_array_t *executableArray = obs_data_array_create();
for (ExecutableSwitch &s : switcher->executableSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
obs_data_array_push_back(executableArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "executableSwitches", executableArray);
obs_data_array_release(executableArray);
}
void SwitcherData::loadExecutableSwitches(obs_data_t *obj)
{
switcher->executableSwitches.clear();
obs_data_array_t *executableArray =
obs_data_get_array(obj, "executableSwitches");
size_t count = obs_data_array_count(executableArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(executableArray, i);
switcher->executableSwitches.emplace_back();
executableSwitches.back().load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(executableArray);
}
void AdvSceneSwitcher::setupExecutableTab()
{
for (auto &s : switcher->executableSwitches) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->executables);
ui->executables->addItem(item);
ExecutableSwitchWidget *sw =
new ExecutableSwitchWidget(this, &s);
item->setSizeHint(sw->minimumSizeHint());
ui->executables->setItemWidget(item, sw);
}
if (switcher->executableSwitches.size() == 0) {
addPulse = PulseWidget(ui->executableAdd, QColor(Qt::green));
ui->exeHelp->setVisible(true);
} else {
ui->exeHelp->setVisible(false);
}
}
void ExecutableSwitch::save(obs_data_t *obj)
{
SceneSwitcherEntry::save(obj);
obs_data_set_string(obj, "exefile", exe.toUtf8());
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");
inFocus = obs_data_get_bool(obj, "infocus");
}
ExecutableSwitchWidget::ExecutableSwitchWidget(QWidget *parent,
ExecutableSwitch *s)
: SwitchWidget(parent, s, true, true)
{
processes = new QComboBox();
requiresFocus = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.executableTab.requiresFocus"));
QWidget::connect(processes, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(ProcessChanged(const QString &)));
QWidget::connect(requiresFocus, SIGNAL(stateChanged(int)), this,
SLOT(FocusChanged(int)));
AdvSceneSwitcher::populateProcessSelection(processes);
processes->setEditable(true);
processes->setMaxVisibleItems(20);
if (s) {
processes->setCurrentText(s->exe);
requiresFocus->setChecked(s->inFocus);
}
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{processes}}", processes},
{"{{requiresFocus}}", requiresFocus},
{"{{scenes}}", scenes},
{"{{transitions}}", transitions}};
placeWidgets(obs_module_text("AdvSceneSwitcher.executableTab.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
switchData = s;
loading = false;
}
ExecutableSwitch *ExecutableSwitchWidget::getSwitchData()
{
return switchData;
}
void ExecutableSwitchWidget::setSwitchData(ExecutableSwitch *s)
{
switchData = s;
}
void ExecutableSwitchWidget::swapSwitchData(ExecutableSwitchWidget *s1,
ExecutableSwitchWidget *s2)
{
SwitchWidget::swapSwitchData(s1, s2);
ExecutableSwitch *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
void ExecutableSwitchWidget::ProcessChanged(const QString &text)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->exe = text;
}
void ExecutableSwitchWidget::FocusChanged(int state)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->inFocus = state;
}

702
src/switch-file.cpp Normal file
View File

@@ -0,0 +1,702 @@
#include <QFileDialog>
#include <QTextStream>
#include <QDateTime>
#include <functional>
#include <curl/curl.h>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/curl-helper.hpp"
#include "headers/utility.hpp"
bool FileSwitch::pause = false;
static QMetaObject::Connection addPulse;
static std::hash<std::string> strHash;
void AdvSceneSwitcher::on_browseButton_clicked()
{
QString path = QFileDialog::getOpenFileName(
this,
tr(obs_module_text("AdvSceneSwitcher.fileTab.selectWrite")),
QDir::currentPath(),
tr(obs_module_text("AdvSceneSwitcher.fileTab.textFileType")));
if (!path.isEmpty()) {
ui->writePathLineEdit->setText(path);
}
}
void AdvSceneSwitcher::on_readFileCheckBox_stateChanged(int state)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
if (!state) {
ui->browseButton_2->setDisabled(true);
ui->readPathLineEdit->setDisabled(true);
switcher->fileIO.readEnabled = false;
} else {
ui->browseButton_2->setDisabled(false);
ui->readPathLineEdit->setDisabled(false);
switcher->fileIO.readEnabled = true;
}
}
void AdvSceneSwitcher::on_readPathLineEdit_textChanged(const QString &text)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
if (text.isEmpty()) {
switcher->fileIO.readEnabled = false;
return;
}
switcher->fileIO.readEnabled = true;
switcher->fileIO.readPath = text.toUtf8().constData();
}
void AdvSceneSwitcher::on_writePathLineEdit_textChanged(const QString &text)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
if (text.isEmpty()) {
switcher->fileIO.writeEnabled = false;
return;
}
switcher->fileIO.writeEnabled = true;
switcher->fileIO.writePath = text.toUtf8().constData();
}
void AdvSceneSwitcher::on_browseButton_2_clicked()
{
QString path = QFileDialog::getOpenFileName(
this,
tr(obs_module_text("AdvSceneSwitcher.fileTab.selectRead")),
QDir::currentPath(),
tr(obs_module_text("AdvSceneSwitcher.fileTab.anyFileType")));
if (!path.isEmpty()) {
ui->readPathLineEdit->setText(path);
}
}
void SwitcherData::writeSceneInfoToFile()
{
if (!fileIO.writeEnabled || fileIO.writePath.empty()) {
return;
}
obs_source_t *currentSource = obs_frontend_get_current_scene();
QFile file(QString::fromStdString(fileIO.writePath));
if (file.open(QIODevice::WriteOnly)) {
const char *msg = obs_source_get_name(currentSource);
file.write(msg, qstrlen(msg));
file.close();
}
obs_source_release(currentSource);
}
void SwitcherData::writeToStatusFile(QString msg)
{
if (!fileIO.writeEnabled || fileIO.writePath.empty()) {
return;
}
QFile file(QString::fromStdString(fileIO.writePath));
if (file.open(QIODevice::ReadWrite)) {
QTextStream stream(&file);
stream << msg << endl;
}
file.close();
}
void SwitcherData::checkSwitchInfoFromFile(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (!fileIO.readEnabled || fileIO.readPath.empty() ||
FileSwitch::pause) {
return;
}
QFile file(QString::fromStdString(fileIO.readPath));
if (!file.open(QIODevice::ReadOnly)) {
return;
}
QTextStream in(&file);
QString sceneStr = in.readLine();
OBSWeakSource sceneRead = GetWeakSourceByQString(sceneStr);
QString transitionStr = in.readLine();
OBSWeakSource transitionRead =
GetWeakTransitionByQString(transitionStr);
if (sceneRead) {
match = true;
scene = sceneRead;
transition = transitionRead;
vblog(LOG_INFO,
"match for 'file' - read scene '%s' and transition '%s' from file '%s'",
sceneStr.toUtf8().constData(),
transitionStr.toUtf8().constData(),
fileIO.readPath.c_str());
}
file.close();
}
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;
}
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 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) {
size_t newHash = strHash(filedata.toUtf8().constData());
if (newHash == s.lastHash) {
return false;
}
s.lastHash = newHash;
}
if (s.useRegex) {
QRegExp rx(QString::fromStdString(s.text));
return rx.exactMatch(filedata);
}
QString text = QString::fromStdString(s.text);
return compareIgnoringLineEnding(text, filedata);
}
bool checkRemoteFileContent(FileSwitch &s)
{
std::string data = getRemoteData(s.file);
QString qdata = QString::fromStdString(data);
return matchFileContent(qdata, s);
}
bool checkLocalFileContent(FileSwitch &s)
{
QString t = QString::fromStdString(s.text);
QFile file(QString::fromStdString(s.file));
if (s.file.empty() || !file.open(QIODevice::ReadOnly)) {
return false;
}
if (s.useTime) {
QDateTime newLastMod = QFileInfo(file).lastModified();
if (s.lastMod == newLastMod) {
return false;
}
s.lastMod = newLastMod;
}
QString filedata = QTextStream(&file).readAll();
bool match = matchFileContent(filedata, s);
file.close();
return match;
}
void SwitcherData::checkFileContent(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (FileSwitch::pause) {
return;
}
for (FileSwitch &s : fileSwitches) {
if (!s.initialized()) {
continue;
}
bool equal = false;
if (s.remote) {
equal = checkRemoteFileContent(s);
} else {
equal = checkLocalFileContent(s);
}
if (equal) {
scene = s.getScene();
transition = s.transition;
match = true;
if (verbose) {
s.logMatch();
}
break;
}
}
}
void AdvSceneSwitcher::on_fileAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->fileSwitches.emplace_back();
listAddClicked(ui->fileSwitches,
new FileSwitchWidget(this,
&switcher->fileSwitches.back()),
ui->fileAdd, &addPulse);
ui->fileHelp->setVisible(false);
}
void AdvSceneSwitcher::on_fileRemove_clicked()
{
QListWidgetItem *item = ui->fileSwitches->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->fileSwitches->currentRow();
auto &switches = switcher->fileSwitches;
switches.erase(switches.begin() + idx);
}
delete item;
}
void AdvSceneSwitcher::on_fileSwitches_currentRowChanged(int idx)
{
if (loading) {
return;
}
if (idx == -1) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
if ((int)switcher->fileSwitches.size() <= idx) {
return;
}
FileSwitch s = switcher->fileSwitches[idx];
if (s.remote) {
ui->remoteFileWarningLabel->show();
} else {
ui->remoteFileWarningLabel->hide();
}
}
void AdvSceneSwitcher::on_fileUp_clicked()
{
int index = ui->fileSwitches->currentRow();
if (!listMoveUp(ui->fileSwitches)) {
return;
}
FileSwitchWidget *s1 = (FileSwitchWidget *)ui->fileSwitches->itemWidget(
ui->fileSwitches->item(index));
FileSwitchWidget *s2 = (FileSwitchWidget *)ui->fileSwitches->itemWidget(
ui->fileSwitches->item(index - 1));
FileSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->fileSwitches[index],
switcher->fileSwitches[index - 1]);
}
void AdvSceneSwitcher::on_fileDown_clicked()
{
int index = ui->fileSwitches->currentRow();
if (!listMoveDown(ui->fileSwitches)) {
return;
}
FileSwitchWidget *s1 = (FileSwitchWidget *)ui->fileSwitches->itemWidget(
ui->fileSwitches->item(index));
FileSwitchWidget *s2 = (FileSwitchWidget *)ui->fileSwitches->itemWidget(
ui->fileSwitches->item(index + 1));
FileSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->fileSwitches[index],
switcher->fileSwitches[index + 1]);
}
void SwitcherData::saveFileSwitches(obs_data_t *obj)
{
obs_data_array_t *fileArray = obs_data_array_create();
for (FileSwitch &s : switcher->fileSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
obs_data_array_push_back(fileArray, array_obj);
obs_data_release(array_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());
}
void SwitcherData::loadFileSwitches(obs_data_t *obj)
{
switcher->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.back().load(array_obj);
obs_data_release(array_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");
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");
}
void AdvSceneSwitcher::setupFileTab()
{
ui->remoteFileWarningLabel->setText(
obs_module_text("AdvSceneSwitcher.fileTab.remoteFileWarning1") +
QString::number(switcher->interval) +
obs_module_text("AdvSceneSwitcher.fileTab.remoteFileWarning2"));
ui->remoteFileWarningLabel->hide();
if (switcher->curl) {
ui->libcurlWarning->setVisible(false);
}
for (auto &s : switcher->fileSwitches) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->fileSwitches);
ui->fileSwitches->addItem(item);
FileSwitchWidget *sw = new FileSwitchWidget(this, &s);
item->setSizeHint(sw->minimumSizeHint());
ui->fileSwitches->setItemWidget(item, sw);
}
if (switcher->fileSwitches.size() == 0) {
addPulse = PulseWidget(ui->fileAdd, QColor(Qt::green));
ui->fileHelp->setVisible(true);
} else {
ui->fileHelp->setVisible(false);
}
ui->readPathLineEdit->setText(
QString::fromStdString(switcher->fileIO.readPath.c_str()));
ui->readFileCheckBox->setChecked(switcher->fileIO.readEnabled);
ui->writePathLineEdit->setText(
QString::fromStdString(switcher->fileIO.writePath.c_str()));
if (ui->readFileCheckBox->checkState()) {
ui->browseButton_2->setDisabled(false);
ui->readPathLineEdit->setDisabled(false);
} else {
ui->browseButton_2->setDisabled(true);
ui->readPathLineEdit->setDisabled(true);
}
}
void FileSwitch::save(obs_data_t *obj)
{
SceneSwitcherEntry::save(obj);
obs_data_set_string(obj, "file", file.c_str());
obs_data_set_string(obj, "text", text.c_str());
obs_data_set_bool(obj, "remote", remote);
obs_data_set_bool(obj, "useRegex", useRegex);
obs_data_set_bool(obj, "useTime", useTime);
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");
text = obs_data_get_string(obj, "text");
remote = obs_data_get_bool(obj, "remote");
useRegex = obs_data_get_bool(obj, "useRegex");
useTime = obs_data_get_bool(obj, "useTime");
onlyMatchIfChanged = obs_data_get_bool(obj, "onlyMatchIfChanged");
}
FileSwitchWidget::FileSwitchWidget(QWidget *parent, FileSwitch *s)
: SwitchWidget(parent, s, true, true)
{
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(CheckFileContentChanged(int)));
fileType->addItem(obs_module_text("AdvSceneSwitcher.fileTab.local"));
fileType->addItem(obs_module_text("AdvSceneSwitcher.fileTab.remote"));
if (s) {
if (s->remote) {
fileType->setCurrentIndex(1);
} else {
fileType->setCurrentIndex(0);
}
filePath->setText(QString::fromStdString(s->file));
matchText->setPlainText(QString::fromStdString(s->text));
useRegex->setChecked(s->useRegex);
checkModificationDate->setChecked(s->useTime);
checkFileContent->setChecked(s->onlyMatchIfChanged);
}
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{fileType}}", fileType},
{"{{filePath}}", filePath},
{"{{browseButton}}", browseButton},
{"{{matchText}}", matchText},
{"{{useRegex}}", useRegex},
{"{{checkModificationDate}}", checkModificationDate},
{"{{checkFileContent}}", checkFileContent},
{"{{scenes}}", scenes},
{"{{transitions}}", transitions}};
QVBoxLayout *mainLayout = new QVBoxLayout;
QHBoxLayout *line1Layout = new QHBoxLayout;
QHBoxLayout *line2Layout = new QHBoxLayout;
QHBoxLayout *line3Layout = new QHBoxLayout;
placeWidgets(obs_module_text("AdvSceneSwitcher.fileTab.entry"),
line1Layout, widgetPlaceholders);
placeWidgets(obs_module_text("AdvSceneSwitcher.fileTab.entry2"),
line2Layout, widgetPlaceholders, false);
placeWidgets(obs_module_text("AdvSceneSwitcher.fileTab.entry3"),
line3Layout, widgetPlaceholders);
mainLayout->addLayout(line1Layout);
mainLayout->addLayout(line2Layout);
mainLayout->addLayout(line3Layout);
setLayout(mainLayout);
switchData = s;
loading = false;
}
FileSwitch *FileSwitchWidget::getSwitchData()
{
return switchData;
}
void FileSwitchWidget::setSwitchData(FileSwitch *s)
{
switchData = s;
}
void FileSwitchWidget::swapSwitchData(FileSwitchWidget *s1,
FileSwitchWidget *s2)
{
SwitchWidget::swapSwitchData(s1, s2);
FileSwitch *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
void FileSwitchWidget::FileTypeChanged(int index)
{
if (loading || !switchData) {
return;
}
if ((file_type)index == LOCAL) {
browseButton->setDisabled(false);
checkModificationDate->setDisabled(false);
} else {
browseButton->setDisabled(true);
checkModificationDate->setDisabled(true);
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->remote = (file_type)index == REMOTE;
}
void FileSwitchWidget::FilePathChanged()
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->file = filePath->text().toUtf8().constData();
}
void FileSwitchWidget::BrowseButtonClicked()
{
if (loading || !switchData) {
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 FileSwitchWidget::MatchTextChanged()
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->text = matchText->toPlainText().toUtf8().constData();
}
void FileSwitchWidget::UseRegexChanged(int state)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->useRegex = state;
}
void FileSwitchWidget::CheckModificationDateChanged(int state)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->useTime = state;
}
void FileSwitchWidget::CheckFileContentChanged(int state)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->onlyMatchIfChanged = state;
}

295
src/switch-generic.cpp Normal file
View File

@@ -0,0 +1,295 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
bool SceneSwitcherEntry::initialized()
{
return (usePreviousScene || WeakSourceValid(scene) ||
SceneGroupValid(group)) &&
(useCurrentTransition || transition);
}
bool SceneSwitcherEntry::valid()
{
return !initialized() ||
((usePreviousScene || WeakSourceValid(scene) ||
SceneGroupValid(group)) &&
(useCurrentTransition || WeakSourceValid(transition)));
}
void SceneSwitcherEntry::logMatchScene()
{
std::string sceneName = previous_scene_name;
if (!usePreviousScene) {
sceneName = GetWeakSourceName(scene);
}
blog(LOG_INFO, "match for '%s' - switch to scene '%s'", getType(),
sceneName.c_str());
}
void SceneSwitcherEntry::logMatchSceneGroup()
{
if (group->scenes.empty()) {
blog(LOG_INFO,
"match for '%s' - but no scenes specified in '%s'",
getType(), group->name.c_str());
return;
}
std::string sceneName = GetWeakSourceName(group->getCurrentScene());
blog(LOG_INFO, "match for '%s' - switch to scene '%s' using '%s'",
getType(), sceneName.c_str(), group->name.c_str());
}
void SceneSwitcherEntry::logMatch()
{
if (targetType == SwitchTargetType::Scene) {
logMatchScene();
} else if (targetType == SwitchTargetType::SceneGroup) {
logMatchSceneGroup();
}
}
OBSWeakSource SceneSwitcherEntry::getScene()
{
if (targetType == SwitchTargetType::Scene) {
if (usePreviousScene && switcher) {
return switcher->previousScene;
}
return scene;
} else if (targetType == SwitchTargetType::SceneGroup) {
return group->getNextScene();
}
return nullptr;
}
void SceneSwitcherEntry::save(obs_data_t *obj, const char *targetTypeSaveName,
const char *targetSaveName,
const char *transitionSaveName)
{
obs_data_set_int(obj, targetTypeSaveName, static_cast<int>(targetType));
std::string targetName = "";
if (targetType == SwitchTargetType::Scene) {
if (usePreviousScene) {
targetName = previous_scene_name;
} else {
targetName = GetWeakSourceName(scene);
}
} else if (targetType == SwitchTargetType::SceneGroup) {
targetName = group->name.c_str();
}
obs_data_set_string(obj, targetSaveName, targetName.c_str());
std::string transitionName = current_transition_name;
if (!useCurrentTransition) {
transitionName = GetWeakSourceName(transition);
}
obs_data_set_string(obj, transitionSaveName, transitionName.c_str());
}
void SceneSwitcherEntry::load(obs_data_t *obj, const char *targetTypeLoadName,
const char *targetLoadName,
const char *transitionLoadName)
{
targetType = static_cast<SwitchTargetType>(
obs_data_get_int(obj, targetTypeLoadName));
const char *targetName = obs_data_get_string(obj, targetLoadName);
if (targetType == SwitchTargetType::Scene) {
usePreviousScene = strcmp(targetName, previous_scene_name) == 0;
if (!usePreviousScene) {
scene = GetWeakSourceByName(targetName);
}
} else if (targetType == SwitchTargetType::SceneGroup) {
group = GetSceneGroupByName(targetName);
}
usePreviousScene = strcmp(targetName, previous_scene_name) == 0;
const char *transitionName =
obs_data_get_string(obj, transitionLoadName);
transition = GetWeakTransitionByName(transitionName);
useCurrentTransition =
strcmp(transitionName, current_transition_name) == 0;
}
void SwitchWidget::SceneGroupAdd(const QString &name)
{
if (!scenes) {
return;
}
scenes->addItem(name);
}
void SwitchWidget::SceneGroupRemove(const QString &name)
{
if (!scenes) {
return;
}
int idx = scenes->findText(name);
if (idx == -1) {
return;
}
scenes->removeItem(idx);
if (switchData && switchData->group == GetSceneGroupByQString(name)) {
std::lock_guard<std::mutex> lock(switcher->m);
switchData->targetType = SwitchTargetType::Scene;
switchData->scene = nullptr;
}
scenes->setCurrentIndex(0);
}
void SwitchWidget::SceneGroupRename(const QString &oldName,
const QString &newName)
{
if (!scenes) {
return;
}
bool renameSelected = scenes->currentText() == oldName;
int idx = scenes->findText(oldName);
if (idx == -1) {
return;
}
scenes->removeItem(idx);
scenes->insertItem(idx, newName);
if (renameSelected) {
scenes->setCurrentIndex(scenes->findText(newName));
}
}
SwitchWidget::SwitchWidget(QWidget *parent, SceneSwitcherEntry *s,
bool usePreviousScene, bool addSceneGroup,
bool addCurrentTransition)
{
scenes = new QComboBox();
transitions = new QComboBox();
// Depending on selected OBS theme some widgets might have a different
// background color than the listwidget and might look out of place
setStyleSheet("QLabel { background-color: transparent; }\
QSlider { background-color: transparent; }\
QCheckBox { background-color: transparent; }");
QWidget::connect(scenes, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(SceneChanged(const QString &)));
QWidget::connect(transitions,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(TransitionChanged(const QString &)));
QWidget::connect(parent, SIGNAL(SceneGroupAdded(const QString &)), this,
SLOT(SceneGroupAdd(const QString &)));
QWidget::connect(parent, SIGNAL(SceneGroupRemoved(const QString &)),
this, SLOT(SceneGroupRemove(const QString &)));
QWidget::connect(
parent,
SIGNAL(SceneGroupRenamed(const QString &, const QString &)),
this, SLOT(SceneGroupRename(const QString &, const QString &)));
AdvSceneSwitcher::populateSceneSelection(scenes, usePreviousScene,
addSceneGroup);
AdvSceneSwitcher::populateTransitionSelection(transitions,
addCurrentTransition);
switchData = s;
showSwitchData();
}
SceneSwitcherEntry *SwitchWidget::getSwitchData()
{
return switchData;
}
void SwitchWidget::setSwitchData(SceneSwitcherEntry *s)
{
switchData = s;
}
void SwitchWidget::showSwitchData()
{
if (!switchData) {
return;
}
transitions->setCurrentText(
GetWeakSourceName(switchData->transition).c_str());
if (switchData->useCurrentTransition) {
transitions->setCurrentText(
obs_module_text("AdvSceneSwitcher.currentTransition"));
}
if (switchData->usePreviousScene) {
scenes->setCurrentText(obs_module_text(
"AdvSceneSwitcher.selectPreviousScene"));
return;
}
scenes->setCurrentText(GetWeakSourceName(switchData->scene).c_str());
if (switchData->group &&
switchData->targetType == SwitchTargetType::SceneGroup) {
scenes->setCurrentText(
QString::fromStdString(switchData->group->name));
}
}
void SwitchWidget::swapSwitchData(SwitchWidget *s1, SwitchWidget *s2)
{
SceneSwitcherEntry *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
bool isPreviousScene(const QString &text)
{
return text.compare(obs_module_text(
"AdvSceneSwitcher.selectPreviousScene")) == 0;
}
void SwitchWidget::SceneChanged(const QString &text)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->usePreviousScene = isPreviousScene(text);
if (switchData->usePreviousScene) {
switchData->targetType = SwitchTargetType::Scene;
return;
}
switchData->scene = GetWeakSourceByQString(text);
switchData->targetType = SwitchTargetType::Scene;
if (!switchData->scene) {
switchData->group = GetSceneGroupByQString(text);
switchData->targetType = SwitchTargetType::SceneGroup;
}
}
void SwitchWidget::TransitionChanged(const QString &text)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->transition = GetWeakTransitionByQString(text);
switchData->useCurrentTransition = switchData->transition == nullptr;
}

View File

@@ -1,16 +1,21 @@
#include <regex>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
bool IdleData::pause = false;
IdleWidget *idleWidget = nullptr;
void SwitcherData::checkIdleSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (!idleData.idleEnable)
if (!idleData.idleEnable || IdleData::pause) {
return;
}
std::string title;
bool ignoreIdle = false;
//lock.unlock();
GetCurrentWindowTitle(title);
//lock.lock();
for (std::string &window : ignoreIdleWindows) {
if (window == title) {
@@ -34,94 +39,45 @@ void SwitcherData::checkIdleSwitch(bool &match, OBSWeakSource &scene,
}
if (!ignoreIdle && secondsSinceLastInput() > idleData.time) {
if (idleData.alreadySwitched)
if (idleData.alreadySwitched) {
return;
scene = (idleData.usePreviousScene) ? previousScene
: idleData.scene;
}
scene = idleData.getScene();
transition = idleData.transition;
match = true;
idleData.alreadySwitched = true;
if (verbose)
blog(LOG_INFO, "Advanced Scene Switcher idle match");
if (verbose) {
idleData.logMatch();
}
} else
idleData.alreadySwitched = false;
}
void SceneSwitcher::on_idleCheckBox_stateChanged(int state)
void AdvSceneSwitcher::on_idleCheckBox_stateChanged(int state)
{
if (loading)
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
if (!state) {
ui->idleScenes->setDisabled(true);
ui->idleSpinBox->setDisabled(true);
ui->idleTransitions->setDisabled(true);
switcher->idleData.idleEnable = false;
idleWidget->setDisabled(true);
} else {
ui->idleScenes->setDisabled(false);
ui->idleSpinBox->setDisabled(false);
ui->idleTransitions->setDisabled(false);
switcher->idleData.idleEnable = true;
UpdateIdleDataTransition(ui->idleTransitions->currentText());
UpdateIdleDataScene(ui->idleScenes->currentText());
idleWidget->setDisabled(false);
}
}
void SceneSwitcher::UpdateIdleDataTransition(const QString &name)
void AdvSceneSwitcher::on_ignoreIdleWindows_currentRowChanged(int idx)
{
obs_weak_source_t *transition = GetWeakTransitionByQString(name);
switcher->idleData.transition = transition;
}
void SceneSwitcher::UpdateIdleDataScene(const QString &name)
{
switcher->idleData.usePreviousScene = (name == PREVIOUS_SCENE_NAME);
obs_source_t *scene = obs_get_source_by_name(name.toUtf8().constData());
obs_weak_source_t *ws = obs_source_get_weak_source(scene);
switcher->idleData.scene = ws;
obs_weak_source_release(ws);
obs_source_release(scene);
}
void SceneSwitcher::on_idleTransitions_currentTextChanged(const QString &text)
{
if (loading)
if (loading) {
return;
std::lock_guard<std::mutex> lock(switcher->m);
UpdateIdleDataTransition(text);
}
void SceneSwitcher::on_idleScenes_currentTextChanged(const QString &text)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
UpdateIdleDataScene(text);
}
void SceneSwitcher::on_idleSpinBox_valueChanged(int i)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
switcher->idleData.time = i;
}
void SceneSwitcher::on_ignoreIdleWindows_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
}
if (idx == -1) {
return;
}
QListWidgetItem *item = ui->ignoreIdleWindows->item(idx);
@@ -136,12 +92,13 @@ void SceneSwitcher::on_ignoreIdleWindows_currentRowChanged(int idx)
}
}
void SceneSwitcher::on_ignoreIdleAdd_clicked()
void AdvSceneSwitcher::on_ignoreIdleAdd_clicked()
{
QString windowName = ui->ignoreIdleWindowsWindows->currentText();
if (windowName.isEmpty())
if (windowName.isEmpty()) {
return;
}
QVariant v = QVariant::fromValue(windowName);
@@ -160,11 +117,12 @@ void SceneSwitcher::on_ignoreIdleAdd_clicked()
}
}
void SceneSwitcher::on_ignoreIdleRemove_clicked()
void AdvSceneSwitcher::on_ignoreIdleRemove_clicked()
{
QListWidgetItem *item = ui->ignoreIdleWindows->currentItem();
if (!item)
if (!item) {
return;
}
QString windowName = item->data(Qt::UserRole).toString();
@@ -185,7 +143,7 @@ void SceneSwitcher::on_ignoreIdleRemove_clicked()
delete item;
}
int SceneSwitcher::IgnoreIdleWindowsFindByData(const QString &window)
int AdvSceneSwitcher::IgnoreIdleWindowsFindByData(const QString &window)
{
int count = ui->ignoreIdleWindows->count();
int idx = -1;
@@ -215,17 +173,7 @@ void SwitcherData::saveIdleSwitches(obs_data_t *obj)
obs_data_set_array(obj, "ignoreIdleWindows", ignoreIdleWindowsArray);
obs_data_array_release(ignoreIdleWindowsArray);
std::string idleSceneName = GetWeakSourceName(switcher->idleData.scene);
std::string idleTransitionName =
GetWeakSourceName(switcher->idleData.transition);
obs_data_set_bool(obj, "idleEnable", switcher->idleData.idleEnable);
obs_data_set_string(obj, "idleSceneName",
switcher->idleData.usePreviousScene
? PREVIOUS_SCENE_NAME
: idleSceneName.c_str());
obs_data_set_string(obj, "idleTransitionName",
idleTransitionName.c_str());
obs_data_set_int(obj, "idleTime", switcher->idleData.time);
idleData.save(obj);
}
void SwitcherData::loadIdleSwitches(obs_data_t *obj)
@@ -248,24 +196,14 @@ void SwitcherData::loadIdleSwitches(obs_data_t *obj)
}
obs_data_array_release(ignoreIdleWindowsArray);
std::string idleSceneName = obs_data_get_string(obj, "idleSceneName");
std::string idleTransitionName =
obs_data_get_string(obj, "idleTransitionName");
switcher->idleData.scene = GetWeakSourceByName(idleSceneName.c_str());
switcher->idleData.transition =
GetWeakTransitionByName(idleTransitionName.c_str());
obs_data_set_default_bool(obj, "idleEnable", false);
switcher->idleData.idleEnable = obs_data_get_bool(obj, "idleEnable");
obs_data_set_default_int(obj, "idleTime", DEFAULT_IDLE_TIME);
switcher->idleData.time = obs_data_get_int(obj, "idleTime");
switcher->idleData.usePreviousScene =
(idleSceneName == PREVIOUS_SCENE_NAME);
obs_data_set_default_int(obj, "idleTime", default_idle_time);
idleData.load(obj);
}
void SceneSwitcher::setupIdleTab()
void AdvSceneSwitcher::setupIdleTab()
{
populateSceneSelection(ui->idleScenes, true);
populateTransitionSelection(ui->idleTransitions);
populateWindowSelection(ui->ignoreIdleWindowsWindows);
for (auto &window : switcher->ignoreIdleWindows) {
@@ -276,22 +214,71 @@ void SceneSwitcher::setupIdleTab()
item->setData(Qt::UserRole, text);
}
idleWidget = new IdleWidget(this, &switcher->idleData);
ui->idleWidgetLayout->addWidget(idleWidget);
ui->idleCheckBox->setChecked(switcher->idleData.idleEnable);
ui->idleScenes->setCurrentText(
switcher->idleData.usePreviousScene
? PREVIOUS_SCENE_NAME
: GetWeakSourceName(switcher->idleData.scene).c_str());
ui->idleTransitions->setCurrentText(
GetWeakSourceName(switcher->idleData.transition).c_str());
ui->idleSpinBox->setValue(switcher->idleData.time);
if (ui->idleCheckBox->checkState()) {
ui->idleScenes->setDisabled(false);
ui->idleSpinBox->setDisabled(false);
ui->idleTransitions->setDisabled(false);
idleWidget->setDisabled(false);
} else {
ui->idleScenes->setDisabled(true);
ui->idleSpinBox->setDisabled(true);
ui->idleTransitions->setDisabled(true);
idleWidget->setDisabled(true);
}
}
void IdleData::save(obs_data_t *obj)
{
SceneSwitcherEntry::save(obj, "idleTargetType", "idleSceneName",
"idleTransitionName");
obs_data_set_bool(obj, "idleEnable", idleEnable);
obs_data_set_int(obj, "idleTime", time);
}
void IdleData::load(obs_data_t *obj)
{
SceneSwitcherEntry::load(obj, "idleTargetType", "idleSceneName",
"idleTransitionName");
idleEnable = obs_data_get_bool(obj, "idleEnable");
time = obs_data_get_int(obj, "idleTime");
}
IdleWidget::IdleWidget(QWidget *parent, IdleData *s)
: SwitchWidget(parent, s, true, true)
{
duration = new QSpinBox();
duration->setMinimum(0);
duration->setMaximum(1000000);
duration->setSuffix("s");
QWidget::connect(duration, SIGNAL(valueChanged(int)), this,
SLOT(DurationChanged(int)));
if (s) {
duration->setValue(s->time);
}
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{duration}}", duration},
{"{{scenes}}", scenes},
{"{{transitions}}", transitions}};
placeWidgets(obs_module_text("AdvSceneSwitcher.idleTab.idleswitch"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
switchData = s;
loading = false;
}
void IdleWidget::DurationChanged(int dur)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->idleData.time = dur;
}

592
src/switch-media.cpp Normal file
View File

@@ -0,0 +1,592 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
bool MediaSwitch::pause = false;
static QMetaObject::Connection addPulse;
constexpr auto media_played_to_end_idx = 8;
constexpr auto media_any_idx = 9;
void AdvSceneSwitcher::on_mediaAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->mediaSwitches.emplace_back();
listAddClicked(ui->mediaSwitches,
new MediaSwitchWidget(this,
&switcher->mediaSwitches.back()),
ui->mediaAdd, &addPulse);
ui->mediaHelp->setVisible(false);
}
void AdvSceneSwitcher::on_mediaRemove_clicked()
{
QListWidgetItem *item = ui->mediaSwitches->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->mediaSwitches->currentRow();
auto &switches = switcher->mediaSwitches;
switches.erase(switches.begin() + idx);
}
delete item;
}
void AdvSceneSwitcher::on_mediaUp_clicked()
{
int index = ui->mediaSwitches->currentRow();
if (!listMoveUp(ui->mediaSwitches)) {
return;
}
MediaSwitchWidget *s1 =
(MediaSwitchWidget *)ui->mediaSwitches->itemWidget(
ui->mediaSwitches->item(index));
MediaSwitchWidget *s2 =
(MediaSwitchWidget *)ui->mediaSwitches->itemWidget(
ui->mediaSwitches->item(index - 1));
MediaSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->mediaSwitches[index],
switcher->mediaSwitches[index - 1]);
}
void AdvSceneSwitcher::on_mediaDown_clicked()
{
int index = ui->mediaSwitches->currentRow();
if (!listMoveDown(ui->mediaSwitches)) {
return;
}
MediaSwitchWidget *s1 =
(MediaSwitchWidget *)ui->mediaSwitches->itemWidget(
ui->mediaSwitches->item(index));
MediaSwitchWidget *s2 =
(MediaSwitchWidget *)ui->mediaSwitches->itemWidget(
ui->mediaSwitches->item(index + 1));
MediaSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->mediaSwitches[index],
switcher->mediaSwitches[index + 1]);
}
void SwitcherData::checkMediaSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (MediaSwitch::pause) {
return;
}
for (MediaSwitch &mediaSwitch : mediaSwitches) {
if (!mediaSwitch.initialized()) {
continue;
}
obs_source_t *source =
obs_weak_source_get_source(mediaSwitch.source);
auto duration = obs_source_media_get_duration(source);
auto time = obs_source_media_get_time(source);
obs_media_state state = obs_source_media_get_state(source);
obs_source_release(source);
bool matchedStopped = mediaSwitch.state ==
OBS_MEDIA_STATE_STOPPED &&
mediaSwitch.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 = mediaSwitch.state == OBS_MEDIA_STATE_ENDED &&
// mediaSwitch.ended;
bool ended = false;
if (state == OBS_MEDIA_STATE_ENDED) {
ended = mediaSwitch.previousStateEnded;
mediaSwitch.previousStateEnded = true;
} else {
mediaSwitch.previousStateEnded = false;
}
bool matchedEnded =
ended && (mediaSwitch.state == OBS_MEDIA_STATE_ENDED);
// match if playedToEnd was true in last interval
// and playback is currently ended
bool matchedPlayedToEnd = mediaSwitch.state ==
media_played_to_end_idx &&
mediaSwitch.playedToEnd && ended;
// interval * 2 to make sure not to miss any state changes
// which happened during check of the conditions
mediaSwitch.playedToEnd = mediaSwitch.playedToEnd ||
(duration - time <= interval * 2);
// reset
if (ended) {
mediaSwitch.playedToEnd = false;
}
// reset for next check
mediaSwitch.stopped = false;
mediaSwitch.ended = false;
bool matchedState = ((state == mediaSwitch.state) ||
mediaSwitch.anyState) ||
matchedStopped || matchedEnded ||
matchedPlayedToEnd;
bool matchedTimeNone =
(mediaSwitch.restriction == TIME_RESTRICTION_NONE);
bool matchedTimeLonger =
(mediaSwitch.restriction == TIME_RESTRICTION_LONGER) &&
(time > mediaSwitch.time);
bool matchedTimeShorter =
(mediaSwitch.restriction == TIME_RESTRICTION_SHORTER) &&
(time < mediaSwitch.time);
bool matchedTimeRemainLonger =
(mediaSwitch.restriction ==
TIME_RESTRICTION_REMAINING_LONGER) &&
(duration > time && duration - time > mediaSwitch.time);
bool matchedTimeRemainShorter =
(mediaSwitch.restriction ==
TIME_RESTRICTION_REMAINING_SHORTER) &&
(duration > time && duration - time < mediaSwitch.time);
bool matchedTime = matchedTimeNone || matchedTimeLonger ||
matchedTimeShorter ||
matchedTimeRemainLonger ||
matchedTimeRemainShorter;
bool matched = matchedState && matchedTime;
if (matched && !mediaSwitch.matched) {
match = true;
scene = mediaSwitch.getScene();
transition = mediaSwitch.transition;
if (verbose) {
mediaSwitch.logMatch();
}
}
mediaSwitch.matched = matched;
if (match) {
break;
}
}
}
void SwitcherData::saveMediaSwitches(obs_data_t *obj)
{
obs_data_array_t *mediaArray = obs_data_array_create();
for (MediaSwitch &s : switcher->mediaSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
obs_data_array_push_back(mediaArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "mediaSwitches", mediaArray);
obs_data_array_release(mediaArray);
}
void SwitcherData::loadMediaSwitches(obs_data_t *obj)
{
obs_data_array_t *mediaArray = obs_data_get_array(obj, "mediaSwitches");
switcher->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.back().load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(mediaArray);
}
void AdvSceneSwitcher::setupMediaTab()
{
for (auto &s : switcher->mediaSwitches) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->mediaSwitches);
ui->mediaSwitches->addItem(item);
MediaSwitchWidget *sw = new MediaSwitchWidget(this, &s);
item->setSizeHint(sw->minimumSizeHint());
ui->mediaSwitches->setItemWidget(item, sw);
}
if (switcher->mediaSwitches.size() == 0) {
addPulse = PulseWidget(ui->mediaAdd, QColor(Qt::green));
ui->mediaHelp->setVisible(true);
} else {
ui->mediaHelp->setVisible(false);
}
}
bool MediaSwitch::initialized()
{
return SceneSwitcherEntry::initialized() && source;
}
bool MediaSwitch::valid()
{
return !initialized() ||
(SceneSwitcherEntry::valid() && WeakSourceValid(source));
}
void MediaSwitch::save(obs_data_t *obj)
{
SceneSwitcherEntry::save(obj);
obs_data_set_string(obj, "source", GetWeakSourceName(source).c_str());
obs_data_set_int(obj, "state", state);
obs_data_set_int(obj, "restriction", restriction);
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");
source = GetWeakSourceByName(sourceName);
state = (obs_media_state)obs_data_get_int(obj, "state");
restriction = (time_restriction)obs_data_get_int(obj, "restriction");
time = obs_data_get_int(obj, "time");
anyState = state == media_any_idx;
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);
}
void MediaSwitch::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 MediaSwitch::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 MediaSwitch::MediaStopped(void *data, calldata_t *)
{
MediaSwitch *media = static_cast<MediaSwitch *>(data);
media->stopped = true;
}
void MediaSwitch::MediaEnded(void *data, calldata_t *)
{
MediaSwitch *media = static_cast<MediaSwitch *>(data);
media->ended = true;
}
MediaSwitch::MediaSwitch(const MediaSwitch &other)
: SceneSwitcherEntry(other.targetType, other.group, other.scene,
other.transition, other.usePreviousScene),
source(other.source),
state(other.state),
restriction(other.restriction),
time(other.time)
{
anyState = state == media_any_idx;
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);
}
MediaSwitch::MediaSwitch(MediaSwitch &&other)
: SceneSwitcherEntry(other.targetType, other.group, other.scene,
other.transition, other.usePreviousScene),
source(other.source),
state(other.state),
anyState(other.anyState),
restriction(other.restriction),
time(other.time)
{
}
MediaSwitch::~MediaSwitch()
{
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);
}
MediaSwitch &MediaSwitch::operator=(const MediaSwitch &other)
{
MediaSwitch t(other);
swap(*this, t);
return *this = MediaSwitch(other);
}
MediaSwitch &MediaSwitch::operator=(MediaSwitch &&other) noexcept
{
if (this == &other) {
return *this;
}
swap(*this, other);
obs_source_t *mediasource = obs_weak_source_get_source(other.source);
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
signal_handler_disconnect(sh, "media_stopped", MediaStopped, &other);
signal_handler_disconnect(sh, "media_ended", MediaEnded, &other);
obs_source_release(mediasource);
return *this;
}
void swap(MediaSwitch &first, MediaSwitch &second)
{
std::swap(first.targetType, second.targetType);
std::swap(first.group, second.group);
std::swap(first.scene, second.scene);
std::swap(first.transition, second.transition);
std::swap(first.usePreviousScene, second.usePreviousScene);
std::swap(first.source, second.source);
std::swap(first.state, second.state);
std::swap(first.restriction, second.restriction);
std::swap(first.time, second.time);
std::swap(first.anyState, second.anyState);
first.resetSignalHandler();
second.resetSignalHandler();
}
void populateMediaStates(QComboBox *list)
{
list->addItem(obs_module_text("AdvSceneSwitcher.mediaTab.states.none"));
list->addItem(
obs_module_text("AdvSceneSwitcher.mediaTab.states.playing"));
list->addItem(
obs_module_text("AdvSceneSwitcher.mediaTab.states.opening"));
list->addItem(
obs_module_text("AdvSceneSwitcher.mediaTab.states.buffering"));
list->addItem(
obs_module_text("AdvSceneSwitcher.mediaTab.states.paused"));
list->addItem(
obs_module_text("AdvSceneSwitcher.mediaTab.states.stopped"));
list->addItem(
obs_module_text("AdvSceneSwitcher.mediaTab.states.ended"));
list->addItem(
obs_module_text("AdvSceneSwitcher.mediaTab.states.error"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.mediaTab.states.playedToEnd"));
list->addItem(obs_module_text("AdvSceneSwitcher.mediaTab.states.any"));
}
void populateTimeRestrictions(QComboBox *list)
{
list->addItem(obs_module_text(
"AdvSceneSwitcher.mediaTab.timeRestriction.none"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.mediaTab.timeRestriction.shorter"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.mediaTab.timeRestriction.longer"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.mediaTab.timeRestriction.remainShorter"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger"));
}
MediaSwitchWidget::MediaSwitchWidget(QWidget *parent, MediaSwitch *s)
: SwitchWidget(parent, s, true, true)
{
mediaSources = new QComboBox();
states = new QComboBox();
timeRestrictions = new QComboBox();
time = new QSpinBox();
time->setSuffix("ms");
time->setMaximum(99999999);
time->setMinimum(0);
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(valueChanged(int)), this,
SLOT(TimeChanged(int)));
AdvSceneSwitcher::populateMediaSelection(mediaSources);
populateMediaStates(states);
populateTimeRestrictions(timeRestrictions);
if (s) {
mediaSources->setCurrentText(
GetWeakSourceName(s->source).c_str());
states->setCurrentIndex(s->state);
timeRestrictions->setCurrentIndex(s->restriction);
time->setValue(s->time);
if (s->restriction == TIME_RESTRICTION_NONE) {
time->setDisabled(true);
}
}
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{mediaSources}}", mediaSources},
{"{{states}}", states},
{"{{timeRestrictions}}", timeRestrictions},
{"{{time}}", time},
{"{{scenes}}", scenes},
{"{{transitions}}", transitions}};
placeWidgets(obs_module_text("AdvSceneSwitcher.mediaTab.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
switchData = s;
loading = false;
}
MediaSwitch *MediaSwitchWidget::getSwitchData()
{
return switchData;
}
void MediaSwitchWidget::setSwitchData(MediaSwitch *s)
{
switchData = s;
}
void MediaSwitchWidget::swapSwitchData(MediaSwitchWidget *s1,
MediaSwitchWidget *s2)
{
SwitchWidget::swapSwitchData(s1, s2);
MediaSwitch *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
void MediaSwitchWidget::SourceChanged(const QString &text)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->clearSignalHandler();
switchData->source = GetWeakSourceByQString(text);
switchData->resetSignalHandler();
}
void MediaSwitchWidget::StateChanged(int index)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->state = (obs_media_state)index;
switchData->anyState = switchData->state == media_any_idx;
}
void MediaSwitchWidget::TimeRestrictionChanged(int index)
{
if (loading || !switchData) {
return;
}
if ((time_restriction)index == TIME_RESTRICTION_NONE) {
time->setDisabled(true);
} else {
time->setDisabled(false);
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->restriction = (time_restriction)index;
}
void MediaSwitchWidget::TimeChanged(int time)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->time = time;
}

467
src/switch-pause.cpp Normal file
View File

@@ -0,0 +1,467 @@
#include <regex>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::on_pauseAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->pauseEntries.emplace_back();
listAddClicked(ui->pauseEntries,
new PauseEntryWidget(this,
&switcher->pauseEntries.back()),
ui->pauseAdd, &addPulse);
ui->pauseHelp->setVisible(false);
}
void AdvSceneSwitcher::on_pauseRemove_clicked()
{
QListWidgetItem *item = ui->pauseEntries->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->pauseEntries->currentRow();
auto &switches = switcher->pauseEntries;
switches.erase(switches.begin() + idx);
}
delete item;
}
void AdvSceneSwitcher::on_pauseUp_clicked()
{
int index = ui->pauseEntries->currentRow();
if (!listMoveUp(ui->pauseEntries)) {
return;
}
PauseEntryWidget *s1 = (PauseEntryWidget *)ui->pauseEntries->itemWidget(
ui->pauseEntries->item(index));
PauseEntryWidget *s2 = (PauseEntryWidget *)ui->pauseEntries->itemWidget(
ui->pauseEntries->item(index - 1));
PauseEntryWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->pauseEntries[index],
switcher->pauseEntries[index - 1]);
}
void AdvSceneSwitcher::on_pauseDown_clicked()
{
int index = ui->pauseEntries->currentRow();
if (!listMoveDown(ui->pauseEntries)) {
return;
}
PauseEntryWidget *s1 = (PauseEntryWidget *)ui->pauseEntries->itemWidget(
ui->pauseEntries->item(index));
PauseEntryWidget *s2 = (PauseEntryWidget *)ui->pauseEntries->itemWidget(
ui->pauseEntries->item(index + 1));
PauseEntryWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->pauseEntries[index],
switcher->pauseEntries[index + 1]);
}
void resetPause()
{
DefaultSceneTransition::pause = false;
WindowSwitch::pause = false;
ExecutableSwitch::pause = false;
ScreenRegionSwitch::pause = false;
MediaSwitch::pause = false;
FileSwitch::pause = false;
RandomSwitch::pause = false;
TimeSwitch::pause = false;
IdleData::pause = false;
SceneSequenceSwitch::pause = false;
AudioSwitch::pause = false;
VideoSwitch::pause = false;
}
void setPauseTarget(PauseTarget &target, bool &verbose)
{
switch (target) {
case PauseTarget::All:
vblog(LOG_INFO, "pause all switching");
break;
case PauseTarget::Transition:
vblog(LOG_INFO, "pause def_transition switching");
DefaultSceneTransition::pause = true;
break;
case PauseTarget::Window:
vblog(LOG_INFO, "pause window switching");
WindowSwitch::pause = true;
break;
case PauseTarget::Executable:
vblog(LOG_INFO, "pause exec switching");
ExecutableSwitch::pause = true;
break;
case PauseTarget::Region:
vblog(LOG_INFO, "pause region switching");
ScreenRegionSwitch::pause = true;
break;
case PauseTarget::Media:
vblog(LOG_INFO, "pause media switching");
MediaSwitch::pause = true;
break;
case PauseTarget::File:
vblog(LOG_INFO, "pause file switching");
FileSwitch::pause = true;
break;
case PauseTarget::Random:
vblog(LOG_INFO, "pause random switching");
RandomSwitch::pause = true;
break;
case PauseTarget::Time:
vblog(LOG_INFO, "pause time switching");
TimeSwitch::pause = true;
break;
case PauseTarget::Idle:
vblog(LOG_INFO, "pause idle switching");
IdleData::pause = true;
break;
case PauseTarget::Sequence:
vblog(LOG_INFO, "pause sequence switching");
SceneSequenceSwitch::pause = true;
break;
case PauseTarget::Audio:
vblog(LOG_INFO, "pause audio switching");
AudioSwitch::pause = true;
break;
case PauseTarget::Video:
vblog(LOG_INFO, "pause video switching");
VideoSwitch::pause = true;
break;
}
}
bool checkPauseScene(obs_weak_source_t *currentScene, obs_weak_source_t *scene,
PauseTarget &target, bool &verbose)
{
if (currentScene != scene) {
return false;
}
setPauseTarget(target, verbose);
return (target == PauseTarget::All);
}
bool checkPauseWindow(std::string &currentTitle, std::string &title,
PauseTarget &target, bool &verbose)
{
if (currentTitle != title) {
return false;
}
setPauseTarget(target, verbose);
return (target == PauseTarget::All);
}
bool SwitcherData::checkPause()
{
bool pauseAll = false;
std::string title;
GetCurrentWindowTitle(title);
resetPause();
obs_source_t *currentSource = obs_frontend_get_current_scene();
obs_weak_source_t *ws = obs_source_get_weak_source(currentSource);
for (PauseEntry &s : pauseEntries) {
if (s.pauseType == PauseType::Scene) {
pauseAll = checkPauseScene(ws, s.scene, s.pauseTarget,
verbose);
} else {
pauseAll = checkPauseWindow(title, s.window,
s.pauseTarget, verbose);
}
if (pauseAll) {
break;
}
}
obs_source_release(currentSource);
obs_weak_source_release(ws);
return pauseAll;
}
void SwitcherData::savePauseSwitches(obs_data_t *obj)
{
obs_data_array_t *pauseScenesArray = obs_data_array_create();
for (PauseEntry &s : switcher->pauseEntries) {
obs_data_t *array_obj = obs_data_create();
obs_data_set_int(array_obj, "pauseType",
static_cast<int>(s.pauseType));
obs_data_set_int(array_obj, "pauseTarget",
static_cast<int>(s.pauseTarget));
obs_data_set_string(array_obj, "pauseWindow", s.window.c_str());
obs_source_t *source = obs_weak_source_get_source(s.scene);
if (source) {
const char *n = obs_source_get_name(source);
obs_data_set_string(array_obj, "pauseScene", n);
}
obs_data_array_push_back(pauseScenesArray, array_obj);
obs_source_release(source);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "pauseEntries", pauseScenesArray);
obs_data_set_int(obj, "oldPauseValuesImported", 1);
obs_data_array_release(pauseScenesArray);
}
void SwitcherData::loadPauseSwitches(obs_data_t *obj)
{
switcher->pauseEntries.clear();
// To be removed in future builds once most users have migrated
loadOldPauseSwitches(obj);
obs_data_array_t *pauseArray = obs_data_get_array(obj, "pauseEntries");
size_t count = obs_data_array_count(pauseArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(pauseArray, i);
PauseType type = static_cast<PauseType>(
obs_data_get_int(array_obj, "pauseType"));
PauseTarget target = static_cast<PauseTarget>(
obs_data_get_int(array_obj, "pauseTarget"));
const char *scene =
obs_data_get_string(array_obj, "pauseScene");
const char *window =
obs_data_get_string(array_obj, "pauseWindow");
switcher->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) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->pauseEntries);
ui->pauseEntries->addItem(item);
PauseEntryWidget *sw = new PauseEntryWidget(this, &s);
item->setSizeHint(sw->minimumSizeHint());
ui->pauseEntries->setItemWidget(item, sw);
}
if (switcher->pauseEntries.size() == 0) {
addPulse = PulseWidget(ui->pauseAdd, QColor(Qt::green));
ui->pauseHelp->setVisible(true);
} else {
ui->pauseHelp->setVisible(false);
}
}
void populatePauseTypes(QComboBox *list)
{
list->addItem(
obs_module_text("AdvSceneSwitcher.pauseTab.pauseTypeScene"));
list->addItem(
obs_module_text("AdvSceneSwitcher.pauseTab.pauseTypeWindow"));
}
void populatePauseTargets(QComboBox *list)
{
list->addItem(
obs_module_text("AdvSceneSwitcher.pauseTab.pauseTargetAll"));
list->addItem(obs_module_text("AdvSceneSwitcher.transitionTab.title"));
list->addItem(obs_module_text("AdvSceneSwitcher.windowTitleTab.title"));
list->addItem(obs_module_text("AdvSceneSwitcher.executableTab.title"));
list->addItem(
obs_module_text("AdvSceneSwitcher.screenRegionTab.title"));
list->addItem(obs_module_text("AdvSceneSwitcher.mediaTab.title"));
list->addItem(obs_module_text("AdvSceneSwitcher.fileTab.title"));
list->addItem(obs_module_text("AdvSceneSwitcher.randomTab.title"));
list->addItem(obs_module_text("AdvSceneSwitcher.timeTab.title"));
list->addItem(obs_module_text("AdvSceneSwitcher.idleTab.title"));
list->addItem(
obs_module_text("AdvSceneSwitcher.sceneSequenceTab.title"));
list->addItem(obs_module_text("AdvSceneSwitcher.audioTab.title"));
list->addItem(obs_module_text("AdvSceneSwitcher.VideoTab.title"));
}
PauseEntryWidget::PauseEntryWidget(QWidget *parent, PauseEntry *s)
: SwitchWidget(parent, s, false, false)
{
pauseTypes = new QComboBox();
pauseTargets = new QComboBox();
windows = new QComboBox();
QWidget::connect(pauseTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(PauseTypeChanged(int)));
QWidget::connect(pauseTargets, SIGNAL(currentIndexChanged(int)), this,
SLOT(PauseTargetChanged(int)));
QWidget::connect(windows, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(WindowChanged(const QString &)));
populatePauseTypes(pauseTypes);
populatePauseTargets(pauseTargets);
AdvSceneSwitcher::populateWindowSelection(windows);
windows->setEditable(true);
windows->setMaxVisibleItems(20);
if (s) {
scenes->setCurrentText(GetWeakSourceName(s->scene).c_str());
pauseTypes->setCurrentIndex(static_cast<int>(s->pauseType));
pauseTargets->setCurrentIndex(static_cast<int>(s->pauseTarget));
windows->setCurrentText(s->window.c_str());
if (s->pauseType == PauseType::Scene) {
windows->setDisabled(true);
windows->setVisible(false);
} else {
scenes->setDisabled(true);
scenes->setVisible(false);
}
}
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", scenes},
{"{{pauseTypes}}", pauseTypes},
{"{{pauseTargets}}", pauseTargets},
{"{{windows}}", windows}};
placeWidgets(obs_module_text("AdvSceneSwitcher.pauseTab.pauseEntry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
switchData = s;
loading = false;
}
PauseEntry *PauseEntryWidget::getSwitchData()
{
return switchData;
}
void PauseEntryWidget::setSwitchData(PauseEntry *s)
{
switchData = s;
}
void PauseEntryWidget::swapSwitchData(PauseEntryWidget *s1,
PauseEntryWidget *s2)
{
SwitchWidget::swapSwitchData(s1, s2);
PauseEntry *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
void PauseEntryWidget::PauseTypeChanged(int index)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->pauseType = static_cast<PauseType>(index);
if (switchData->pauseType == PauseType::Scene) {
windows->setDisabled(true);
windows->setVisible(false);
scenes->setDisabled(false);
scenes->setVisible(true);
} else {
scenes->setDisabled(true);
scenes->setVisible(false);
windows->setDisabled(false);
windows->setVisible(true);
}
}
void PauseEntryWidget::PauseTargetChanged(int index)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->pauseTarget = static_cast<PauseTarget>(index);
}
void PauseEntryWidget::WindowChanged(const QString &text)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->window = text.toStdString();
}
bool PauseEntry::valid()
{
return true;
}

View File

@@ -1,10 +1,11 @@
#include <algorithm>
#include "headers/advanced-scene-switcher.hpp"
void SceneSwitcher::on_threadPriority_currentTextChanged(const QString &text)
void AdvSceneSwitcher::on_threadPriority_currentTextChanged(const QString &text)
{
if (loading ||
ui->threadPriority->count() != switcher->threadPriorities.size())
if (loading || ui->threadPriority->count() !=
(int)switcher->threadPriorities.size())
return;
std::lock_guard<std::mutex> lock(switcher->m);
@@ -17,7 +18,7 @@ void SceneSwitcher::on_threadPriority_currentTextChanged(const QString &text)
}
}
void SceneSwitcher::on_priorityUp_clicked()
void AdvSceneSwitcher::on_priorityUp_clicked()
{
int currentIndex = ui->priorityList->currentRow();
if (currentIndex != -1 && currentIndex != 0) {
@@ -34,7 +35,7 @@ void SceneSwitcher::on_priorityUp_clicked()
}
}
void SceneSwitcher::on_priorityDown_clicked()
void AdvSceneSwitcher::on_priorityDown_clicked()
{
int currentIndex = ui->priorityList->currentRow();
if (currentIndex != -1 &&
@@ -54,15 +55,20 @@ void SceneSwitcher::on_priorityDown_clicked()
bool SwitcherData::prioFuncsValid()
{
auto it = std::unique(functionNamesByPriority.begin(),
functionNamesByPriority.end());
bool wasUnique = (it == functionNamesByPriority.end());
if (!wasUnique)
auto fNBPCopy = functionNamesByPriority;
std::sort(fNBPCopy.begin(), fNBPCopy.end());
auto it = std::unique(fNBPCopy.begin(), fNBPCopy.end());
bool wasUnique = (it == fNBPCopy.end());
if (!wasUnique) {
return false;
}
for (int p : functionNamesByPriority) {
if (p < 0 || p > 7)
if (p < 0 || p > 9) {
return false;
}
}
return true;
}

201
src/switch-random.cpp Normal file
View File

@@ -0,0 +1,201 @@
#include <random>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
bool RandomSwitch::pause = false;
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::on_randomAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->randomSwitches.emplace_back();
listAddClicked(ui->randomSwitches,
new RandomSwitchWidget(this,
&switcher->randomSwitches.back()),
ui->randomAdd, &addPulse);
ui->randomHelp->setVisible(false);
}
void AdvSceneSwitcher::on_randomRemove_clicked()
{
QListWidgetItem *item = ui->randomSwitches->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->randomSwitches->currentRow();
auto &switches = switcher->randomSwitches;
switches.erase(switches.begin() + idx);
}
delete item;
}
void SwitcherData::checkRandom(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition, int &delay)
{
if (randomSwitches.size() == 0 || RandomSwitch::pause) {
return;
}
std::deque<RandomSwitch> rs(randomSwitches);
std::random_device rng;
std::mt19937 urng(rng());
std::shuffle(rs.begin(), rs.end(), urng);
for (RandomSwitch &r : rs) {
if (!r.initialized()) {
continue;
}
if (r.scene == lastRandomScene && randomSwitches.size() != 1) {
continue;
}
scene = r.getScene();
transition = r.transition;
delay = (int)r.delay * 1000;
match = true;
lastRandomScene = r.scene;
if (verbose) {
r.logMatch();
}
break;
}
}
void SwitcherData::saveRandomSwitches(obs_data_t *obj)
{
obs_data_array_t *randomArray = obs_data_array_create();
for (RandomSwitch &s : switcher->randomSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
obs_data_array_push_back(randomArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "randomSwitches", randomArray);
obs_data_array_release(randomArray);
}
void SwitcherData::loadRandomSwitches(obs_data_t *obj)
{
switcher->randomSwitches.clear();
obs_data_array_t *randomArray =
obs_data_get_array(obj, "randomSwitches");
size_t count = obs_data_array_count(randomArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(randomArray, i);
switcher->randomSwitches.emplace_back();
randomSwitches.back().load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(randomArray);
}
void AdvSceneSwitcher::setupRandomTab()
{
for (auto &s : switcher->randomSwitches) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->randomSwitches);
ui->randomSwitches->addItem(item);
RandomSwitchWidget *sw = new RandomSwitchWidget(this, &s);
item->setSizeHint(sw->minimumSizeHint());
ui->randomSwitches->setItemWidget(item, sw);
}
if (switcher->randomSwitches.size() == 0) {
addPulse = PulseWidget(ui->randomAdd, QColor(Qt::green));
ui->randomHelp->setVisible(true);
} else {
ui->randomHelp->setVisible(false);
}
if (switcher->switchIfNotMatching != RANDOM_SWITCH) {
PulseWidget(ui->randomDisabledWarning, QColor(Qt::red),
QColor(0, 0, 0, 0), "QLabel ");
} else {
ui->randomDisabledWarning->setVisible(false);
}
}
void RandomSwitch::save(obs_data_t *obj)
{
SceneSwitcherEntry::save(obj, "targetType", "scene");
obs_data_set_double(obj, "delay", delay);
}
void RandomSwitch::load(obs_data_t *obj)
{
SceneSwitcherEntry::load(obj, "targetType", "scene");
delay = obs_data_get_double(obj, "delay");
}
RandomSwitchWidget::RandomSwitchWidget(QWidget *parent, RandomSwitch *s)
: SwitchWidget(parent, s, false, false)
{
delay = new QDoubleSpinBox();
QWidget::connect(delay, SIGNAL(valueChanged(double)), this,
SLOT(DelayChanged(double)));
delay->setSuffix("s");
delay->setMaximum(999999999.9);
if (s) {
delay->setValue(s->delay);
}
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", scenes},
{"{{transitions}}", transitions},
{"{{delay}}", delay}};
placeWidgets(obs_module_text("AdvSceneSwitcher.randomTab.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
switchData = s;
loading = false;
}
RandomSwitch *RandomSwitchWidget::getSwitchData()
{
return switchData;
}
void RandomSwitchWidget::setSwitchData(RandomSwitch *s)
{
switchData = s;
}
void RandomSwitchWidget::swapSwitchData(RandomSwitchWidget *s1,
RandomSwitchWidget *s2)
{
SwitchWidget::swapSwitchData(s1, s2);
RandomSwitch *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
void RandomSwitchWidget::DelayChanged(double d)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->delay = d;
}

View File

@@ -0,0 +1,489 @@
#include <QTimer>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
bool ScreenRegionSwitch::pause = false;
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::clearFrames(QListWidget *list)
{
for (int i = 0; i < list->count(); ++i) {
ScreenRegionWidget *sw =
(ScreenRegionWidget *)list->itemWidget(list->item(i));
sw->hideFrame();
}
}
void showCurrentFrame(QListWidget *list)
{
QListWidgetItem *item = list->currentItem();
if (!item) {
return;
}
ScreenRegionWidget *sw = (ScreenRegionWidget *)list->itemWidget(item);
sw->showFrame();
}
void AdvSceneSwitcher::SetShowFrames()
{
ui->showFrame->setText(obs_module_text(
"AdvSceneSwitcher.screenRegionTab.showGuideFrames"));
}
void AdvSceneSwitcher::SetHideFrames()
{
ui->showFrame->setText(obs_module_text(
"AdvSceneSwitcher.screenRegionTab.hideGuideFrames"));
}
void AdvSceneSwitcher::on_showFrame_clicked()
{
switcher->showFrame = !switcher->showFrame;
if (switcher->showFrame) {
SetHideFrames();
showCurrentFrame(ui->screenRegionSwitches);
} else {
SetShowFrames();
clearFrames(ui->screenRegionSwitches);
}
}
void AdvSceneSwitcher::on_screenRegionSwitches_currentRowChanged(int idx)
{
if (loading || idx == -1) {
return;
}
if (switcher->showFrame) {
clearFrames(ui->screenRegionSwitches);
showCurrentFrame(ui->screenRegionSwitches);
}
}
void AdvSceneSwitcher::on_screenRegionAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->screenRegionSwitches.emplace_back();
listAddClicked(ui->screenRegionSwitches,
new ScreenRegionWidget(
this, &switcher->screenRegionSwitches.back()),
ui->screenRegionAdd, &addPulse);
ui->regionHelp->setVisible(false);
}
void AdvSceneSwitcher::on_screenRegionRemove_clicked()
{
QListWidgetItem *item = ui->screenRegionSwitches->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->screenRegionSwitches->currentRow();
auto &switches = switcher->screenRegionSwitches;
switches.erase(switches.begin() + idx);
}
delete item;
}
void AdvSceneSwitcher::on_screenRegionUp_clicked()
{
int index = ui->screenRegionSwitches->currentRow();
if (!listMoveUp(ui->screenRegionSwitches)) {
return;
}
ScreenRegionWidget *s1 =
(ScreenRegionWidget *)ui->screenRegionSwitches->itemWidget(
ui->screenRegionSwitches->item(index));
ScreenRegionWidget *s2 =
(ScreenRegionWidget *)ui->screenRegionSwitches->itemWidget(
ui->screenRegionSwitches->item(index - 1));
ScreenRegionWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->screenRegionSwitches[index],
switcher->screenRegionSwitches[index - 1]);
}
void AdvSceneSwitcher::on_screenRegionDown_clicked()
{
int index = ui->screenRegionSwitches->currentRow();
if (!listMoveDown(ui->screenRegionSwitches)) {
return;
}
ScreenRegionWidget *s1 =
(ScreenRegionWidget *)ui->screenRegionSwitches->itemWidget(
ui->screenRegionSwitches->item(index));
ScreenRegionWidget *s2 =
(ScreenRegionWidget *)ui->screenRegionSwitches->itemWidget(
ui->screenRegionSwitches->item(index + 1));
ScreenRegionWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->screenRegionSwitches[index],
switcher->screenRegionSwitches[index + 1]);
}
bool shouldIgnoreSceneSwitch(ScreenRegionSwitch &matchingRegion)
{
if (!matchingRegion.excludeScene) {
return false;
}
obs_source_t *currentScene = obs_frontend_get_current_scene();
OBSWeakSource ws = obs_source_get_weak_source(currentScene);
obs_weak_source_release(ws);
obs_source_release(currentScene);
return matchingRegion.excludeScene == ws;
}
void SwitcherData::checkScreenRegionSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (ScreenRegionSwitch::pause) {
return;
}
std::pair<int, int> cursorPos = getCursorPos();
int minRegionSize = 99999;
for (auto &s : screenRegionSwitches) {
if (!s.initialized()) {
continue;
}
if (cursorPos.first >= s.minX && cursorPos.second >= s.minY &&
cursorPos.first <= s.maxX && cursorPos.second <= s.maxY) {
int regionSize = (s.maxX - s.minX) + (s.maxY - s.minY);
if (regionSize < minRegionSize) {
if (shouldIgnoreSceneSwitch(s)) {
// We technically have a match.
// But just ignore it.
return;
}
match = true;
scene = s.getScene();
transition = s.transition;
minRegionSize = regionSize;
if (verbose) {
s.logMatch();
}
break;
}
}
}
}
void AdvSceneSwitcher::updateScreenRegionCursorPos()
{
std::pair<int, int> position = getCursorPos();
ui->cursorXPosition->setText(QString::number(position.first));
ui->cursorYPosition->setText(QString::number(position.second));
}
void SwitcherData::saveScreenRegionSwitches(obs_data_t *obj)
{
obs_data_array_t *screenRegionArray = obs_data_array_create();
for (ScreenRegionSwitch &s : switcher->screenRegionSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
obs_data_array_push_back(screenRegionArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "screenRegion", screenRegionArray);
obs_data_array_release(screenRegionArray);
}
void SwitcherData::loadScreenRegionSwitches(obs_data_t *obj)
{
switcher->screenRegionSwitches.clear();
obs_data_array_t *screenRegionArray =
obs_data_get_array(obj, "screenRegion");
size_t count = obs_data_array_count(screenRegionArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj =
obs_data_array_item(screenRegionArray, i);
switcher->screenRegionSwitches.emplace_back();
screenRegionSwitches.back().load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(screenRegionArray);
}
void AdvSceneSwitcher::setupRegionTab()
{
for (auto &s : switcher->screenRegionSwitches) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->screenRegionSwitches);
ui->screenRegionSwitches->addItem(item);
ScreenRegionWidget *sw = new ScreenRegionWidget(this, &s);
item->setSizeHint(sw->minimumSizeHint());
ui->screenRegionSwitches->setItemWidget(item, sw);
}
if (switcher->screenRegionSwitches.size() == 0) {
addPulse = PulseWidget(ui->screenRegionAdd, QColor(Qt::green));
ui->regionHelp->setVisible(true);
} else {
ui->regionHelp->setVisible(false);
}
// screen region cursor position
QTimer *screenRegionTimer = new QTimer(this);
connect(screenRegionTimer, SIGNAL(timeout()), this,
SLOT(updateScreenRegionCursorPos()));
screenRegionTimer->start(1000);
}
void ScreenRegionSwitch::save(obs_data_t *obj)
{
SceneSwitcherEntry::save(obj);
obs_data_set_string(obj, "excludeScene",
GetWeakSourceName(excludeScene).c_str());
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);
}
// 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");
excludeScene = GetWeakSourceByName(excludeSceneName);
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");
}
ScreenRegionWidget::ScreenRegionWidget(QWidget *parent, ScreenRegionSwitch *s)
: SwitchWidget(parent, s, true, true)
{
excludeScenes = new QComboBox();
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(excludeScenes,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(ExcludeSceneChanged(const QString &)));
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)));
AdvSceneSwitcher::populateSceneSelection(
excludeScenes, false, false, true,
obs_module_text(
"AdvSceneSwitcher.screenRegionTab.excludeScenes.None"),
true);
if (s) {
excludeScenes->setCurrentText(
GetWeakSourceName(s->excludeScene).c_str());
minX->setValue(s->minX);
minY->setValue(s->minY);
maxX->setValue(s->maxX);
maxY->setValue(s->maxY);
}
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{minX}}", minX},
{"{{minY}}", minY},
{"{{maxX}}", maxX},
{"{{maxY}}", maxY},
{"{{scenes}}", scenes},
{"{{transitions}}", transitions},
{"{{excludeScenes}}", excludeScenes}};
placeWidgets(obs_module_text("AdvSceneSwitcher.screenRegionTab.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
switchData = s;
loading = false;
}
ScreenRegionSwitch *ScreenRegionWidget::getSwitchData()
{
return switchData;
}
void ScreenRegionWidget::setSwitchData(ScreenRegionSwitch *s)
{
switchData = s;
}
void ScreenRegionWidget::swapSwitchData(ScreenRegionWidget *s1,
ScreenRegionWidget *s2)
{
SwitchWidget::swapSwitchData(s1, s2);
ScreenRegionSwitch *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
void ScreenRegionWidget::showFrame()
{
drawFrame();
helperFrame.show();
}
void ScreenRegionWidget::hideFrame()
{
helperFrame.hide();
}
void ScreenRegionWidget::ExcludeSceneChanged(const QString &text)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->excludeScene = GetWeakSourceByQString(text);
}
void ScreenRegionWidget::MinXChanged(int pos)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->minX = pos;
drawFrame();
}
void ScreenRegionWidget::MinYChanged(int pos)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->minY = pos;
drawFrame();
}
void ScreenRegionWidget::MaxXChanged(int pos)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->maxX = pos;
drawFrame();
}
void ScreenRegionWidget::MaxYChanged(int pos)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->maxY = pos;
drawFrame();
}
void ScreenRegionWidget::drawFrame()
{
helperFrame.setFrameStyle(QFrame::Box | QFrame::Plain);
helperFrame.setWindowFlags(Qt::FramelessWindowHint | Qt::Tool |
Qt::WindowTransparentForInput |
Qt::WindowDoesNotAcceptFocus |
Qt::WindowStaysOnTopHint);
helperFrame.setAttribute(Qt::WA_TranslucentBackground, true);
if (switchData) {
helperFrame.setGeometry(switchData->minX, switchData->minY,
switchData->maxX - switchData->minX,
switchData->maxY - switchData->minY);
}
}

897
src/switch-sequence.cpp Normal file
View File

@@ -0,0 +1,897 @@
#include <QFileDialog>
#include <QTextStream>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
constexpr auto max_extend_text_size = 150;
bool SceneSequenceSwitch::pause = false;
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::on_sceneSequenceAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->sceneSequenceSwitches.emplace_back();
listAddClicked(ui->sceneSequenceSwitches,
new SequenceWidget(
this, &switcher->sceneSequenceSwitches.back()),
ui->sceneSequenceAdd, &addPulse);
ui->sequenceHelp->setVisible(false);
}
void AdvSceneSwitcher::on_sceneSequenceRemove_clicked()
{
QListWidgetItem *item = ui->sceneSequenceSwitches->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->sceneSequenceSwitches->currentRow();
auto &switches = switcher->sceneSequenceSwitches;
switches.erase(switches.begin() + idx);
}
delete item;
}
void AdvSceneSwitcher::on_sceneSequenceUp_clicked()
{
int index = ui->sceneSequenceSwitches->currentRow();
if (!listMoveUp(ui->sceneSequenceSwitches)) {
return;
}
SequenceWidget *s1 =
(SequenceWidget *)ui->sceneSequenceSwitches->itemWidget(
ui->sceneSequenceSwitches->item(index));
SequenceWidget *s2 =
(SequenceWidget *)ui->sceneSequenceSwitches->itemWidget(
ui->sceneSequenceSwitches->item(index - 1));
SequenceWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->sceneSequenceSwitches[index],
switcher->sceneSequenceSwitches[index - 1]);
}
void AdvSceneSwitcher::on_sceneSequenceDown_clicked()
{
int index = ui->sceneSequenceSwitches->currentRow();
if (!listMoveDown(ui->sceneSequenceSwitches)) {
return;
}
SequenceWidget *s1 =
(SequenceWidget *)ui->sceneSequenceSwitches->itemWidget(
ui->sceneSequenceSwitches->item(index));
SequenceWidget *s2 =
(SequenceWidget *)ui->sceneSequenceSwitches->itemWidget(
ui->sceneSequenceSwitches->item(index + 1));
SequenceWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->sceneSequenceSwitches[index],
switcher->sceneSequenceSwitches[index + 1]);
}
void AdvSceneSwitcher::on_sceneSequenceSave_clicked()
{
QString directory = QFileDialog::getSaveFileName(
this,
tr(obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.saveTitle")),
QDir::currentPath(),
tr(obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.fileType")));
if (directory.isEmpty()) {
return;
}
QFile file(directory);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return;
}
obs_data_t *obj = obs_data_create();
switcher->saveSceneSequenceSwitches(obj);
obs_data_save_json(obj, file.fileName().toUtf8().constData());
obs_data_release(obj);
}
void AdvSceneSwitcher::on_sceneSequenceLoad_clicked()
{
QString directory = QFileDialog::getOpenFileName(
this,
tr(obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.loadTitle")),
QDir::currentPath(),
tr(obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.fileType")));
if (directory.isEmpty()) {
return;
}
QFile file(directory);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return;
}
obs_data_t *obj = obs_data_create_from_json_file(
file.fileName().toUtf8().constData());
if (!obj) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.loadFail"));
return;
}
switcher->loadSceneSequenceSwitches(obj);
obs_data_release(obj);
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.loadSuccess"));
close();
}
void AdvSceneSwitcher::OpenSequenceExtendEdit(SequenceWidget *sw)
{
QDialog edit;
SequenceWidget editWidget(this, sw->getSwitchData(), false, true,
false);
QHBoxLayout layout;
layout.setSizeConstraint(QLayout::SetFixedSize);
layout.addWidget(&editWidget);
edit.setLayout(&layout);
edit.setWindowTitle(obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.extendEdit"));
edit.exec();
sw->UpdateWidgetStatus(true);
}
void AdvSceneSwitcher::on_sequenceEdit_clicked()
{
int index = ui->sceneSequenceSwitches->currentRow();
if (index == -1) {
return;
}
SequenceWidget *currentWidget =
(SequenceWidget *)ui->sceneSequenceSwitches->itemWidget(
ui->sceneSequenceSwitches->item(index));
OpenSequenceExtendEdit(currentWidget);
}
void AdvSceneSwitcher::on_sceneSequenceSwitches_itemDoubleClicked(
QListWidgetItem *item)
{
SequenceWidget *currentWidget =
(SequenceWidget *)ui->sceneSequenceSwitches->itemWidget(item);
OpenSequenceExtendEdit(currentWidget);
}
void SwitcherData::checkSceneSequence(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition, int &linger)
{
if (SceneSequenceSwitch::pause) {
return;
}
obs_source_t *currentSceneSource = obs_frontend_get_current_scene();
obs_weak_source_t *currentScene =
obs_source_get_weak_source(currentSceneSource);
for (SceneSequenceSwitch &s : sceneSequenceSwitches) {
// Continue the active uninterruptible sequence and skip others
if (uninterruptibleSceneSequenceActive &&
s.activeSequence == nullptr) {
continue;
}
bool matched = s.checkMatch(currentScene, linger);
if (!match && matched) {
match = matched;
if (s.activeSequence) {
scene = s.activeSequence->getScene();
transition = s.activeSequence->transition;
} else {
scene = s.getScene();
transition = s.transition;
if (verbose) {
s.logMatch();
}
}
s.advanceActiveSequence();
if (verbose) {
s.logAdvanceSequence();
}
// Ignore other switching methods if sequence is not
// interruptible and has not reached its end
if (s.activeSequence) {
uninterruptibleSceneSequenceActive =
!s.interruptible;
}
}
}
if (!match) {
uninterruptibleSceneSequenceActive = false;
}
obs_source_release(currentSceneSource);
obs_weak_source_release(currentScene);
}
void SwitcherData::saveSceneSequenceSwitches(obs_data_t *obj)
{
obs_data_array_t *sceneSequenceArray = obs_data_array_create();
for (SceneSequenceSwitch &s : switcher->sceneSequenceSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
obs_data_array_push_back(sceneSequenceArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "sceneRoundTrip", sceneSequenceArray);
obs_data_array_release(sceneSequenceArray);
}
void SwitcherData::loadSceneSequenceSwitches(obs_data_t *obj)
{
switcher->sceneSequenceSwitches.clear();
obs_data_array_t *sceneSequenceArray =
obs_data_get_array(obj, "sceneRoundTrip");
size_t count = obs_data_array_count(sceneSequenceArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj =
obs_data_array_item(sceneSequenceArray, i);
switcher->sceneSequenceSwitches.emplace_back();
sceneSequenceSwitches.back().load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(sceneSequenceArray);
}
void AdvSceneSwitcher::setupSequenceTab()
{
for (auto &s : switcher->sceneSequenceSwitches) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->sceneSequenceSwitches);
ui->sceneSequenceSwitches->addItem(item);
SequenceWidget *sw = new SequenceWidget(this, &s);
item->setSizeHint(sw->minimumSizeHint());
ui->sceneSequenceSwitches->setItemWidget(item, sw);
}
if (switcher->sceneSequenceSwitches.size() == 0) {
addPulse = PulseWidget(ui->sceneSequenceAdd, QColor(Qt::green));
ui->sequenceHelp->setVisible(true);
} else {
ui->sequenceHelp->setVisible(false);
}
}
bool SceneSequenceSwitch::initialized()
{
return SceneSwitcherEntry::initialized() && startScene;
}
bool SceneSequenceSwitch::valid()
{
return !initialized() ||
(SceneSwitcherEntry::valid() && WeakSourceValid(startScene));
}
void SceneSequenceSwitch::save(obs_data_t *obj, bool saveExt)
{
SceneSwitcherEntry::save(obj);
obs_data_set_int(obj, "startTargetType",
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);
obs_data_set_bool(obj, "interruptible", interruptible);
if (saveExt) {
auto cur = extendedSequence.get();
obs_data_array_t *extendScenes = obs_data_array_create();
while (cur) {
obs_data_t *array_obj = obs_data_create();
cur->save(array_obj, false);
obs_data_array_push_back(extendScenes, array_obj);
obs_data_release(array_obj);
cur = cur->extendedSequence.get();
}
obs_data_set_array(obj, "extendScenes", extendScenes);
obs_data_array_release(extendScenes);
}
}
// 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;
interruptible = obs_data_get_bool(obj, "interruptible");
if (saveExt) {
auto cur = this;
obs_data_array_t *extendScenes =
obs_data_get_array(obj, "extendScenes");
size_t count = obs_data_array_count(extendScenes);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj =
obs_data_array_item(extendScenes, i);
cur->extendedSequence =
std::make_unique<SceneSequenceSwitch>();
cur->extendedSequence->load(array_obj, false);
cur = cur->extendedSequence.get();
obs_data_release(array_obj);
}
obs_data_array_release(extendScenes);
}
}
bool SceneSequenceSwitch::reduce()
{
// Reset activeSequence just in case it was one of the deleted entries
activeSequence = nullptr;
if (!extendedSequence) {
return true;
}
if (extendedSequence->reduce()) {
extendedSequence.reset(nullptr);
}
return false;
}
SceneSequenceSwitch *SceneSequenceSwitch::extend()
{
if (extendedSequence) {
return extendedSequence->extend();
}
extendedSequence = std::make_unique<SceneSequenceSwitch>();
extendedSequence->startScene = scene;
return extendedSequence.get();
}
bool SceneSequenceSwitch::checkMatch(OBSWeakSource currentScene, int &linger,
SceneSequenceSwitch *root)
{
if (!initialized()) {
if (root) {
root->activeSequence = nullptr;
}
return false;
}
bool match = false;
if (activeSequence) {
return activeSequence->checkMatch(currentScene, linger, this);
}
if (startScene == currentScene) {
if (interruptible) {
match = checkDurationMatchInterruptible();
} else {
match = true;
prepareUninterruptibleMatch(currentScene, linger);
}
} else {
matchCount = 0;
if (root) {
root->activeSequence = nullptr;
logSequenceCanceled();
}
}
return match;
}
bool SceneSequenceSwitch::checkDurationMatchInterruptible()
{
bool durationReached = matchCount * (switcher->interval / 1000.0) >=
delay;
matchCount++;
if (durationReached) {
matchCount = 0;
return true;
}
return false;
}
void SceneSequenceSwitch::prepareUninterruptibleMatch(
OBSWeakSource currentScene, int &linger)
{
int dur = delay * 1000;
if (dur > 0) {
switcher->waitScene = obs_weak_source_get_source(currentScene);
obs_source_release(switcher->waitScene);
linger = dur;
}
}
void SceneSequenceSwitch::advanceActiveSequence()
{
// Set start Scene
OBSWeakSource currentSceneGroupScene = nullptr;
if (targetType == SwitchTargetType::SceneGroup && group) {
currentSceneGroupScene = group->getCurrentScene();
}
if (activeSequence) {
activeSequence = activeSequence->extendedSequence.get();
} else {
activeSequence = extendedSequence.get();
}
if (activeSequence) {
if (activeSequence->startTargetType ==
SwitchTargetType::SceneGroup) {
activeSequence->startScene = currentSceneGroupScene;
}
if (activeSequence->targetType == SwitchTargetType::Scene &&
!activeSequence->scene) {
blog(LOG_WARNING,
"cannot advance sequence - null scene set");
activeSequence = nullptr;
return;
}
if (activeSequence->targetType ==
SwitchTargetType::SceneGroup &&
activeSequence->group &&
activeSequence->group->scenes.empty()) {
blog(LOG_WARNING,
"cannot advance sequence - no scenes specified in '%s'",
activeSequence->group->name.c_str());
activeSequence = nullptr;
return;
}
// Reinit old matchCount value in case it was previously set
activeSequence->matchCount = 0;
}
}
void SceneSequenceSwitch::logAdvanceSequence()
{
if (activeSequence) {
std::string targetName =
GetWeakSourceName(activeSequence->scene);
if (activeSequence->targetType ==
SwitchTargetType::SceneGroup &&
activeSequence->group) {
targetName = activeSequence->group->name;
}
blog(LOG_INFO, "continuing sequence with '%s' -> '%s'",
GetWeakSourceName(activeSequence->startScene).c_str(),
targetName.c_str());
}
}
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) {
return "";
}
QString ext = "";
ext = QString::number(s->delay / s->delayMultiplier) + " ";
ext += delayMultiplierToString(s->delayMultiplier);
QString sceneName = GetWeakSourceName(s->scene).c_str();
if (s->targetType == SwitchTargetType::SceneGroup && s->group) {
sceneName = QString::fromStdString(s->group->name);
}
if (sceneName.isEmpty()) {
sceneName = obs_module_text("AdvSceneSwitcher.selectScene");
}
ext += " -> [" + sceneName + "]";
if (ext.length() + curLen > max_extend_text_size) {
return "...";
}
if (s->extendedSequence.get()) {
return ext +=
" | " + makeExtendText(s->extendedSequence.get(),
curLen + ext.length());
} else {
return ext;
}
}
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)
: SwitchWidget(parent, s, !extendSequence, true)
{
this->setParent(parent);
delay = new QDoubleSpinBox();
delayUnits = new QComboBox();
startScenes = new QComboBox();
interruptible = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.interruptible"));
extendText = new QLabel();
extend = new QPushButton();
reduce = new QPushButton();
extend->setProperty("themeID",
QVariant(QStringLiteral("addIconSmall")));
reduce->setProperty("themeID",
QVariant(QStringLiteral("removeIconSmall")));
extend->setMaximumSize(22, 22);
reduce->setMaximumSize(22, 22);
// We need to extend the generic SwitchWidget::SceneChanged()
// with our own SequenceWidget::SceneChanged()
// so the old singal / slot needs to be disconnected
scenes->disconnect();
QWidget::connect(scenes, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(SceneChanged(const QString &)));
QWidget::connect(delay, SIGNAL(valueChanged(double)), this,
SLOT(DelayChanged(double)));
QWidget::connect(delayUnits, SIGNAL(currentIndexChanged(int)), this,
SLOT(DelayUnitsChanged(int)));
QWidget::connect(startScenes,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(StartSceneChanged(const QString &)));
QWidget::connect(interruptible, SIGNAL(stateChanged(int)), this,
SLOT(InterruptibleChanged(int)));
QWidget::connect(extend, SIGNAL(clicked()), this,
SLOT(ExtendClicked()));
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"));
// The extended sequence widgets never exist on their own and are always
// place inside a non-extend sequence widget
if (extendSequence) {
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", scenes},
{"{{delay}}", delay},
{"{{delayUnits}}", delayUnits},
{"{{transitions}}", transitions}};
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.extendEntry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
} else {
QHBoxLayout *startSequence = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{startScenes}}", startScenes},
{"{{scenes}}", scenes},
{"{{delay}}", delay},
{"{{delayUnits}}", delayUnits},
{"{{transitions}}", transitions},
{"{{interruptible}}", interruptible}};
placeWidgets(obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.entry"),
startSequence, widgetPlaceholders);
// The exetend widgets placed here
extendSequenceLayout = new QVBoxLayout;
if (s) {
if (!editExtendMode) {
extendText->setText(makeExtendText(
s->extendedSequence.get()));
} else {
auto cur = s->extendedSequence.get();
while (cur != nullptr) {
extendSequenceLayout->addWidget(
new SequenceWidget(parent, cur,
true, true));
cur = cur->extendedSequence.get();
}
}
}
QHBoxLayout *extendSequenceControlsLayout = new QHBoxLayout;
if (editExtendMode) {
extendSequenceControlsLayout->addWidget(extend);
extendSequenceControlsLayout->addWidget(reduce);
}
extendSequenceControlsLayout->addStretch();
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(startSequence);
mainLayout->addLayout(extendSequenceLayout);
mainLayout->addWidget(extendText);
mainLayout->addLayout(extendSequenceControlsLayout);
setLayout(mainLayout);
}
switchData = s;
UpdateWidgetStatus(showExtendText);
loading = false;
}
SceneSequenceSwitch *SequenceWidget::getSwitchData()
{
return switchData;
}
void SequenceWidget::setSwitchData(SceneSequenceSwitch *s)
{
switchData = s;
}
void SequenceWidget::swapSwitchData(SequenceWidget *s1, SequenceWidget *s2)
{
SwitchWidget::swapSwitchData(s1, s2);
SceneSequenceSwitch *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
void SequenceWidget::UpdateDelay()
{
if (switchData) {
delay->setValue(switchData->delay /
switchData->delayMultiplier);
}
}
void SequenceWidget::DelayChanged(double delay)
{
if (loading || !switchData) {
return;
}
switchData->delay = delay * switchData->delayMultiplier;
}
void SequenceWidget::DelayUnitsChanged(int idx)
{
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();
}
void SequenceWidget::setExtendedSequenceStartScene()
{
switchData->extendedSequence->startScene = switchData->scene;
switchData->extendedSequence->startTargetType = SwitchTargetType::Scene;
if (switchData->targetType == SwitchTargetType::SceneGroup) {
switchData->extendedSequence->startScene = nullptr;
switchData->extendedSequence->startTargetType =
SwitchTargetType::SceneGroup;
}
}
void SequenceWidget::SceneChanged(const QString &text)
{
if (loading || !switchData) {
return;
}
SwitchWidget::SceneChanged(text);
std::lock_guard<std::mutex> lock(switcher->m);
if (switchData->extendedSequence) {
setExtendedSequenceStartScene();
}
}
void SequenceWidget::StartSceneChanged(const QString &text)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->startScene = GetWeakSourceByQString(text);
}
void SequenceWidget::InterruptibleChanged(int state)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->interruptible = state;
auto cur = switchData->extendedSequence.get();
while (cur != nullptr) {
cur->interruptible = state;
cur = cur->extendedSequence.get();
}
}
void SequenceWidget::UpdateWidgetStatus(bool showExtendText)
{
if (showExtendText) {
extendText->setText(
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();
startScenes->setCurrentText(
GetWeakSourceName(switchData->startScene).c_str());
interruptible->setChecked(switchData->interruptible);
SwitchWidget::showSwitchData();
}
void SequenceWidget::ExtendClicked()
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
auto es = switchData->extend();
SequenceWidget *ew = new SequenceWidget(this->parentWidget(), es, true);
extendSequenceLayout->addWidget(ew);
}
void SequenceWidget::ReduceClicked()
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->reduce();
int count = extendSequenceLayout->count();
auto item = extendSequenceLayout->takeAt(count - 1);
if (item) {
item->widget()->setVisible(false);
delete item;
}
}

333
src/switch-time.cpp Normal file
View File

@@ -0,0 +1,333 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
bool TimeSwitch::pause = false;
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::on_timeAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->timeSwitches.emplace_back();
listAddClicked(ui->timeSwitches,
new TimeSwitchWidget(this,
&switcher->timeSwitches.back()),
ui->timeAdd, &addPulse);
ui->timeHelp->setVisible(false);
}
void AdvSceneSwitcher::on_timeRemove_clicked()
{
QListWidgetItem *item = ui->timeSwitches->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->timeSwitches->currentRow();
auto &switches = switcher->timeSwitches;
switches.erase(switches.begin() + idx);
}
delete item;
}
void AdvSceneSwitcher::on_timeUp_clicked()
{
int index = ui->timeSwitches->currentRow();
if (!listMoveUp(ui->timeSwitches)) {
return;
}
TimeSwitchWidget *s1 = (TimeSwitchWidget *)ui->timeSwitches->itemWidget(
ui->timeSwitches->item(index));
TimeSwitchWidget *s2 = (TimeSwitchWidget *)ui->timeSwitches->itemWidget(
ui->timeSwitches->item(index - 1));
TimeSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->timeSwitches[index],
switcher->timeSwitches[index - 1]);
}
void AdvSceneSwitcher::on_timeDown_clicked()
{
int index = ui->timeSwitches->currentRow();
if (!listMoveDown(ui->timeSwitches)) {
return;
}
TimeSwitchWidget *s1 = (TimeSwitchWidget *)ui->timeSwitches->itemWidget(
ui->timeSwitches->item(index));
TimeSwitchWidget *s2 = (TimeSwitchWidget *)ui->timeSwitches->itemWidget(
ui->timeSwitches->item(index + 1));
TimeSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->timeSwitches[index],
switcher->timeSwitches[index + 1]);
}
bool timesAreInInterval(QTime &time1, QTime &time2, int &interval)
{
if (time1.isNull() || time2.isNull()) {
return false;
}
bool ret = false;
QTime validSwitchTimeWindow = time1.addMSecs(interval);
ret = time1 <= time2 && time2 <= validSwitchTimeWindow;
// Check for overflow
if (!ret && validSwitchTimeWindow.msecsSinceStartOfDay() < interval) {
ret = time2 >= time1 || time2 <= validSwitchTimeWindow;
}
return ret;
}
bool checkLiveTime(TimeSwitch &s, QDateTime &start, int &interval)
{
if (start.isNull()) {
return false;
}
QDateTime now = QDateTime::currentDateTime();
QTime timePassed = QTime(0, 0).addMSecs(start.msecsTo(now));
return timesAreInInterval(s.time, timePassed, interval);
}
bool checkRegularTime(TimeSwitch &s, int &interval)
{
if (s.trigger != ANY_DAY &&
s.trigger != QDate::currentDate().dayOfWeek())
return false;
QTime now = QTime::currentTime();
return timesAreInInterval(s.time, now, interval);
}
void SwitcherData::checkTimeSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (TimeSwitch::pause) {
return;
}
for (TimeSwitch &s : timeSwitches) {
if (!s.initialized()) {
continue;
}
if (s.trigger == LIVE) {
match = checkLiveTime(s, liveTime, interval);
} else {
match = checkRegularTime(s, interval);
}
if (match) {
scene = s.getScene();
transition = s.transition;
match = true;
if (verbose) {
s.logMatch();
}
break;
}
}
}
void SwitcherData::saveTimeSwitches(obs_data_t *obj)
{
obs_data_array_t *timeArray = obs_data_array_create();
for (TimeSwitch &s : switcher->timeSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
obs_data_array_push_back(timeArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "timeSwitches", timeArray);
obs_data_array_release(timeArray);
}
void SwitcherData::loadTimeSwitches(obs_data_t *obj)
{
switcher->timeSwitches.clear();
obs_data_array_t *timeArray = obs_data_get_array(obj, "timeSwitches");
size_t count = obs_data_array_count(timeArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(timeArray, i);
switcher->timeSwitches.emplace_back();
timeSwitches.back().load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(timeArray);
}
void AdvSceneSwitcher::setupTimeTab()
{
for (auto &s : switcher->timeSwitches) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->timeSwitches);
ui->timeSwitches->addItem(item);
TimeSwitchWidget *sw = new TimeSwitchWidget(this, &s);
item->setSizeHint(sw->minimumSizeHint());
ui->timeSwitches->setItemWidget(item, sw);
}
if (switcher->timeSwitches.size() == 0) {
addPulse = PulseWidget(ui->timeAdd, QColor(Qt::green));
ui->timeHelp->setVisible(true);
} else {
ui->timeHelp->setVisible(false);
}
}
void TimeSwitch::save(obs_data_t *obj)
{
SceneSwitcherEntry::save(obj);
obs_data_set_int(obj, "trigger", trigger);
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");
time = QTime::fromString(obs_data_get_string(obj, "time"));
}
static inline void populateTriggers(QComboBox *list)
{
list->addItem(obs_module_text("AdvSceneSwitcher.timeTab.anyDay"));
list->addItem(obs_module_text("AdvSceneSwitcher.timeTab.mondays"));
list->addItem(obs_module_text("AdvSceneSwitcher.timeTab.tuesdays"));
list->addItem(obs_module_text("AdvSceneSwitcher.timeTab.wednesdays"));
list->addItem(obs_module_text("AdvSceneSwitcher.timeTab.thursdays"));
list->addItem(obs_module_text("AdvSceneSwitcher.timeTab.fridays"));
list->addItem(obs_module_text("AdvSceneSwitcher.timeTab.saturdays"));
list->addItem(obs_module_text("AdvSceneSwitcher.timeTab.sundays"));
list->addItem(obs_module_text("AdvSceneSwitcher.timeTab.afterstart"));
list->setItemData(
8, obs_module_text("AdvSceneSwitcher.timeTab.afterstart.tip"),
Qt::ToolTipRole);
}
TimeSwitchWidget::TimeSwitchWidget(QWidget *parent, TimeSwitch *s)
: SwitchWidget(parent, s, true, true)
{
triggers = new QComboBox();
time = new QTimeEdit();
QWidget::connect(triggers, SIGNAL(currentIndexChanged(int)), this,
SLOT(TriggerChanged(int)));
QWidget::connect(time, SIGNAL(timeChanged(const QTime &)), this,
SLOT(TimeChanged(const QTime &)));
populateTriggers(triggers);
time->setDisplayFormat("HH:mm:ss");
if (s) {
triggers->setCurrentIndex(s->trigger);
time->setTime(s->time);
}
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{triggers}}", triggers},
{"{{time}}", time},
{"{{scenes}}", scenes},
{"{{transitions}}", transitions}};
placeWidgets(obs_module_text("AdvSceneSwitcher.timeTab.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
switchData = s;
loading = false;
}
TimeSwitch *TimeSwitchWidget::getSwitchData()
{
return switchData;
}
void TimeSwitchWidget::setSwitchData(TimeSwitch *s)
{
switchData = s;
}
void TimeSwitchWidget::swapSwitchData(TimeSwitchWidget *s1,
TimeSwitchWidget *s2)
{
SwitchWidget::swapSwitchData(s1, s2);
TimeSwitch *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
void TimeSwitchWidget::TriggerChanged(int index)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->trigger = (timeTrigger)index;
}
void TimeSwitchWidget::TimeChanged(const QTime &time)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->time = time;
}

557
src/switch-transitions.cpp Normal file
View File

@@ -0,0 +1,557 @@
#include <thread>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
bool DefaultSceneTransition::pause = false;
void AdvSceneSwitcher::on_transitionsAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->sceneTransitions.emplace_back();
listAddClicked(ui->sceneTransitions,
new TransitionSwitchWidget(
this, &switcher->sceneTransitions.back()));
ui->transitionHelp->setVisible(false);
}
void AdvSceneSwitcher::on_transitionsRemove_clicked()
{
QListWidgetItem *item = ui->sceneTransitions->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->sceneTransitions->currentRow();
auto &switches = switcher->sceneTransitions;
switches.erase(switches.begin() + idx);
}
delete item;
}
void AdvSceneSwitcher::on_transitionsUp_clicked()
{
int index = ui->sceneTransitions->currentRow();
if (!listMoveUp(ui->sceneTransitions)) {
return;
}
TransitionSwitchWidget *s1 =
(TransitionSwitchWidget *)ui->sceneTransitions->itemWidget(
ui->sceneTransitions->item(index));
TransitionSwitchWidget *s2 =
(TransitionSwitchWidget *)ui->sceneTransitions->itemWidget(
ui->sceneTransitions->item(index - 1));
TransitionSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->sceneTransitions[index],
switcher->sceneTransitions[index - 1]);
}
void AdvSceneSwitcher::on_transitionsDown_clicked()
{
int index = ui->sceneTransitions->currentRow();
if (!listMoveDown(ui->sceneTransitions)) {
return;
}
TransitionSwitchWidget *s1 =
(TransitionSwitchWidget *)ui->sceneTransitions->itemWidget(
ui->sceneTransitions->item(index));
TransitionSwitchWidget *s2 =
(TransitionSwitchWidget *)ui->sceneTransitions->itemWidget(
ui->sceneTransitions->item(index + 1));
TransitionSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->sceneTransitions[index],
switcher->sceneTransitions[index + 1]);
}
void AdvSceneSwitcher::on_defaultTransitionsAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->defaultSceneTransitions.emplace_back();
listAddClicked(ui->defaultTransitions,
new DefTransitionSwitchWidget(
this,
&switcher->defaultSceneTransitions.back()));
ui->defaultTransitionHelp->setVisible(false);
}
void AdvSceneSwitcher::on_defaultTransitionsRemove_clicked()
{
QListWidgetItem *item = ui->defaultTransitions->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->defaultTransitions->currentRow();
auto &switches = switcher->defaultSceneTransitions;
switches.erase(switches.begin() + idx);
}
delete item;
}
void AdvSceneSwitcher::on_defaultTransitionsUp_clicked()
{
int index = ui->defaultTransitions->currentRow();
if (!listMoveUp(ui->defaultTransitions)) {
return;
}
TransitionSwitchWidget *s1 =
(TransitionSwitchWidget *)ui->defaultTransitions->itemWidget(
ui->defaultTransitions->item(index));
TransitionSwitchWidget *s2 =
(TransitionSwitchWidget *)ui->defaultTransitions->itemWidget(
ui->defaultTransitions->item(index - 1));
TransitionSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->defaultSceneTransitions[index],
switcher->defaultSceneTransitions[index - 1]);
}
void AdvSceneSwitcher::on_defaultTransitionsDown_clicked()
{
int index = ui->defaultTransitions->currentRow();
if (!listMoveDown(ui->defaultTransitions)) {
return;
}
DefTransitionSwitchWidget *s1 =
(DefTransitionSwitchWidget *)ui->defaultTransitions->itemWidget(
ui->defaultTransitions->item(index));
DefTransitionSwitchWidget *s2 =
(DefTransitionSwitchWidget *)ui->defaultTransitions->itemWidget(
ui->defaultTransitions->item(index + 1));
DefTransitionSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->defaultSceneTransitions[index],
switcher->defaultSceneTransitions[index + 1]);
}
void SwitcherData::checkDefaultSceneTransitions()
{
if (DefaultSceneTransition::pause || stop) {
return;
}
obs_source_t *currentSceneSource = obs_frontend_get_current_scene();
obs_weak_source_t *currentScene =
obs_source_get_weak_source(currentSceneSource);
for (auto &t : defaultSceneTransitions) {
if (t.checkMatch(currentScene)) {
if (verbose) {
t.logMatch();
}
t.setTransition();
break;
}
}
obs_weak_source_release(currentScene);
obs_source_release(currentSceneSource);
}
void AdvSceneSwitcher::on_transitionOverridecheckBox_stateChanged(int state)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
if (!state) {
switcher->tansitionOverrideOverride = false;
} else {
switcher->tansitionOverrideOverride = true;
}
}
obs_weak_source_t *getNextTransition(obs_weak_source_t *scene1,
obs_weak_source_t *scene2)
{
obs_weak_source_t *ws = nullptr;
if (scene1 && scene2) {
for (SceneTransition &t : switcher->sceneTransitions) {
if (!t.initialized()) {
continue;
}
if (t.scene == scene1 && t.scene2 == scene2) {
ws = t.transition;
break;
}
}
obs_weak_source_addref(ws);
}
return ws;
}
void overwriteTransitionOverride(obs_weak_source_t *sceneWs,
obs_source_t *transition, transitionData &td)
{
obs_source_t *scene = obs_weak_source_get_source(sceneWs);
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");
const char *name = obs_source_get_name(transition);
int duration = obs_frontend_get_transition_duration();
obs_data_set_string(data, "transition", name);
obs_data_set_int(data, "transition_duration", duration);
obs_data_release(data);
obs_source_release(scene);
}
void restoreTransitionOverride(obs_source_t *scene, transitionData td)
{
obs_data_t *data = obs_source_get_private_settings(scene);
obs_data_set_string(data, "transition", td.name.c_str());
obs_data_set_int(data, "transition_duration", td.duration);
obs_data_release(data);
}
void setNextTransition(OBSWeakSource &targetScene, obs_source_t *currentSource,
OBSWeakSource &transition,
bool &transitionOverrideOverride, transitionData &td)
{
obs_weak_source_t *currentScene =
obs_source_get_weak_source(currentSource);
obs_weak_source_t *nextTransitionWs =
getNextTransition(currentScene, targetScene);
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);
}
if (nextTransition) {
obs_frontend_set_current_transition(nextTransition);
}
if (transitionOverrideOverride) {
overwriteTransitionOverride(targetScene, nextTransition, td);
}
obs_weak_source_release(nextTransitionWs);
obs_source_release(nextTransition);
}
void SwitcherData::saveSceneTransitions(obs_data_t *obj)
{
obs_data_array_t *sceneTransitionsArray = obs_data_array_create();
for (SceneTransition &s : switcher->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);
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) {
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);
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);
}
void SwitcherData::loadSceneTransitions(obs_data_t *obj)
{
switcher->sceneTransitions.clear();
obs_data_array_t *sceneTransitionsArray =
obs_data_get_array(obj, "sceneTransitions");
size_t count = obs_data_array_count(sceneTransitionsArray);
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));
obs_data_release(array_obj);
}
obs_data_array_release(sceneTransitionsArray);
switcher->defaultSceneTransitions.clear();
obs_data_array_t *defaultTransitionsArray =
obs_data_get_array(obj, "defaultTransitions");
count = obs_data_array_count(defaultTransitionsArray);
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));
obs_data_release(array_obj);
}
obs_data_array_release(defaultTransitionsArray);
switcher->tansitionOverrideOverride =
obs_data_get_bool(obj, "tansitionOverrideOverride");
}
void AdvSceneSwitcher::setupTransitionsTab()
{
for (auto &s : switcher->sceneTransitions) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->sceneTransitions);
ui->sceneTransitions->addItem(item);
TransitionSwitchWidget *sw =
new TransitionSwitchWidget(this, &s);
item->setSizeHint(sw->minimumSizeHint());
ui->sceneTransitions->setItemWidget(item, sw);
}
if (switcher->sceneTransitions.size() == 0) {
ui->transitionHelp->setVisible(true);
} else {
ui->transitionHelp->setVisible(false);
}
for (auto &s : switcher->defaultSceneTransitions) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->defaultTransitions);
ui->defaultTransitions->addItem(item);
DefTransitionSwitchWidget *sw =
new DefTransitionSwitchWidget(this, &s);
item->setSizeHint(sw->minimumSizeHint());
ui->defaultTransitions->setItemWidget(item, sw);
}
if (switcher->defaultSceneTransitions.size() == 0) {
ui->defaultTransitionHelp->setVisible(true);
} else {
ui->defaultTransitionHelp->setVisible(false);
}
ui->transitionOverridecheckBox->setChecked(
switcher->tansitionOverrideOverride);
}
bool SceneTransition::initialized()
{
return SceneSwitcherEntry::initialized() && scene2;
}
bool SceneTransition::valid()
{
return !initialized() ||
(SceneSwitcherEntry::valid() && WeakSourceValid(scene2));
}
TransitionSwitchWidget::TransitionSwitchWidget(QWidget *parent,
SceneTransition *s)
: SwitchWidget(parent, s, false, false, false)
{
scenes2 = new QComboBox();
QWidget::connect(scenes2, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(Scene2Changed(const QString &)));
AdvSceneSwitcher::populateSceneSelection(scenes2, false);
if (s) {
scenes2->setCurrentText(GetWeakSourceName(s->scene2).c_str());
}
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", scenes},
{"{{scenes2}}", scenes2},
{"{{transitions}}", transitions}};
placeWidgets(obs_module_text("AdvSceneSwitcher.transitionTab.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
switchData = s;
loading = false;
}
SceneTransition *TransitionSwitchWidget::getSwitchData()
{
return switchData;
}
void TransitionSwitchWidget::setSwitchData(SceneTransition *s)
{
switchData = s;
}
void TransitionSwitchWidget::swapSwitchData(TransitionSwitchWidget *s1,
TransitionSwitchWidget *s2)
{
SwitchWidget::swapSwitchData(s1, s2);
SceneTransition *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
void TransitionSwitchWidget::Scene2Changed(const QString &text)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->scene2 = GetWeakSourceByQString(text);
}
DefTransitionSwitchWidget::DefTransitionSwitchWidget(QWidget *parent,
DefaultSceneTransition *s)
: SwitchWidget(parent, s, false, false, false)
{
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", scenes}, {"{{transitions}}", transitions}};
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.transitionTab.defaultTransitionEntry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
switchData = s;
loading = false;
}
DefaultSceneTransition *DefTransitionSwitchWidget::getSwitchData()
{
return switchData;
}
void DefTransitionSwitchWidget::setSwitchData(DefaultSceneTransition *s)
{
switchData = s;
}
void DefTransitionSwitchWidget::swapSwitchData(DefTransitionSwitchWidget *s1,
DefTransitionSwitchWidget *s2)
{
SwitchWidget::swapSwitchData(s1, s2);
DefaultSceneTransition *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
bool DefaultSceneTransition::checkMatch(OBSWeakSource currentScene)
{
return scene == currentScene;
}
void setTransitionDelayed(OBSWeakSource transition)
{
// A hardcoded delay of 50 ms before switching transition type is
// necessary due to OBS_FRONTEND_EVENT_SCENE_CHANGED seemingly firing a
// bit too early and thus leading to canceled transitions.
//
// 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.
// 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));
obs_source_t *transitionSource = obs_weak_source_get_source(transition);
obs_frontend_set_current_transition(transitionSource);
obs_source_release(transitionSource);
}
void DefaultSceneTransition::setTransition()
{
std::thread t;
t = std::thread(setTransitionDelayed, transition);
t.detach();
}

698
src/switch-video.cpp Normal file
View File

@@ -0,0 +1,698 @@
#include <QFileDialog>
#include <QBuffer>
#include <QToolTip>
#include <thread>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
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);
switcher->videoSwitches.emplace_back();
VideoSwitchWidget *sw =
new VideoSwitchWidget(this, &switcher->videoSwitches.back());
listAddClicked(ui->videoSwitches, sw, ui->videoAdd, &addPulse);
ui->videoHelp->setVisible(false);
}
void AdvSceneSwitcher::on_videoRemove_clicked()
{
QListWidgetItem *item = ui->videoSwitches->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->videoSwitches->currentRow();
auto &switches = switcher->videoSwitches;
switches.erase(switches.begin() + idx);
}
delete item;
}
void AdvSceneSwitcher::on_videoUp_clicked()
{
int index = ui->videoSwitches->currentRow();
if (!listMoveUp(ui->videoSwitches)) {
return;
}
VideoSwitchWidget *s1 =
(VideoSwitchWidget *)ui->videoSwitches->itemWidget(
ui->videoSwitches->item(index));
VideoSwitchWidget *s2 =
(VideoSwitchWidget *)ui->videoSwitches->itemWidget(
ui->videoSwitches->item(index - 1));
VideoSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->videoSwitches[index],
switcher->videoSwitches[index - 1]);
}
void AdvSceneSwitcher::on_videoDown_clicked()
{
int index = ui->videoSwitches->currentRow();
if (!listMoveDown(ui->videoSwitches)) {
return;
}
VideoSwitchWidget *s1 =
(VideoSwitchWidget *)ui->videoSwitches->itemWidget(
ui->videoSwitches->item(index));
VideoSwitchWidget *s2 =
(VideoSwitchWidget *)ui->videoSwitches->itemWidget(
ui->videoSwitches->item(index + 1));
VideoSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->videoSwitches[index],
switcher->videoSwitches[index + 1]);
}
void AdvSceneSwitcher::on_getScreenshot_clicked()
{
QListWidgetItem *item = ui->videoSwitches->currentItem();
if (!item) {
return;
}
VideoSwitchWidget *sw =
(VideoSwitchWidget *)ui->videoSwitches->itemWidget(item);
auto s = sw->getSwitchData();
if (!s || !s->videoSource) {
return;
}
auto source = obs_weak_source_get_source(s->videoSource);
auto screenshotData = std::make_unique<AdvSSScreenshotObj>(source);
obs_source_release(source);
QString filePath = QFileDialog::getSaveFileName(this);
if (filePath.isEmpty()) {
return;
}
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return;
}
// During selection of the save path enough time should usually have
// passed already
// Add this just in case ...
if (!screenshotData->done) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (!screenshotData->done) {
DisplayMessage("Failed to get screenshot of source!");
return;
}
screenshotData->image.save(file.fileName());
sw->SetFilePath(file.fileName());
}
void SwitcherData::checkVideoSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (VideoSwitch::pause) {
return;
}
for (auto &s : videoSwitches) {
bool matched = s.checkMatch();
if (!match && matched) {
match = true;
scene = s.getScene();
transition = s.transition;
if (verbose) {
s.logMatch();
}
}
}
}
void SwitcherData::saveVideoSwitches(obs_data_t *obj)
{
obs_data_array_t *videoArray = obs_data_array_create();
for (VideoSwitch &s : videoSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
obs_data_array_push_back(videoArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "videoSwitches", videoArray);
obs_data_array_release(videoArray);
}
void SwitcherData::loadVideoSwitches(obs_data_t *obj)
{
switcher->videoSwitches.clear();
obs_data_array_t *videoArray = obs_data_get_array(obj, "videoSwitches");
size_t count = obs_data_array_count(videoArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(videoArray, i);
switcher->videoSwitches.emplace_back();
videoSwitches.back().load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(videoArray);
}
void AdvSceneSwitcher::setupVideoTab()
{
for (auto &s : switcher->videoSwitches) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->videoSwitches);
ui->videoSwitches->addItem(item);
VideoSwitchWidget *sw = new VideoSwitchWidget(this, &s);
item->setSizeHint(sw->minimumSizeHint());
ui->videoSwitches->setItemWidget(item, sw);
}
if (switcher->videoSwitches.size() == 0) {
addPulse = PulseWidget(ui->videoAdd, QColor(Qt::green));
ui->videoHelp->setVisible(true);
} else {
ui->videoHelp->setVisible(false);
}
ui->getScreenshot->setToolTip(
obs_module_text("AdvSceneSwitcher.videoTab.getScreenshotHelp"));
}
bool VideoSwitch::initialized()
{
return SceneSwitcherEntry::initialized() && videoSource;
}
bool VideoSwitch::valid()
{
return !initialized() ||
(SceneSwitcherEntry::valid() && WeakSourceValid(videoSource));
}
void VideoSwitch::save(obs_data_t *obj)
{
SceneSwitcherEntry::save(obj);
obs_data_set_string(obj, "videoSource",
GetWeakSourceName(videoSource).c_str());
obs_data_set_int(obj, "condition", static_cast<int>(condition));
obs_data_set_double(obj, "duration", duration);
obs_data_set_string(obj, "filePath", file.c_str());
obs_data_set_bool(obj, "ignoreInactiveSource", ignoreInactiveSource);
}
void VideoSwitch::load(obs_data_t *obj)
{
SceneSwitcherEntry::load(obj);
const char *videoSourceName = obs_data_get_string(obj, "videoSource");
videoSource = GetWeakSourceByName(videoSourceName);
condition = static_cast<videoSwitchType>(
obs_data_get_int(obj, "condition"));
duration = obs_data_get_double(obj, "duration");
file = obs_data_get_string(obj, "filePath");
ignoreInactiveSource = obs_data_get_bool(obj, "ignoreInactiveSource");
if (condition != videoSwitchType::HAS_NOT_CHANGED) {
(void)loadImageFromFile();
}
}
void VideoSwitch::getScreenshot()
{
auto source = obs_weak_source_get_source(videoSource);
screenshotData = std::make_unique<AdvSSScreenshotObj>(source);
obs_source_release(source);
}
bool VideoSwitch::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 VideoSwitch::checkMatch()
{
if (ignoreInactiveSource) {
obs_source_t *vs = obs_weak_source_get_source(videoSource);
bool videoActive = obs_source_active(vs);
obs_source_release(vs);
if (!videoActive) {
screenshotData.reset(nullptr);
return false;
}
}
bool match = false;
if (screenshotData) {
if (screenshotData->done) {
bool conditionMatch = false;
switch (condition) {
case videoSwitchType::MATCH:
conditionMatch = screenshotData->image ==
matchImage;
break;
case videoSwitchType::DIFFER:
conditionMatch = screenshotData->image !=
matchImage;
break;
case videoSwitchType::HAS_NOT_CHANGED:
conditionMatch = screenshotData->image ==
matchImage;
break;
default:
break;
}
if (conditionMatch) {
currentMatchDuration +=
std::chrono::duration_cast<
std::chrono::milliseconds>(
screenshotData->time -
previousTime);
} else {
currentMatchDuration = {};
}
bool durationMatch = currentMatchDuration.count() >=
duration * 1000;
if (conditionMatch && durationMatch) {
match = true;
}
if (condition == videoSwitchType::HAS_NOT_CHANGED) {
matchImage = std::move(screenshotData->image);
}
previousTime = std::move(screenshotData->time);
screenshotData.reset(nullptr);
}
}
getScreenshot();
return match;
}
void swap(VideoSwitch &first, VideoSwitch &second)
{
std::swap(first.targetType, second.targetType);
std::swap(first.group, second.group);
std::swap(first.scene, second.scene);
std::swap(first.transition, second.transition);
std::swap(first.usePreviousScene, second.usePreviousScene);
std::swap(first.videoSource, second.videoSource);
}
static inline void populateConditionSelection(QComboBox *list)
{
list->addItem(
obs_module_text("AdvSceneSwitcher.videoTab.condition.match"));
list->setItemData(
0,
obs_module_text(
"AdvSceneSwitcher.videoTab.condition.match.tooltip"),
Qt::ToolTipRole);
list->addItem(
obs_module_text("AdvSceneSwitcher.videoTab.condition.differ"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.videoTab.condition.hasNotChanged"));
}
VideoSwitchWidget::VideoSwitchWidget(QWidget *parent, VideoSwitch *s)
: SwitchWidget(parent, s, true, true)
{
videoSources = new QComboBox();
condition = new QComboBox();
duration = new QDoubleSpinBox();
filePath = new QLineEdit();
browseButton =
new QPushButton(obs_module_text("AdvSceneSwitcher.browse"));
ignoreInactiveSource = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.videoTab.ignoreInactiveSource"));
filePath->setFixedWidth(100);
browseButton->setStyleSheet("border:1px solid gray;");
duration->setMinimum(0.0);
duration->setMaximum(99.000000);
duration->setSuffix("s");
QWidget::connect(videoSources,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(SourceChanged(const QString &)));
QWidget::connect(condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(duration, SIGNAL(valueChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(filePath, SIGNAL(editingFinished()), this,
SLOT(FilePathChanged()));
QWidget::connect(browseButton, SIGNAL(clicked()), this,
SLOT(BrowseButtonClicked()));
QWidget::connect(ignoreInactiveSource, SIGNAL(stateChanged(int)), this,
SLOT(IgnoreInactiveChanged(int)));
// TODO:
// Figure out why scene do not work for "match exactly".
// Until then do not allow selecting scenes
AdvSceneSwitcher::populateVideoSelection(videoSources, false);
populateConditionSelection(condition);
if (s) {
videoSources->setCurrentText(
GetWeakSourceName(s->videoSource).c_str());
condition->setCurrentIndex(static_cast<int>(s->condition));
duration->setValue(s->duration);
filePath->setText(QString::fromStdString(s->file));
ignoreInactiveSource->setChecked(s->ignoreInactiveSource);
if (s->condition == videoSwitchType::HAS_NOT_CHANGED) {
filePath->hide();
browseButton->hide();
}
}
QHBoxLayout *switchLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{videoSources}}", videoSources},
{"{{condition}}", condition},
{"{{duration}}", duration},
{"{{filePath}}", filePath},
{"{{browseButton}}", browseButton},
{"{{ignoreInactiveSource}}", ignoreInactiveSource},
{"{{scenes}}", scenes},
{"{{transitions}}", transitions}};
placeWidgets(obs_module_text("AdvSceneSwitcher.videoTab.entry"),
switchLayout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(switchLayout);
setLayout(mainLayout);
switchData = s;
UpdatePreviewTooltip();
loading = false;
}
VideoSwitch *VideoSwitchWidget::getSwitchData()
{
return switchData;
}
void VideoSwitchWidget::setSwitchData(VideoSwitch *s)
{
switchData = s;
}
void VideoSwitchWidget::swapSwitchData(VideoSwitchWidget *s1,
VideoSwitchWidget *s2)
{
SwitchWidget::swapSwitchData(s1, s2);
VideoSwitch *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
void VideoSwitchWidget::UpdatePreviewTooltip()
{
if (!switchData ||
switchData->condition == videoSwitchType::HAS_NOT_CHANGED) {
return;
}
QImage preview =
switchData->matchImage.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 VideoSwitchWidget::SetFilePath(const QString &text)
{
filePath->setText(text);
FilePathChanged();
}
void VideoSwitchWidget::SourceChanged(const QString &text)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->videoSource = GetWeakSourceByQString(text);
}
void VideoSwitchWidget::ConditionChanged(int cond)
{
if (loading || !switchData) {
return;
}
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 {
filePath->show();
browseButton->show();
}
// 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 (switchData->loadImageFromFile()) {
UpdatePreviewTooltip();
}
}
void VideoSwitchWidget::DurationChanged(double dur)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->duration = dur;
}
void VideoSwitchWidget::FilePathChanged()
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->file = filePath->text().toUtf8().constData();
if (switchData->loadImageFromFile()) {
UpdatePreviewTooltip();
}
}
void VideoSwitchWidget::BrowseButtonClicked()
{
if (loading || !switchData) {
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 VideoSwitchWidget::IgnoreInactiveChanged(int state)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->ignoreInactiveSource = state;
}

550
src/switch-window.cpp Normal file
View File

@@ -0,0 +1,550 @@
#include <regex>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
bool WindowSwitch::pause = false;
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::on_windowAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->windowSwitches.emplace_back();
listAddClicked(ui->windowSwitches,
new WindowSwitchWidget(this,
&switcher->windowSwitches.back()),
ui->windowAdd, &addPulse);
ui->windowHelp->setVisible(false);
}
void AdvSceneSwitcher::on_windowRemove_clicked()
{
QListWidgetItem *item = ui->windowSwitches->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->windowSwitches->currentRow();
auto &switches = switcher->windowSwitches;
switches.erase(switches.begin() + idx);
}
delete item;
}
void AdvSceneSwitcher::on_windowUp_clicked()
{
int index = ui->windowSwitches->currentRow();
if (!listMoveUp(ui->windowSwitches)) {
return;
}
WindowSwitchWidget *s1 =
(WindowSwitchWidget *)ui->windowSwitches->itemWidget(
ui->windowSwitches->item(index));
WindowSwitchWidget *s2 =
(WindowSwitchWidget *)ui->windowSwitches->itemWidget(
ui->windowSwitches->item(index - 1));
WindowSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->windowSwitches[index],
switcher->windowSwitches[index - 1]);
}
void AdvSceneSwitcher::on_windowDown_clicked()
{
int index = ui->windowSwitches->currentRow();
if (!listMoveDown(ui->windowSwitches)) {
return;
}
WindowSwitchWidget *s1 =
(WindowSwitchWidget *)ui->windowSwitches->itemWidget(
ui->windowSwitches->item(index));
WindowSwitchWidget *s2 =
(WindowSwitchWidget *)ui->windowSwitches->itemWidget(
ui->windowSwitches->item(index + 1));
WindowSwitchWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->windowSwitches[index],
switcher->windowSwitches[index + 1]);
}
void AdvSceneSwitcher::on_ignoreWindowsAdd_clicked()
{
QString windowName = ui->ignoreWindowsWindows->currentText();
if (windowName.isEmpty()) {
return;
}
QVariant v = QVariant::fromValue(windowName);
QList<QListWidgetItem *> items =
ui->ignoreWindows->findItems(windowName, Qt::MatchExactly);
if (items.size() == 0) {
QListWidgetItem *item =
new QListWidgetItem(windowName, ui->ignoreWindows);
item->setData(Qt::UserRole, v);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->ignoreWindowsSwitches.emplace_back(
windowName.toUtf8().constData());
}
ui->ignoreWindowHelp->setVisible(false);
}
void AdvSceneSwitcher::on_ignoreWindowsRemove_clicked()
{
QListWidgetItem *item = ui->ignoreWindows->currentItem();
if (!item) {
return;
}
QString windowName = item->data(Qt::UserRole).toString();
{
std::lock_guard<std::mutex> lock(switcher->m);
auto &switches = switcher->ignoreWindowsSwitches;
for (auto it = switches.begin(); it != switches.end(); ++it) {
auto &s = *it;
if (s == windowName.toUtf8().constData()) {
switches.erase(it);
break;
}
}
}
delete item;
}
int AdvSceneSwitcher::IgnoreWindowsFindByData(const QString &window)
{
int count = ui->ignoreWindows->count();
int idx = -1;
for (int i = 0; i < count; i++) {
QListWidgetItem *item = ui->ignoreWindows->item(i);
QString itemRegion = item->data(Qt::UserRole).toString();
if (itemRegion == window) {
idx = i;
break;
}
}
return idx;
}
void AdvSceneSwitcher::on_ignoreWindows_currentRowChanged(int idx)
{
if (loading) {
return;
}
if (idx == -1) {
return;
}
QListWidgetItem *item = ui->ignoreWindows->item(idx);
QString window = item->data(Qt::UserRole).toString();
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->ignoreWindowsSwitches) {
if (window.compare(s.c_str()) == 0) {
ui->ignoreWindowsWindows->setCurrentText(s.c_str());
break;
}
}
}
void checkWindowTitleSwitchDirect(WindowSwitch &s,
std::string &currentWindowTitle, bool &match,
OBSWeakSource &scene,
OBSWeakSource &transition)
{
bool focus = s.window == currentWindowTitle;
bool fullscreen = (!s.fullscreen || isFullscreen(s.window));
bool max = (!s.maximized || isMaximized(s.window));
if (focus && fullscreen && max) {
match = true;
scene = s.getScene();
transition = s.transition;
}
}
void checkWindowTitleSwitchRegex(WindowSwitch &s,
std::string &currentWindowTitle,
std::vector<std::string> windowList,
bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
for (auto &window : windowList) {
try {
std::regex expr(s.window);
if (!std::regex_match(window, expr)) {
continue;
}
} catch (const std::regex_error &) {
}
bool focus = window == currentWindowTitle;
bool fullscreen = (!s.fullscreen || isFullscreen(window));
bool max = (!s.maximized || isMaximized(window));
if (focus && fullscreen && max) {
match = true;
scene = s.getScene();
transition = s.transition;
}
}
}
void SwitcherData::checkWindowTitleSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (WindowSwitch::pause) {
return;
}
std::string currentWindowTitle;
GetCurrentWindowTitle(currentWindowTitle);
// Check if current window is ignored
for (auto &window : ignoreWindowsSwitches) {
bool equals = (currentWindowTitle == window);
try {
std::regex expr(window);
bool matches =
std::regex_match(currentWindowTitle, expr);
if (equals || matches) {
currentWindowTitle = lastTitle;
break;
}
} catch (const std::regex_error &) {
}
}
lastTitle = currentWindowTitle;
std::vector<std::string> windowList;
GetWindowList(windowList);
for (WindowSwitch &s : windowSwitches) {
if (!s.initialized()) {
continue;
}
if (std::find(windowList.begin(), windowList.end(), s.window) !=
windowList.end()) {
checkWindowTitleSwitchDirect(s, currentWindowTitle,
match, scene, transition);
} else {
checkWindowTitleSwitchRegex(s, currentWindowTitle,
windowList, match, scene,
transition);
}
if (match) {
if (verbose) {
s.logMatch();
}
break;
}
}
}
void SwitcherData::saveWindowTitleSwitches(obs_data_t *obj)
{
obs_data_array_t *windowTitleArray = obs_data_array_create();
for (WindowSwitch &s : switcher->windowSwitches) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
obs_data_array_push_back(windowTitleArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "switches", windowTitleArray);
obs_data_array_release(windowTitleArray);
obs_data_array_t *ignoreWindowsArray = obs_data_array_create();
for (std::string &window : switcher->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);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "ignoreWindows", ignoreWindowsArray);
obs_data_array_release(ignoreWindowsArray);
}
void SwitcherData::loadWindowTitleSwitches(obs_data_t *obj)
{
switcher->windowSwitches.clear();
obs_data_array_t *windowTitleArray =
obs_data_get_array(obj, "switches");
size_t count = obs_data_array_count(windowTitleArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj =
obs_data_array_item(windowTitleArray, i);
switcher->windowSwitches.emplace_back();
windowSwitches.back().load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(windowTitleArray);
switcher->ignoreWindowsSwitches.clear();
obs_data_array_t *ignoreWindowsArray =
obs_data_get_array(obj, "ignoreWindows");
count = obs_data_array_count(ignoreWindowsArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj =
obs_data_array_item(ignoreWindowsArray, i);
const char *window =
obs_data_get_string(array_obj, "ignoreWindow");
switcher->ignoreWindowsSwitches.emplace_back(window);
obs_data_release(array_obj);
}
obs_data_array_release(ignoreWindowsArray);
}
void AdvSceneSwitcher::setupTitleTab()
{
for (auto &s : switcher->windowSwitches) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->windowSwitches);
ui->windowSwitches->addItem(item);
WindowSwitchWidget *sw = new WindowSwitchWidget(this, &s);
item->setSizeHint(sw->minimumSizeHint());
ui->windowSwitches->setItemWidget(item, sw);
}
if (switcher->windowSwitches.size() == 0) {
addPulse = PulseWidget(ui->windowAdd, QColor(Qt::green));
ui->windowHelp->setVisible(true);
} else {
ui->windowHelp->setVisible(false);
}
populateWindowSelection(ui->ignoreWindowsWindows);
for (auto &window : switcher->ignoreWindowsSwitches) {
QString text = QString::fromStdString(window);
QListWidgetItem *item =
new QListWidgetItem(text, ui->ignoreWindows);
item->setData(Qt::UserRole, text);
}
if (switcher->ignoreWindowsSwitches.size() == 0) {
ui->ignoreWindowHelp->setVisible(true);
} else {
ui->ignoreWindowHelp->setVisible(false);
}
}
void WindowSwitch::save(obs_data_t *obj)
{
SceneSwitcherEntry::save(obj);
obs_data_set_string(obj, "windowTitle", window.c_str());
obs_data_set_bool(obj, "fullscreen", fullscreen);
obs_data_set_bool(obj, "maximized", maximized);
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");
fullscreen = obs_data_get_bool(obj, "fullscreen");
#if __APPLE__
// TODO:
// not implemented on MacOS as I cannot test it
maximized = false;
#else
maximized = obs_data_get_bool(obj, "maximized");
#endif
focus = obs_data_get_bool(obj, "focus") ||
!obs_data_has_user_value(obj, "focus");
}
WindowSwitchWidget::WindowSwitchWidget(QWidget *parent, WindowSwitch *s)
: SwitchWidget(parent, s, true, true)
{
windows = new QComboBox();
fullscreen = new QCheckBox(
obs_module_text("AdvSceneSwitcher.windowTitleTab.fullscreen"));
maximized = new QCheckBox(
obs_module_text("AdvSceneSwitcher.windowTitleTab.maximized"));
focused = new QCheckBox(
obs_module_text("AdvSceneSwitcher.windowTitleTab.focused"));
QWidget::connect(windows, 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(windows);
windows->setEditable(true);
windows->setMaxVisibleItems(20);
#if __APPLE__
// TODO:
// not implemented on MacOS as I cannot test it
maximized->setDisabled(true);
maximized->setVisible(false);
#endif
if (s) {
windows->setCurrentText(s->window.c_str());
fullscreen->setChecked(s->fullscreen);
maximized->setChecked(s->maximized);
focused->setChecked(s->focus);
}
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{windows}}", windows},
{"{{scenes}}", scenes},
{"{{transitions}}", transitions},
{"{{fullscreen}}", fullscreen},
{"{{maximized}}", maximized},
{"{{focused}}", focused}};
placeWidgets(obs_module_text("AdvSceneSwitcher.windowTitleTab.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
switchData = s;
loading = false;
}
WindowSwitch *WindowSwitchWidget::getSwitchData()
{
return switchData;
}
void WindowSwitchWidget::setSwitchData(WindowSwitch *s)
{
switchData = s;
}
void WindowSwitchWidget::swapSwitchData(WindowSwitchWidget *s1,
WindowSwitchWidget *s2)
{
SwitchWidget::swapSwitchData(s1, s2);
WindowSwitch *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
void WindowSwitchWidget::WindowChanged(const QString &text)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->window = text.toStdString();
}
void WindowSwitchWidget::FullscreenChanged(int state)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->fullscreen = state;
}
void WindowSwitchWidget::MaximizedChanged(int state)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->maximized = state;
}
void WindowSwitchWidget::FocusChanged(int state)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->focus = state;
}

View File

@@ -0,0 +1,130 @@
#include "headers/switcher-data-structs.hpp"
#include "headers/utility.hpp"
void SwitcherData::Prune()
{
for (size_t i = 0; i < windowSwitches.size(); i++) {
WindowSwitch &s = windowSwitches[i];
if (!s.valid()) {
windowSwitches.erase(windowSwitches.begin() + i--);
}
}
if (nonMatchingScene && !WeakSourceValid(nonMatchingScene)) {
switchIfNotMatching = NO_SWITCH;
nonMatchingScene = nullptr;
}
for (size_t i = 0; i < randomSwitches.size(); i++) {
RandomSwitch &s = randomSwitches[i];
if (!s.valid()) {
randomSwitches.erase(randomSwitches.begin() + i--);
}
}
for (size_t i = 0; i < screenRegionSwitches.size(); i++) {
ScreenRegionSwitch &s = screenRegionSwitches[i];
if (!s.valid()) {
screenRegionSwitches.erase(
screenRegionSwitches.begin() + i--);
}
}
for (size_t i = 0; i < pauseEntries.size(); i++) {
PauseEntry &s = pauseEntries[i];
if (!s.valid()) {
pauseEntries.erase(pauseEntries.begin() + i--);
}
}
for (size_t i = 0; i < sceneSequenceSwitches.size(); i++) {
SceneSequenceSwitch &s = sceneSequenceSwitches[i];
if (!s.valid()) {
sceneSequenceSwitches.erase(
sceneSequenceSwitches.begin() + i--);
}
auto cur = &s;
while (cur != nullptr) {
if (cur->extendedSequence &&
!cur->extendedSequence->valid()) {
cur->extendedSequence.reset(nullptr);
s.activeSequence = nullptr;
}
cur = cur->extendedSequence.get();
}
}
for (size_t i = 0; i < sceneTransitions.size(); i++) {
SceneTransition &s = sceneTransitions[i];
if (!s.valid()) {
sceneTransitions.erase(sceneTransitions.begin() + i--);
}
}
for (size_t i = 0; i < defaultSceneTransitions.size(); i++) {
DefaultSceneTransition &s = defaultSceneTransitions[i];
if (!s.valid()) {
defaultSceneTransitions.erase(
defaultSceneTransitions.begin() + i--);
}
}
for (size_t i = 0; i < executableSwitches.size(); i++) {
ExecutableSwitch &s = executableSwitches[i];
if (!s.valid()) {
executableSwitches.erase(executableSwitches.begin() +
i--);
}
}
for (size_t i = 0; i < fileSwitches.size(); i++) {
FileSwitch &s = fileSwitches[i];
if (!s.valid()) {
fileSwitches.erase(fileSwitches.begin() + i--);
}
}
for (size_t i = 0; i < timeSwitches.size(); i++) {
TimeSwitch &s = timeSwitches[i];
if (!s.valid()) {
timeSwitches.erase(timeSwitches.begin() + i--);
}
}
if (!idleData.valid()) {
idleData.idleEnable = false;
}
for (size_t i = 0; i < mediaSwitches.size(); i++) {
MediaSwitch &s = mediaSwitches[i];
if (!s.valid()) {
mediaSwitches.erase(mediaSwitches.begin() + i--);
}
}
for (size_t i = 0; i < audioSwitches.size(); i++) {
AudioSwitch &s = audioSwitches[i];
if (!s.valid()) {
audioSwitches.erase(audioSwitches.begin() + i--);
}
}
for (auto &sg : sceneGroups) {
for (size_t i = 0; i < sg.scenes.size(); i++)
if (!WeakSourceValid(sg.scenes[i])) {
sg.scenes.erase(sg.scenes.begin() + i--);
}
}
}
bool SwitcherData::versionChanged(obs_data_t *obj, std::string currentVersion)
{
std::string previousVersion = obs_data_get_string(obj, "version");
return previousVersion != currentVersion;
}
void SwitcherData::saveVersion(obs_data_t *obj, std::string currentVersion)
{
obs_data_set_string(obj, "version", currentVersion.c_str());
}

View File

@@ -1,238 +0,0 @@
#include "headers/advanced-scene-switcher.hpp"
void SceneSwitcher::on_timeSwitches_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
return;
QListWidgetItem *item = ui->timeSwitches->item(idx);
QString timeScenestr = item->data(Qt::UserRole).toString();
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->timeSwitches) {
if (timeScenestr.compare(s.timeSwitchStr.c_str()) == 0) {
QString sceneName = GetWeakSourceName(s.scene).c_str();
QString transitionName =
GetWeakSourceName(s.transition).c_str();
ui->timeScenes->setCurrentText(sceneName);
ui->timeEdit->setTime(s.time);
ui->timeTransitions->setCurrentText(transitionName);
break;
}
}
}
int SceneSwitcher::timeFindByData(const QString &timeStr)
{
QRegExp rx("At " + timeStr + " switch to .*");
int count = ui->timeSwitches->count();
for (int i = 0; i < count; i++) {
QListWidgetItem *item = ui->timeSwitches->item(i);
QString str = item->data(Qt::UserRole).toString();
if (rx.exactMatch(str))
return i;
}
return -1;
}
void SceneSwitcher::on_timeAdd_clicked()
{
QString sceneName = ui->timeScenes->currentText();
QString transitionName = ui->timeTransitions->currentText();
QTime time = ui->timeEdit->time();
if (sceneName.isEmpty())
return;
OBSWeakSource source = GetWeakSourceByQString(sceneName);
OBSWeakSource transition = GetWeakTransitionByQString(transitionName);
QString text = MakeTimeSwitchName(sceneName, transitionName, time);
QVariant v = QVariant::fromValue(text);
int idx = timeFindByData(time.toString());
if (idx == -1) {
std::lock_guard<std::mutex> lock(switcher->m);
switcher->timeSwitches.emplace_back(
source, transition, time,
(sceneName == QString(PREVIOUS_SCENE_NAME)),
text.toUtf8().constData());
QListWidgetItem *item =
new QListWidgetItem(text, ui->timeSwitches);
item->setData(Qt::UserRole, v);
} else {
QListWidgetItem *item = ui->timeSwitches->item(idx);
item->setText(text);
item->setData(Qt::UserRole, v);
{
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->timeSwitches) {
if (s.time == time) {
s.scene = source;
s.transition = transition;
s.usePreviousScene =
(sceneName ==
QString(PREVIOUS_SCENE_NAME));
s.timeSwitchStr =
text.toUtf8().constData();
break;
}
}
}
ui->timeSwitches->sortItems();
}
}
void SceneSwitcher::on_timeRemove_clicked()
{
QListWidgetItem *item = ui->timeSwitches->currentItem();
if (!item)
return;
std::string text =
item->data(Qt::UserRole).toString().toUtf8().constData();
{
std::lock_guard<std::mutex> lock(switcher->m);
auto &switches = switcher->timeSwitches;
for (auto it = switches.begin(); it != switches.end(); ++it) {
auto &s = *it;
if (s.timeSwitchStr == text) {
switches.erase(it);
break;
}
}
}
delete item;
}
void SwitcherData::checkTimeSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (timeSwitches.size() == 0)
return;
QTime now = QTime::currentTime();
for (TimeSwitch &s : timeSwitches) {
QTime validSwitchTimeWindow = s.time.addMSecs(interval);
match = s.time <= now && now <= validSwitchTimeWindow;
if (!match &&
validSwitchTimeWindow.msecsSinceStartOfDay() < interval) {
// check for overflow
match = now >= s.time || now <= validSwitchTimeWindow;
}
if (match) {
scene = (s.usePreviousScene) ? previousScene : s.scene;
transition = s.transition;
match = true;
if (verbose)
blog(LOG_INFO,
"Advanced Scene Switcher time match");
break;
}
}
}
void SwitcherData::saveTimeSwitches(obs_data_t *obj)
{
obs_data_array_t *timeArray = obs_data_array_create();
for (TimeSwitch &s : switcher->timeSwitches) {
obs_data_t *array_obj = obs_data_create();
obs_source_t *sceneSource = obs_weak_source_get_source(s.scene);
obs_source_t *transition =
obs_weak_source_get_source(s.transition);
if ((s.usePreviousScene || sceneSource) && transition) {
const char *sceneName =
obs_source_get_name(sceneSource);
const char *transitionName =
obs_source_get_name(transition);
obs_data_set_string(array_obj, "scene",
s.usePreviousScene
? PREVIOUS_SCENE_NAME
: sceneName);
obs_data_set_string(array_obj, "transition",
transitionName);
obs_data_set_string(
array_obj, "time",
s.time.toString().toStdString().c_str());
obs_data_array_push_back(timeArray, array_obj);
}
obs_source_release(sceneSource);
obs_source_release(transition);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "timeSwitches", timeArray);
obs_data_array_release(timeArray);
}
void SwitcherData::loadTimeSwitches(obs_data_t *obj)
{
switcher->timeSwitches.clear();
obs_data_array_t *timeArray = obs_data_get_array(obj, "timeSwitches");
size_t count = obs_data_array_count(timeArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(timeArray, i);
const char *scene = obs_data_get_string(array_obj, "scene");
const char *transition =
obs_data_get_string(array_obj, "transition");
QTime time = QTime::fromString(
obs_data_get_string(array_obj, "time"));
std::string timeSwitchStr =
MakeTimeSwitchName(scene, transition, time)
.toUtf8()
.constData();
switcher->timeSwitches.emplace_back(
GetWeakSourceByName(scene),
GetWeakTransitionByName(transition), time,
(strcmp(scene, PREVIOUS_SCENE_NAME) == 0),
timeSwitchStr);
obs_data_release(array_obj);
}
obs_data_array_release(timeArray);
}
void SceneSwitcher::setupTimeTab()
{
populateSceneSelection(ui->timeScenes, true);
populateTransitionSelection(ui->timeTransitions);
for (auto &s : switcher->timeSwitches) {
std::string sceneName = (s.usePreviousScene)
? PREVIOUS_SCENE_NAME
: GetWeakSourceName(s.scene);
std::string transitionName = GetWeakSourceName(s.transition);
QString listText = MakeTimeSwitchName(
sceneName.c_str(), transitionName.c_str(), s.time);
QListWidgetItem *item =
new QListWidgetItem(listText, ui->timeSwitches);
item->setData(Qt::UserRole, listText);
}
}

1006
src/volume-control.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,17 +3,21 @@
#include "../headers/advanced-scene-switcher.hpp"
#include <TlHelp32.h>
#include <Psapi.h>
#include <locale>
#include <codecvt>
#include <string>
using namespace std;
#define MAX_SEARCH 1000
static bool GetWindowTitle(HWND window, string &title)
static bool GetWindowTitle(HWND window, std::string &title)
{
size_t len = (size_t)GetWindowTextLengthW(window);
wstring wtitle;
std::wstring wtitle;
wtitle.resize(len);
if (!GetWindowTextW(window, &wtitle[0], (int)len + 1))
if (!GetWindowTextW(window, &wtitle[0], (int)len + 1)) {
return false;
}
len = os_wcs_to_utf8(wtitle.c_str(), 0, nullptr, 0);
title.resize(len);
@@ -23,67 +27,89 @@ static bool GetWindowTitle(HWND window, string &title)
static bool WindowValid(HWND window)
{
LONG_PTR styles, ex_styles;
RECT rect;
LONG_PTR styles;
DWORD id;
if (!IsWindowVisible(window))
if (!IsWindowVisible(window)) {
return false;
}
GetWindowThreadProcessId(window, &id);
if (id == GetCurrentProcessId())
if (id == GetCurrentProcessId()) {
return false;
}
GetClientRect(window, &rect);
styles = GetWindowLongPtr(window, GWL_STYLE);
ex_styles = GetWindowLongPtr(window, GWL_EXSTYLE);
if (ex_styles & WS_EX_TOOLWINDOW)
return false;
if (styles & WS_CHILD)
if (styles & WS_CHILD) {
return false;
}
return true;
}
void GetWindowList(vector<string> &windows)
BOOL CALLBACK GetTitleCB(HWND hwnd, LPARAM lParam)
{
windows.resize(0);
if (!WindowValid(hwnd)) {
return TRUE;
}
HWND window = GetWindow(GetDesktopWindow(), GW_CHILD);
std::string title;
GetWindowTitle(hwnd, title);
if (title.empty()) {
return TRUE;
}
while (window) {
string title;
if (WindowValid(window) && GetWindowTitle(window, title))
windows.emplace_back(title);
window = GetNextWindow(window, GW_HWNDNEXT);
std::vector<std::string> &titles =
*reinterpret_cast<std::vector<std::string> *>(lParam);
titles.push_back(title);
return TRUE;
}
VOID EnumWindowsWithMetro(__in WNDENUMPROC lpEnumFunc, __in LPARAM lParam)
{
HWND childWindow = NULL;
int i = 0;
while (i < MAX_SEARCH &&
(childWindow = FindWindowEx(NULL, childWindow, NULL, NULL))) {
if (!lpEnumFunc(childWindow, lParam)) {
return;
}
i++;
}
}
// Overloaded
void GetWindowList(std::vector<std::string> &windows)
{
windows.resize(0);
EnumWindowsWithMetro(GetTitleCB, reinterpret_cast<LPARAM>(&windows));
}
void GetWindowList(QStringList &windows)
{
windows.clear();
HWND window = GetWindow(GetDesktopWindow(), GW_CHILD);
while (window) {
string title;
if (WindowValid(window) && GetWindowTitle(window, title))
windows << QString::fromStdString(title);
window = GetNextWindow(window, GW_HWNDNEXT);
std::vector<std::string> w;
GetWindowList(w);
for (auto window : w) {
windows << QString::fromStdString(window);
}
// Add entry for OBS Studio itself, see GetCurrentWindowTitle
windows << QString("OBS");
}
void GetCurrentWindowTitle(string &title)
void GetCurrentWindowTitle(std::string &title)
{
HWND window = GetForegroundWindow();
DWORD pid;
DWORD thid;
thid = GetWindowThreadProcessId(window, &pid);
/*GetWindowText will freeze if the control it is reading was created in another thread.
It does not directly read the control.Instead,
it waits for the thread that created the control to process a WM_GETTEXT message.
So if that thread is frozen in a WaitFor... call you have a deadlock.*/
// GetWindowText will freeze if the control it is reading was created in another thread.
// It does not directly read the control.
// Instead it waits for the thread that created the control to process a WM_GETTEXT message.
// So if that thread is frozen in a WaitFor... call you have a deadlock.
if (GetCurrentProcessId() == pid) {
title = "OBS";
return;
@@ -102,21 +128,26 @@ std::pair<int, int> getCursorPos()
return pos;
}
bool isFullscreen(std::string &title)
HWND getHWNDfromTitle(std::string title)
{
HWND hwnd = NULL;
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
std::wstring wTitle = converter.from_bytes(title);
hwnd = FindWindowEx(NULL, NULL, NULL, wTitle.c_str());
return hwnd;
}
bool isMaximized(std::string &title)
{
RECT appBounds;
MONITORINFO monitorInfo = {0};
HWND hwnd = NULL;
HWND hwnd = GetWindow(GetDesktopWindow(), GW_CHILD);
while (hwnd) {
string wtitle;
if (WindowValid(hwnd) && GetWindowTitle(hwnd, wtitle) &&
(wtitle == title ||
QString::fromStdString(wtitle).contains(QRegularExpression(
QString::fromStdString(title)))))
break;
hwnd = GetNextWindow(hwnd, GW_HWNDNEXT);
hwnd = getHWNDfromTitle(title);
if (!hwnd) {
return false;
}
monitorInfo.cbSize = sizeof(MONITORINFO);
@@ -135,6 +166,34 @@ bool isFullscreen(std::string &title)
return true;
}
}
return false;
}
bool isFullscreen(std::string &title)
{
RECT appBounds;
MONITORINFO monitorInfo = {0};
HWND hwnd = NULL;
hwnd = getHWNDfromTitle(title);
if (!hwnd) {
return false;
}
monitorInfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST),
&monitorInfo);
if (hwnd && hwnd != GetDesktopWindow() && hwnd != GetShellWindow()) {
GetWindowRect(hwnd, &appBounds);
if (monitorInfo.rcMonitor.bottom == appBounds.bottom &&
monitorInfo.rcMonitor.top == appBounds.top &&
monitorInfo.rcMonitor.left == appBounds.left &&
monitorInfo.rcMonitor.right == appBounds.right) {
return true;
}
}
return false;
}
@@ -146,8 +205,9 @@ void GetProcessList(QStringList &processes)
PROCESSENTRY32 procEntry;
procSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (procSnapshot == INVALID_HANDLE_VALUE)
if (procSnapshot == INVALID_HANDLE_VALUE) {
return;
}
procEntry.dwSize = sizeof(PROCESSENTRY32);
@@ -158,12 +218,15 @@ void GetProcessList(QStringList &processes)
do {
QString tempexe = QString::fromWCharArray(procEntry.szExeFile);
if (tempexe == "System")
if (tempexe == "System") {
continue;
if (tempexe == "[System Process]")
}
if (tempexe == "[System Process]") {
continue;
if (processes.contains(tempexe))
}
if (processes.contains(tempexe)) {
continue;
}
processes.append(tempexe);
} while (Process32Next(procSnapshot, &procEntry));
@@ -180,8 +243,9 @@ bool isInFocus(const QString &executable)
HANDLE process = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
if (process == NULL)
if (process == NULL) {
return false;
}
WCHAR executablePath[600];
GetModuleFileNameEx(process, 0, executablePath, 600);
@@ -203,8 +267,9 @@ int getLastInputTime()
{
LASTINPUTINFO lastInputInfo;
lastInputInfo.cbSize = sizeof(LASTINPUTINFO);
if (GetLastInputInfo(&lastInputInfo))
if (GetLastInputInfo(&lastInputInfo)) {
return lastInputInfo.dwTime;
}
return 0;
}

View File

@@ -1,432 +0,0 @@
#include <obs-module.h>
#include "headers/advanced-scene-switcher.hpp"
bool isRunning(std::string &title)
{
QStringList windows;
GetWindowList(windows);
// True if switch is running (direct)
bool equals = windows.contains(QString::fromStdString(title));
// True if switch is running (regex)
bool matches = (windows.indexOf(QRegularExpression(
QString::fromStdString(title))) != -1);
return (equals || matches);
}
bool isFocused(std::string &title)
{
std::string current;
GetCurrentWindowTitle(current);
// True if switch equals current window
bool equals = (title == current);
// True if switch matches current window
bool matches = QString::fromStdString(current).contains(
QRegularExpression(QString::fromStdString(title)));
return (equals || matches);
}
void SceneSwitcher::on_up_clicked()
{
int index = ui->switches->currentRow();
if (index != -1 && index != 0) {
ui->switches->insertItem(index - 1,
ui->switches->takeItem(index));
ui->switches->setCurrentRow(index - 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->windowSwitches.begin() + index,
switcher->windowSwitches.begin() + index - 1);
}
}
void SceneSwitcher::on_down_clicked()
{
int index = ui->switches->currentRow();
if (index != -1 && index != ui->switches->count() - 1) {
ui->switches->insertItem(index + 1,
ui->switches->takeItem(index));
ui->switches->setCurrentRow(index + 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->windowSwitches.begin() + index,
switcher->windowSwitches.begin() + index + 1);
}
}
void SceneSwitcher::on_add_clicked()
{
QString sceneName = ui->scenes->currentText();
QString windowName = ui->windows->currentText();
QString transitionName = ui->transitions->currentText();
bool fullscreen = ui->fullscreenCheckBox->isChecked();
bool focus = ui->focusCheckBox->isChecked();
if (windowName.isEmpty() || sceneName.isEmpty())
return;
OBSWeakSource source = GetWeakSourceByQString(sceneName);
OBSWeakSource transition = GetWeakTransitionByQString(transitionName);
QVariant v = QVariant::fromValue(windowName);
QString text = MakeSwitchName(sceneName, windowName, transitionName,
fullscreen, focus);
int idx = FindByData(windowName);
if (idx == -1) {
std::lock_guard<std::mutex> lock(switcher->m);
switcher->windowSwitches.emplace_back(
source, windowName.toUtf8().constData(), transition,
fullscreen, focus);
QListWidgetItem *item = new QListWidgetItem(text, ui->switches);
item->setData(Qt::UserRole, v);
} else {
QListWidgetItem *item = ui->switches->item(idx);
item->setText(text);
std::string window = windowName.toUtf8().constData();
{
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->windowSwitches) {
if (s.window == window) {
s.scene = source;
s.transition = transition;
s.fullscreen = fullscreen;
s.focus = focus;
break;
}
}
}
}
}
void SceneSwitcher::on_remove_clicked()
{
QListWidgetItem *item = ui->switches->currentItem();
if (!item)
return;
std::string window =
item->data(Qt::UserRole).toString().toUtf8().constData();
{
std::lock_guard<std::mutex> lock(switcher->m);
auto &switches = switcher->windowSwitches;
for (auto it = switches.begin(); it != switches.end(); ++it) {
auto &s = *it;
if (s.window == window) {
switches.erase(it);
break;
}
}
}
delete item;
}
void SceneSwitcher::on_ignoreWindowsAdd_clicked()
{
QString windowName = ui->ignoreWindowsWindows->currentText();
if (windowName.isEmpty())
return;
QVariant v = QVariant::fromValue(windowName);
QList<QListWidgetItem *> items =
ui->ignoreWindows->findItems(windowName, Qt::MatchExactly);
if (items.size() == 0) {
QListWidgetItem *item =
new QListWidgetItem(windowName, ui->ignoreWindows);
item->setData(Qt::UserRole, v);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->ignoreWindowsSwitches.emplace_back(
windowName.toUtf8().constData());
}
}
void SceneSwitcher::on_ignoreWindowsRemove_clicked()
{
QListWidgetItem *item = ui->ignoreWindows->currentItem();
if (!item)
return;
QString windowName = item->data(Qt::UserRole).toString();
{
std::lock_guard<std::mutex> lock(switcher->m);
auto &switches = switcher->ignoreWindowsSwitches;
for (auto it = switches.begin(); it != switches.end(); ++it) {
auto &s = *it;
if (s == windowName.toUtf8().constData()) {
switches.erase(it);
break;
}
}
}
delete item;
}
int SceneSwitcher::FindByData(const QString &window)
{
int count = ui->switches->count();
int idx = -1;
for (int i = 0; i < count; i++) {
QListWidgetItem *item = ui->switches->item(i);
QString itemWindow = item->data(Qt::UserRole).toString();
if (itemWindow == window) {
idx = i;
break;
}
}
return idx;
}
int SceneSwitcher::IgnoreWindowsFindByData(const QString &window)
{
int count = ui->ignoreWindows->count();
int idx = -1;
for (int i = 0; i < count; i++) {
QListWidgetItem *item = ui->ignoreWindows->item(i);
QString itemRegion = item->data(Qt::UserRole).toString();
if (itemRegion == window) {
idx = i;
break;
}
}
return idx;
}
void SceneSwitcher::on_switches_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
return;
QListWidgetItem *item = ui->switches->item(idx);
QString window = item->data(Qt::UserRole).toString();
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->windowSwitches) {
if (window.compare(s.window.c_str()) == 0) {
std::string name = GetWeakSourceName(s.scene);
std::string transitionName =
GetWeakSourceName(s.transition);
ui->scenes->setCurrentText(name.c_str());
ui->windows->setCurrentText(window);
ui->transitions->setCurrentText(transitionName.c_str());
ui->fullscreenCheckBox->setChecked(s.fullscreen);
ui->focusCheckBox->setChecked(s.focus);
break;
}
}
}
void SceneSwitcher::on_ignoreWindows_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
return;
QListWidgetItem *item = ui->ignoreWindows->item(idx);
QString window = item->data(Qt::UserRole).toString();
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &s : switcher->ignoreWindowsSwitches) {
if (window.compare(s.c_str()) == 0) {
ui->ignoreWindowsWindows->setCurrentText(s.c_str());
break;
}
}
}
void SwitcherData::checkWindowTitleSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
std::string title;
bool ignored = false;
// Check if current window is ignored
GetCurrentWindowTitle(title);
for (auto &window : ignoreWindowsSwitches) {
// True if ignored switch equals current window
bool equals = (title == window);
// True if ignored switch matches current window
bool matches = QString::fromStdString(title).contains(
QRegularExpression(QString::fromStdString(window)));
if (equals || matches) {
ignored = true;
title = lastTitle;
break;
}
}
lastTitle = title;
// Check for match
for (WindowSceneSwitch &s : windowSwitches) {
// True if fullscreen is disabled OR current window is fullscreen
bool fullscreen = (!s.fullscreen || isFullscreen(s.window));
// True if focus is disabled OR switch is focused
bool focus = (!s.focus || isFocused(s.window));
// True if current window is ignored AND switch equals OR matches last window
bool ignore =
(ignored &&
(title == s.window ||
QString::fromStdString(title).contains(
QRegularExpression(
QString::fromStdString(s.window)))));
if (isRunning(s.window) && (fullscreen && (focus || ignore))) {
match = true;
scene = s.scene;
transition = s.transition;
break;
}
}
}
void SwitcherData::saveWindowTitleSwitches(obs_data_t *obj)
{
obs_data_array_t *windowTitleArray = obs_data_array_create();
for (WindowSceneSwitch &s : switcher->windowSwitches) {
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_set_string(array_obj, "window_title",
s.window.c_str());
obs_data_set_bool(array_obj, "fullscreen",
s.fullscreen);
obs_data_set_bool(array_obj, "focus", s.focus);
obs_data_array_push_back(windowTitleArray, array_obj);
obs_source_release(source);
obs_source_release(transition);
}
obs_data_release(array_obj);
}
obs_data_set_array(obj, "switches", windowTitleArray);
obs_data_array_release(windowTitleArray);
obs_data_array_t *ignoreWindowsArray = obs_data_array_create();
for (std::string &window : switcher->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);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "ignoreWindows", ignoreWindowsArray);
obs_data_array_release(ignoreWindowsArray);
}
void SwitcherData::loadWindowTitleSwitches(obs_data_t *obj)
{
switcher->windowSwitches.clear();
obs_data_array_t *windowTitleArray =
obs_data_get_array(obj, "switches");
size_t count = obs_data_array_count(windowTitleArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj =
obs_data_array_item(windowTitleArray, i);
const char *scene = obs_data_get_string(array_obj, "scene");
const char *transition =
obs_data_get_string(array_obj, "transition");
const char *window =
obs_data_get_string(array_obj, "window_title");
bool fullscreen = obs_data_get_bool(array_obj, "fullscreen");
bool focus = obs_data_get_bool(array_obj, "focus") ||
!obs_data_has_user_value(array_obj, "focus");
switcher->windowSwitches.emplace_back(
GetWeakSourceByName(scene), window,
GetWeakTransitionByName(transition), fullscreen, focus);
obs_data_release(array_obj);
}
obs_data_array_release(windowTitleArray);
switcher->ignoreWindowsSwitches.clear();
obs_data_array_t *ignoreWindowsArray =
obs_data_get_array(obj, "ignoreWindows");
count = obs_data_array_count(ignoreWindowsArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj =
obs_data_array_item(ignoreWindowsArray, i);
const char *window =
obs_data_get_string(array_obj, "ignoreWindow");
switcher->ignoreWindowsSwitches.emplace_back(window);
obs_data_release(array_obj);
}
obs_data_array_release(ignoreWindowsArray);
}
void SceneSwitcher::setupTitleTab()
{
populateSceneSelection(ui->scenes, false);
populateTransitionSelection(ui->transitions);
populateWindowSelection(ui->windows);
populateWindowSelection(ui->ignoreWindowsWindows);
for (auto &s : switcher->windowSwitches) {
std::string sceneName = GetWeakSourceName(s.scene);
std::string transitionName = GetWeakSourceName(s.transition);
QString text = MakeSwitchName(sceneName.c_str(),
s.window.c_str(),
transitionName.c_str(),
s.fullscreen, s.focus);
QListWidgetItem *item = new QListWidgetItem(text, ui->switches);
item->setData(Qt::UserRole, s.window.c_str());
}
for (auto &window : switcher->ignoreWindowsSwitches) {
QString text = QString::fromStdString(window);
QListWidgetItem *item =
new QListWidgetItem(text, ui->ignoreWindows);
item->setData(Qt::UserRole, text);
}
}