Compare commits

..

173 Commits
1.4 ... 1.8

Author SHA1 Message Date
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
WarmUpTill
255d10e603 fix auto fill not wokring for modified entries on time tab 2020-08-09 20:48:49 +02:00
WarmUpTill
ab3e93ab84 add fallback to old format for sequence import 2020-08-02 20:22:13 +02:00
WarmUpTill
3b5f88c14f do not sort sequence and region on add 2020-08-02 19:38:32 +02:00
WarmUpTill
2f33b6bdc3 fix data loss for sequence delay 2020-08-02 19:37:37 +02:00
WarmUpTill
3c1e240722 add option to set startup behavior 2020-08-02 18:45:44 +02:00
WarmUpTill
91831a1075 fix autoStop not being persisted 2020-08-02 18:00:50 +02:00
WarmUpTill
ccc824abc1 fix loading of old delay values 2020-08-02 17:46:36 +02:00
WarmUpTill
6f39d4648b add included 'thread' and 'QAction' for Linux and macOS build 2020-08-01 20:34:12 +02:00
WarmUpTill
74cb400426 Merge branch 'master' of https://github.com/WarmUpTill/SceneSwitcher 2020-08-01 20:15:49 +02:00
WarmUpTill
910b1dde99 set sceneRoundTripDelayUnits 2020-08-01 16:19:57 +02:00
WarmUpTill
e462d7f420 fix sequence not stopping when manually switching scene 2020-08-01 16:11:54 +02:00
WarmUpTill
0cdd8bf1d0 move UI init of tabs to respective source files 2020-08-01 11:14:54 +02:00
WarmUpTill
f8ad8c31be scene sequence adjustments
use double internally instead of two ints
rework save / load to file to reuse existing functionality
add option to select between seconds / minutes / hours
2020-08-01 10:12:32 +02:00
WarmUpTill
93c889c215 fix MacOS CI issue 2020-07-31 22:18:52 +02:00
WarmUpTill
f8b505fa4e silence warnings 2020-07-31 22:05:55 +02:00
WarmUpTill
1516263d2a save and load tab order 2020-07-31 21:58:17 +02:00
WarmUpTill
79f5ec340d allow reordering of tabs and silence a few UI warnings 2020-07-29 20:23:03 +02:00
WarmUpTill
cd54cc17c0 fix switch priority falling back to default values on startup 2020-07-09 19:57:55 +02:00
WarmUpTill
098159dc0f increase window size on mac
currently the general tab will be compressed to an almost unusable level due to the differences in Windows and Macintosh styles
2020-06-30 20:11:44 +02:00
WarmUpTill
a4418996f4 disable mediaTime selection if restriction is set to none 2020-06-19 19:52:43 +02:00
WarmUpTill
340cb559ee add option to override transitionOverrides 2020-06-17 22:16:47 +02:00
WarmUpTill
eeefd8279f allow reordering of file/media/sequence/region 2020-06-16 20:25:25 +02:00
WarmUpTill
e62dc91cd7 fix linux/mac build error 2020-06-15 21:46:58 +02:00
WarmUpTill
536041abd2 Merge branch 'master' of https://github.com/WarmUpTill/SceneSwitcher 2020-06-15 21:20:02 +02:00
WarmUpTill
d3ab9e01c5 add support for remote file using libcurl 2020-06-15 21:19:34 +02:00
WarmUpTill
2113025877 undo switch to Visual Studio 15 2017
windows-latest image only supports Visual Studio 16 2019
2020-06-15 18:48:32 +02:00
WarmUpTill
6aaffeb5a4 use CMAKE_GENERATOR "Visual Studio 15 2017"
the 2017 version of the Microsoft Visual C++ Redistributable for Visual Studio is more widespread than the 2019 one and thus is less likely to require users to install the Redistributable manually
2020-06-15 18:32:01 +02:00
WarmUpTill
2a92df4f3c add draft of remote file support 2020-06-14 20:35:02 +02:00
WarmUpTill
560044bbbf implement export/import of settings 2020-06-14 15:22:34 +02:00
WarmUpTill
4bd8cdf5a0 move auto start/stop to gernal.cpp 2020-06-14 14:17:03 +02:00
WarmUpTill
c17cbb66d9 write to OBS log if verbose logging is enabled 2020-06-14 14:14:00 +02:00
WarmUpTill
8d23dd46b0 add export/import button and verbose log checkbox 2020-06-14 13:57:11 +02:00
WarmUpTill
bf70bd7c60 move saving and loading to separate files 2020-06-13 21:56:26 +02:00
WarmUpTill
1a144d84f5 set correct qt lib path for macos builds 2020-06-12 21:42:23 +02:00
WarmUpTill
6810ba71e8 remove using namespace std 2020-06-12 21:40:06 +02:00
WarmUpTill
bbe6a30506 clarify idle detection inputs 2020-06-12 18:48:30 +02:00
WarmUpTill
08c668f90a Merge pull request #24 from Myned/master
General improvements, changes, and fixes
2020-06-12 18:39:08 +02:00
WarmUpTill
3151a7542f return true in isFullscreen() if window maximized 2020-06-11 16:25:17 +02:00
Myned
7bfde0a39b Add regex matching to fullscreen check 2020-06-10 04:06:04 -04:00
Myned
7347be370e Fix type error
Pushed a little too soon
2020-06-09 17:14:22 -04:00
Myned
66d847c047 Fix ignored switches not directly matching last window 2020-06-09 17:10:14 -04:00
Myned
c9f1f9624b Use actual window titles and fix fullscreen check on macOS
Fallback to window owner if title does not exist
Remove duplicate entries from list
2020-06-09 07:49:39 -04:00
Myned
8e4a8b7e9d Use QString::fromStdString() instead of c_str() 2020-06-09 07:10:30 -04:00
Myned
e693794549 Add sorting of window lists for UI 2020-06-09 07:09:08 -04:00
Myned
7a924438bc Add .clang-format and apply to src/ files 2020-06-08 14:58:44 -04:00
Myned
7458e9f2d5 Change and fix a multitude of UI elements
Fix spelling
Add minimum size
Simplify tab names and reorder by type and use
Change layout of start, interval, and no-condition selection
2020-06-07 21:09:48 -04:00
Myned
da2e11e35e Resize UI to fit added elements
This size is what Qt Designer defaults to when saving
2020-06-07 14:19:23 -04:00
Myned
9f7ca3e4c7 Merge remote-tracking branch 'WarmUpTill/master' 2020-06-07 14:15:08 -04:00
WarmUpTill
2f77d99161 Undo "Revert "do not use QThread::create""
This reverts commit 4b86849356.
2020-06-07 14:15:01 -04:00
WarmUpTill
acd2e2863a adjust win isFullscreen() to use GetMonitorInfo()
only tested on a single monitor setup but should work on secondary monitors also as MONITOR_DEFAULTTONEAREST is set
2020-06-07 13:25:51 +02:00
WarmUpTill
513fe0728c initialize SwitcherThread to nullptr 2020-06-07 10:43:52 +02:00
Myned
cba39633aa Add ability to reorder Window and Executable switches with arrows 2020-06-06 23:23:02 -04:00
Myned
c6a26d862f Enable sorting for Ignored windows
There is no use in reordering them
2020-06-06 23:21:06 -04:00
Myned
9386109168 Remove tooltip from UI
Wasn't able to isolate it to the link, so it gets in the way
2020-06-06 23:20:14 -04:00
WarmUpTill
4b86849356 Revert "do not use QThread::create"
This reverts commit 98c3131017.
2020-06-06 22:38:22 -04:00
Myned
8fd263f44a Merge remote-tracking branch 'WarmUpTill/master' 2020-06-06 18:40:17 -04:00
Myned
5590e97f4f Fix fullscreen check only returning true if window is focused on Windows
by @WarmUpTill
2020-06-06 18:39:57 -04:00
WarmUpTill
98c3131017 do not use QThread::create
there might still be versions of QT in use which do not yet have this functionality (see Linux build fail of f03fca8)
2020-06-06 18:51:26 +02:00
WarmUpTill
f03fca85aa add thread priority option (using QThread) 2020-06-06 17:05:49 +02:00
WarmUpTill
7fef29dafb fix media delete button deleting wrong entry
the mediaSwitches UI is sorted alphabetically while the switches data strucutre is not
the previous implementation of the delete function was just using the index of the currently selected row to delete  this index of the switches data structure
the new implementation will try to find the correct entry and delete it
2020-06-06 08:59:03 +02:00
WarmUpTill
9f03264672 fix memory leak in check for media switch 2020-06-04 20:35:36 +02:00
WarmUpTill
5082f5944e prune media switches if invalid 2020-06-03 19:56:18 +02:00
WarmUpTill
b981a446b6 fix for times switch not saving when using previous scene 2020-06-03 19:55:06 +02:00
WarmUpTill
5ab8199e03 add log on start / stop of switcher thread 2020-06-02 20:53:26 +02:00
WarmUpTill
e6a3f98ab7 use source_id to identify meida sources 2020-06-02 20:45:46 +02:00
WarmUpTill
5e60cefd1c update current selection on media tab when selecting a different row 2020-06-02 19:34:15 +02:00
Myned
fabb7406bd Merge remote-tracking branch 'WarmUpTill/master' 2020-06-02 00:48:01 -04:00
Myned
cfb0a44b1f Change UI to specify greater than
Scene locking can occur when interval is equal to transition time due to inconsistencies in timers
2020-06-02 00:40:22 -04:00
Myned
78ed7415f5 Fix allocation error 2020-06-02 00:39:14 -04:00
Myned
d6380b865e Fix some windows (Steam) not being found on Linux
Reverts part of commit e61905d02f

Fallback to WM_NAME if _NET_WM_NAME does not exist

Also fixes some BadWindow errors that were due to some apps like Steam only populating WM_NAME, and only for certain windows (tooltips count as windows due to the odd way the GUI works on Linux, so they don't have either property)
2020-06-02 00:38:35 -04:00
WarmUpTill
80cc8ba06c fix install_name_tool path for libobs 2020-06-01 15:21:45 +02:00
WarmUpTill
f1d5b110b8 adjustments for macos installer
adjust default install path
adjust lib lookup paths
2020-06-01 14:56:59 +02:00
WarmUpTill
2e475b8059 add first draft of thread priority 2020-06-01 14:54:08 +02:00
WarmUpTill
99b5c16406 add Time scene switching 2020-05-31 12:43:34 +02:00
Myned
55d8303ecf Fix isFullscreen() logic error for Linux
Not sure why this arose in the first place
2020-05-26 02:44:12 -04:00
Myned
23201100fa Fix macOS build error (again)
Adds to f0cf5a5c19

Forgot to remove vestigial code
2020-05-23 00:34:40 -04:00
Myned
3959e41e88 Add clearing of passed window list like with other platforms 2020-05-23 00:27:19 -04:00
Myned
f0cf5a5c19 Fix Windows and macOS build errors
Adds to e4b640903d and de5b6f8eb4
2020-05-23 00:25:30 -04:00
Myned
87da123c19 Change default focus value to true 2020-05-23 00:00:04 -04:00
Myned
2a0e9e9d9a Change wording 2020-05-22 23:58:18 -04:00
WarmUpTill
9bedeedcb6 fix issue with out-of-tree builds not being able to find *ui on some platforms 2020-05-22 19:41:48 +02:00
WarmUpTill
a88a5bb2da adjust macos pkg name 2020-05-21 21:04:26 +02:00
WarmUpTill
cfd6be2ba0 fix mac and linux builds
ignore data/ dir as it is not used by this plugin
adjust path to .so in SceneSwitcher.pkgproj
2020-05-21 20:53:49 +02:00
WarmUpTill
3658efff20 add missing env. 2020-05-21 20:16:37 +02:00
WarmUpTill
8331b50c5a try to resovle github actions issues
add CI/ for mac
use LIB_NAME instead of PLUGIN_NAME when saving results
2020-05-21 20:11:17 +02:00
WarmUpTill
7a343c2070 try setting up actions 2020-05-21 19:19:08 +02:00
Myned
a7a8adb91b Remove whitespace 2020-05-21 00:54:49 -04:00
Myned
e61905d02f Change window title property on Linux to align with freedesktop spec
Freedesktop recommends using _NET_WM_NAME instead of WM_NAME, and on KDE, for example, the difference between them is including the application name for some windows (e.g. "freedesktop - Google Search - Vivaldi" compared to "freedesktop - Google Search")
2020-05-21 00:53:47 -04:00
Myned
f00c244bfa Change wording slightly 2020-05-21 00:52:41 -04:00
Myned
463b54f1fb Remove sorting when adding switches 2020-05-21 00:52:11 -04:00
Myned
70894f9302 Add regex matching to reverted isInFocus() for Windows
Reverts part of a4de8c0c59
2020-05-21 00:42:58 -04:00
Myned
de5b6f8eb4 Fix fullscreen check only returning true if window is focused on Linux 2020-05-21 00:40:37 -04:00
Myned
be83232caa Update UI for focus addition, add/change warnings, and move exe tab 2020-05-21 00:37:03 -04:00
Myned
e4b640903d Add ability to choose focus state to window title switcher 2020-05-21 00:34:22 -04:00
Myned
a4de8c0c59 Change isInFocus() check to be OS-agnostic
isInFocus() can utilize the platform-dependent GetCurrentWindowTitle() instead of re-implementing the same functionality.

Tested on Linux and macOS without issues
2020-05-18 00:11:07 -04:00
Myned
ac051a42a8 Remove unnecessary spaces 2020-05-18 00:06:19 -04:00
Myned
e33e93fd18 Change UI warning to allow direct matches and disable list sorting 2020-05-17 13:47:48 -04:00
Myned
d5052c94ca Add direct matching to window, executable, and ignored switches
Reverts part of commit 5f87a61177

Direct matching occurs in the same loop as regex in order to allow the list order to function as indicated in the UI
2020-05-17 13:44:50 -04:00
Myned
f0aa76517a Change executable switcher warning to regex requirement
Also change wording to align with Window Title, and fix an inconsequential error
2020-05-17 00:27:01 -04:00
Myned
a2543f1b85 Add ability to use existing window ignore for executable switching
Also cleaned up some logic
2020-05-17 00:24:24 -04:00
Myned
5f87a61177 Remove direct match from window switcher and reorganize
Direct matching before regex has the potential to interfere with some usecases. For example, if a user wants to enter regex syntax such as `shell[1]`, a direct match would return a window matching `shell[1]` exactly instead of `shell1`. Granted, the odds of the two conflicting each other are slim, but the core logic shouldn't prevent a user from utilizing regex to its full potential. One can always escape reserved characters if they want to match it directly. I have also added a warning and link to https://regexr.com in the UI should this be accepted.

The rest of what has changed is simply to align with the executable switcher's matching, namely the use of `QRegularExpression` to drop the need for a try/catch block and moving the check for fullscreen to the if statement in order to remove the possibility of a race condition.
2020-05-17 00:18:28 -04:00
Myned
451bb6518c Revert "Move forms/advanced-scene-switcher.ui to src/headers/"
This reverts commit 318502d8d0.
2020-05-16 23:42:26 -04:00
Myned
318502d8d0 Move forms/advanced-scene-switcher.ui to src/headers/
Out-of-tree building (at least) fails without this file in this location, and builds successfully without it under forms
2020-05-14 19:11:04 -04:00
Myned
e9b1ef58ba Change executable list to disable sorting
This allows the UI to show the order in which the executables will be checked
2020-05-14 18:56:39 -04:00
Myned
26c24d4d53 Change executable entry box to be editable 2020-05-14 18:56:39 -04:00
Myned
3654f39247 Fix scene not switching with multiple executables 2020-05-14 18:56:39 -04:00
Myned
b45b38cf6a Add ability to use regex for executable switching
Previously, only exact matches to a window title would switch scenes
2020-05-14 18:56:39 -04:00
WarmUpTill
228a18eb77 fix media switch not saving when using previous scene as target 2020-04-27 20:51:58 +02:00
WarmUpTill
4ed12db12b add support for previous scene on media tab 2020-04-27 20:20:50 +02:00
WarmUpTill
b20b894b2f add hint about install_name_tool for MacOS builds 2020-04-27 19:39:05 +02:00
WarmUpTill
7c052f2633 do not link against X11 for MAC in-tree builds 2020-04-27 18:46:32 +02:00
WarmUpTill
9305576789 Merge pull request #18 from exeldro/media
add media
2020-04-26 15:09:51 +02:00
Exeldro
38bd59c1fd add media 2020-04-25 13:55:53 +02:00
WarmUpTill
7f84de64d8 README.md: add BUILD_OUT_OF_TREE variable to Compiling out of tree section 2020-04-12 23:33:12 +02:00
WarmUpTill
5ae90fe913 README.md: Add separate sections for "Compiling in and out of tree" 2020-04-12 21:37:36 +02:00
WarmUpTill
115a90196c fix deadlock when rapidly switching scenes 2020-04-12 21:26:45 +02:00
WarmUpTill
5e92a7bcdf use std::shuffle instead of std::random_shuffle 2020-04-12 21:25:11 +02:00
WarmUpTill
922cf95119 support both in- and out-of-tree builds 2020-04-12 21:23:30 +02:00
WarmUpTill
05540b61a1 add separate hotkey (fix for out of tree builds) 2020-02-16 20:47:55 +01:00
WarmUpTill
218ad13160 add separate hotkeys for start and stop 2020-02-16 20:35:12 +01:00
WarmUpTill
6e18eebb25 don't link against X11 on Mac 2020-01-30 21:14:51 +01:00
WarmUpTill
b7058f97a9 Merge pull request #10 from c3r1c3/out-of-tree-builds
Out of tree builds
2019-02-11 14:17:32 +01:00
c3r1c3
b0c00dfa98 README.md: Added Windows build instructions 2019-02-09 16:26:33 -06:00
Cephas Reis
7a074aa548 README.md: Updated the Mac and Linux build instructions. 2019-02-09 16:13:41 -06:00
c3r1c3
51c740bdc6 README.md: Updated Mac Build instructions. Still need to fix Windows and Linux. 2019-02-09 14:54:11 -06:00
Cephas Reis
826a25926c README.md: Update with new install/build instructions 2019-02-09 01:35:47 -06:00
Cephas Reis
e2cd39315f CMakeLists.txt: Fix-ups for out-of-tree builds.
CMake really likes it when you specify a minimum version. Let's do that.
Also added the ability te build and install on Linux out-of-tree.
Mac *should* work, but I can't test it.
Windows *should* work, but it has some trouble compiling on VS2017 15.9+ because
of changes MS made to their compilier. Seeing as that issue is outside the scope
and modifications made in this PR, that is an issue to address another day.
2019-02-09 01:35:46 -06:00
Cephas Reis
7665bf8872 Platform-specific commits. Basically fixes for the directories. 2019-02-09 01:35:46 -06:00
c3r1c3
14d78100c1 Most files: Move files into subdirectories
Almost all the files have been moved into folders and subfolders to keep everything organized and clean.
2019-02-09 01:35:46 -06:00
Cephas Reis
92d75bbddb external/: Add OBS helper cmake files 2019-02-09 01:35:46 -06:00
Cephas Reis
14d6e6f5bf .gitignore: Let's start by adding a normal set of ignores. 2018-12-27 01:14:37 -06:00
WarmUpTill
78fa18f22a prevent idle switch loop when using previous scene as target 2018-08-16 12:28:10 +02:00
WarmUpTill
bbf7d4acf5 add 'previous scene' option for idle detection 2018-08-14 22:44:34 +02:00
WarmUpTill
299f7c00e0 fix save/load file for scene sequence 2018-07-20 21:00:06 +02:00
WarmUpTill
70c0a8ed57 add previous scene as option for scene round trip 2018-07-13 23:54:05 +02:00
WarmUpTill
d41868c660 cleanup 2018-07-13 22:29:01 +02:00
WarmUpTill
3381e6d62f read any file, fix space in file switch name 2018-05-22 21:32:41 +02:00
WarmUpTill
6338aaa891 add regex, date check to file switch 2018-05-22 21:04:06 +02:00
WarmUpTill
104f74bf39 add option to switch based on file contents 2018-05-21 23:20:02 +02:00
WarmUpTill
9cc2875455 add note of dependency on libXScrnSaver 2018-05-21 23:16:57 +02:00
WarmUpTill
e466bf9750 fix for scene not being written to file 2018-04-19 19:52:13 +02:00
WarmUpTill
42dcd9dec4 fix for certain window names not being detected 2018-03-05 02:26:34 +01:00
WarmUpTill
c934b5c7ff option to switch to random scene if no no match
added random tab:
scenes listed here can be switched to at random for a set duration if no switch condition is met (and this setting is selected on the general tab)
2018-02-19 19:32:09 +01:00
WarmUpTill
dd2d70bb9c Fix for crash on Scene Collection change
functionNamesByPriority was cleared while in use when changing Scene Collection.
2018-02-16 20:42:58 +01:00
WarmUpTill
ad052e9323 remove all translations 2018-01-13 17:09:00 +01:00
WarmUpTill
086d07f678 Removed Translations, bugfixes for Round Trip 2018-01-13 16:27:32 +01:00
WarmUpTill
c606869d41 changed window name of the OBS window to "OBS"
"Advanced Scene Switcher" didn't make sense for the whole of OBS
2017-11-29 21:21:25 +01:00
WarmUpTill
12512f7d0a fix for not being able to modify / delete old scene sequences
the "sceneRounTripStr" was not adjusted to match the increased precision of the timer
2017-11-28 20:01:38 +01:00
WarmUpTill
1ed924a39c Increased Scene Sequence timer limit 2017-11-14 20:04:30 +01:00
WarmUpTill
59ed240311 Delete advanced-scene-switcher.ui 2017-11-14 20:04:07 +01:00
WarmUpTill
712767b824 Increased Scene Sequence timer limit 2017-11-14 20:03:27 +01:00
WarmUpTill
bb4035824b Increased Scene Sequence timer limit 2017-11-14 19:58:43 +01:00
WarmUpTill
a1d385f5dc Added build instructions 2017-11-09 19:31:37 +01:00
WarmUpTill
3c9b1f7ad8 fix cursor pos crash 2017-11-09 19:03:00 +01:00
WarmUpTill
fc66509e50 More precise timing in scene sequence 2017-11-03 23:47:15 +01:00
WarmUpTill
62f1109256 data not needed anymore 2017-10-31 15:16:19 +01:00
WarmUpTill
25540aee2b Regex support for Ignore windows 2017-10-29 14:10:30 +01:00
WarmUpTill
53aa828c9e Remove invalid path warning on empty path 2017-10-29 14:09:54 +01:00
69 changed files with 14129 additions and 5471 deletions

107
.clang-format Normal file
View File

@@ -0,0 +1,107 @@
# please use clang-format version 8 or later
Standard: Cpp11
AccessModifierOffset: -8
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
#AllowAllArgumentsOnNextLine: false # requires clang-format 9
#AllowAllConstructorInitializersOnNextLine: false # requires clang-format 9
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: false
#AllowShortLambdasOnASingleLine: Inline # requires clang-format 9
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakStringLiterals: false # apparently unpredictable
ColumnLimit: 80
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
FixNamespaceComments: false
ForEachMacros:
- 'json_object_foreach'
- 'json_object_foreach_safe'
- 'json_array_foreach'
IncludeBlocks: Preserve
IndentCaseLabels: false
IndentPPDirectives: None
IndentWidth: 8
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
#ObjCBinPackProtocolList: Auto # requires clang-format 7
ObjCBlockIndentWidth: 8
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 10
PenaltyBreakBeforeFirstCallParameter: 30
PenaltyBreakComment: 10
PenaltyBreakFirstLessLess: 0
PenaltyBreakString: 10
PenaltyExcessCharacter: 100
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: false
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
#SpaceAfterLogicalNot: false # requires clang-format 9
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
#SpaceBeforeCtorInitializerColon: true # requires clang-format 7
#SpaceBeforeInheritanceColon: true # requires clang-format 7
SpaceBeforeParens: ControlStatements
#SpaceBeforeRangeBasedForLoopColon: true # requires clang-format 7
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
#StatementMacros: # requires clang-format 8
# - 'Q_OBJECT'
TabWidth: 8
#TypenameMacros: # requires clang-format 9
# - 'DARRAY'
UseTab: ForContinuationAndIndentation
---
Language: ObjC

295
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,295 @@
name: build obs plugin
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
PLUGIN_NAME: SceneSwitcher
LIB_NAME: advanced-scene-switcher
jobs:
macos64:
name: "macOS 64-bit"
runs-on: [macos-latest]
env:
QT_VERSION: 5.14.1
MACOS_DEPS_VERSION: '2020-08-30'
steps:
- name: Checkout
uses: actions/checkout@v2
with:
repository: obsproject/obs-studio
submodules: 'recursive'
- name: "Checkout plugin"
uses: actions/checkout@v2
with:
path: UI/frontend-plugins/${{ env.PLUGIN_NAME }}
- name: Fetch Git Tags
run: |
cd UI/frontend-plugins/${{ env.PLUGIN_NAME }}
git fetch --prune --tags --unshallow
- name: 'Install prerequisites (Homebrew)'
shell: bash
run: |
cd UI/frontend-plugins/${{ env.PLUGIN_NAME }}/CI/macos
brew untap local/openssl
brew untap local/python2
brew update
brew bundle
cd -
- 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.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 -DDISABLE_PYTHON=ON -DDepsPath="/tmp/obsdeps" -DQTDIR="/tmp/obsdeps" ..
cd -
- name: Build
shell: bash
run: |
set -e
cd ./build
make -j4
cd -
- name: 'Install prerequisite: Packages app'
if: success()
shell: bash
run: |
curl -L -O https://s3-us-west-2.amazonaws.com/obs-nightly/Packages.pkg
sudo installer -pkg ./Packages.pkg -target /
- name: Package
if: success()
shell: bash
run: |
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 /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}"
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
with:
name: '${{ env.FILE_NAME }}'
path: ./nightly/*.pkg
ubuntu64:
name: 'Linux/Ubuntu 64-bit'
runs-on: [ubuntu-latest]
steps:
- name: Checkout
uses: actions/checkout@v2
with:
repository: obsproject/obs-studio
submodules: 'recursive'
- name: "Checkout plugin"
uses: actions/checkout@v2
with:
path: UI/frontend-plugins/${{ env.PLUGIN_NAME }}
- name: Add plugin to obs cmake
shell: bash
run: echo "add_subdirectory(${{ env.PLUGIN_NAME }})" >> UI/frontend-plugins/CMakeLists.txt
- name: Fetch Git Tags
run: git fetch --prune --tags --unshallow
- name: Install prerequisites (Apt)
shell: bash
run: |
sudo dpkg --add-architecture amd64
sudo apt-get -qq update
sudo apt-get install -y \
build-essential \
checkinstall \
cmake \
libasound2-dev \
libavcodec-dev \
libavdevice-dev \
libavfilter-dev \
libavformat-dev \
libavutil-dev \
libcurl4-openssl-dev \
libfdk-aac-dev \
libfontconfig-dev \
libfreetype6-dev \
libgl1-mesa-dev \
libjack-jackd2-dev \
libjansson-dev \
libluajit-5.1-dev \
libpulse-dev \
libqt5x11extras5-dev \
libspeexdsp-dev \
libswresample-dev \
libswscale-dev \
libudev-dev \
libv4l-dev \
libva-dev \
libvlc-dev \
libx11-dev \
libx264-dev \
libxcb-randr0-dev \
libxcb-shm0-dev \
libxcb-xinerama0-dev \
libxcomposite-dev \
libxinerama-dev \
libmbedtls-dev \
pkg-config \
python3-dev \
qtbase5-dev \
libqt5svg5-dev \
swig \
libxss-dev
- name: 'Configure'
shell: bash
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 ..
- name: 'Build'
shell: bash
working-directory: ${{ github.workspace }}/build
run: make -j4
- name: 'Package'
shell: bash
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 }}
- name: 'Publish'
uses: actions/upload-artifact@v2-preview
with:
name: '${{ env.FILE_NAME }}'
path: '*.tar.gz'
win64:
name: Windows 64-bit
runs-on: [windows-latest]
env:
QT_VERSION: 5.10.1
CMAKE_GENERATOR: "Visual Studio 16 2019"
CMAKE_SYSTEM_VERSION: "10.0.18363.657"
steps:
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.0
- name: Checkout obs
uses: actions/checkout@v2
with:
repository: obsproject/obs-studio
submodules: 'recursive'
- name: Checkout plugin
uses: actions/checkout@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'
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 ./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
- 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
if: success()
uses: actions/upload-artifact@v2-preview
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"
steps:
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.0
- name: Checkout obs
uses: actions/checkout@v2
with:
repository: obsproject/obs-studio
submodules: 'recursive'
- name: Checkout plugin
uses: actions/checkout@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'
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\*
- name: Publish
if: success()
uses: actions/upload-artifact@v2-preview
with:
name: '${{ env.FILE_NAME }}'
path: '*-win32.zip'

98
.gitignore vendored Normal file
View File

@@ -0,0 +1,98 @@
# Prerequisites #
#################
*.d
# Object files #
################
*.o
*.ko
*.obj
*.elf
*.slo
*.lo
*.obj
# Linker output #
#################
*.ilk
*.map
*.exp
# Precompiled Headers #
#######################
*.gch
*.pch
# Libraries #
#############
*.lib
*.a
*.la
*.lo
*.lai
# Shared objects (inc. Windows DLLs) #
######################################
*.dll
*.so
*.so.*
*.dylib
# Executables #
###############
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files #
###############
*.dSYM/
*.su
*.idb
*.pdb
# CMake temp files #
####################
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake_install
# OS generated files #
######################
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
*.directory
# backup text files generated by an editor #
############################################
**/*~
# Various IDE Files #
#####################
.classpath
.project
.settings
.idea
.metadata
*.iml
*.ipr
*.sublime*
# Directories #
###############
build/

5
CI/macos/Brewfile Normal file
View File

@@ -0,0 +1,5 @@
brew "jack"
brew "speexdsp"
brew "cmake"
brew "freetype"
brew "fdk-aac"

View File

@@ -0,0 +1,760 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PROJECT</key>
<dict>
<key>PACKAGE_FILES</key>
<dict>
<key>DEFAULT_INSTALL_LOCATION</key>
<string>/</string>
<key>HIERARCHY</key>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../../../../build/UI/frontend-plugins/SceneSwitcher/advanced-scene-switcher.so</string>
<key>PATH_TYPE</key>
<integer>3</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>
<key>PATH</key>
<string>bin</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>advanced-scene-switcher</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>plugins</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>obs-studio</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>2</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Application Support</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Automator</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Documentation</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Extensions</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Filesystems</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Frameworks</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Input Methods</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Internet Plug-Ins</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>LaunchAgents</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>LaunchDaemons</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>PreferencePanes</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Preferences</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Printers</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>PrivilegedHelperTools</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>1005</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>QuickLook</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>QuickTime</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Screen Savers</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Scripts</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Services</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Widgets</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Library</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>Shared</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>1023</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Users</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>Applications</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>509</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>0</integer>
<key>PATH</key>
<string>/</string>
<key>PATH_TYPE</key>
<integer>0</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>1</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<key>PAYLOAD_TYPE</key>
<integer>0</integer>
<key>PRESERVE_EXTENDED_ATTRIBUTES</key>
<false/>
<key>SHOW_INVISIBLE</key>
<false/>
<key>SPLIT_FORKS</key>
<true/>
<key>TREAT_MISSING_FILES_AS_WARNING</key>
<false/>
<key>VERSION</key>
<integer>5</integer>
</dict>
<key>PACKAGE_SCRIPTS</key>
<dict>
<key>POSTINSTALL_PATH</key>
<dict>
<key>PATH_TYPE</key>
<integer>0</integer>
</dict>
<key>PREINSTALL_PATH</key>
<dict>
<key>PATH_TYPE</key>
<integer>0</integer>
</dict>
<key>RESOURCES</key>
<array/>
</dict>
<key>PACKAGE_SETTINGS</key>
<dict>
<key>AUTHENTICATION</key>
<integer>1</integer>
<key>CONCLUSION_ACTION</key>
<integer>0</integer>
<key>FOLLOW_SYMBOLIC_LINKS</key>
<false/>
<key>IDENTIFIER</key>
<string>com.warmuptill.SceneSwitcher</string>
<key>LOCATION</key>
<integer>0</integer>
<key>NAME</key>
<string></string>
<key>OVERWRITE_PERMISSIONS</key>
<false/>
<key>PAYLOAD_SIZE</key>
<integer>-1</integer>
<key>REFERENCE_PATH</key>
<string></string>
<key>RELOCATABLE</key>
<false/>
<key>USE_HFS+_COMPRESSION</key>
<false/>
<key>VERSION</key>
<string>0.0.2</string>
</dict>
<key>PROJECT_COMMENTS</key>
<dict>
<key>NOTES</key>
<data>
PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M
IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv
c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l
cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7
IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250
ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp
dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u
dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD
b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE0MDQuMTMiPgo8c3R5bGUg
dHlwZT0idGV4dC9jc3MiPgo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5
Pgo8L2JvZHk+CjwvaHRtbD4K
</data>
</dict>
<key>PROJECT_SETTINGS</key>
<dict>
<key>BUILD_PATH</key>
<dict>
<key>PATH</key>
<string>../..</string>
<key>PATH_TYPE</key>
<integer>1</integer>
</dict>
<key>EXCLUDED_FILES</key>
<array>
<dict>
<key>PATTERNS_ARRAY</key>
<array>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.DS_Store</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
</array>
<key>PROTECTED</key>
<true/>
<key>PROXY_NAME</key>
<string>Remove .DS_Store files</string>
<key>PROXY_TOOLTIP</key>
<string>Remove ".DS_Store" files created by the Finder.</string>
<key>STATE</key>
<true/>
</dict>
<dict>
<key>PATTERNS_ARRAY</key>
<array>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.pbdevelopment</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
</array>
<key>PROTECTED</key>
<true/>
<key>PROXY_NAME</key>
<string>Remove .pbdevelopment files</string>
<key>PROXY_TOOLTIP</key>
<string>Remove ".pbdevelopment" files created by ProjectBuilder or Xcode.</string>
<key>STATE</key>
<true/>
</dict>
<dict>
<key>PATTERNS_ARRAY</key>
<array>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>CVS</string>
<key>TYPE</key>
<integer>1</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.cvsignore</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.cvspass</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.svn</string>
<key>TYPE</key>
<integer>1</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.git</string>
<key>TYPE</key>
<integer>1</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>.gitignore</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
</array>
<key>PROTECTED</key>
<true/>
<key>PROXY_NAME</key>
<string>Remove SCM metadata</string>
<key>PROXY_TOOLTIP</key>
<string>Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems.</string>
<key>STATE</key>
<true/>
</dict>
<dict>
<key>PATTERNS_ARRAY</key>
<array>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>classes.nib</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>designable.db</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>info.nib</string>
<key>TYPE</key>
<integer>0</integer>
</dict>
</array>
<key>PROTECTED</key>
<true/>
<key>PROXY_NAME</key>
<string>Optimize nib files</string>
<key>PROXY_TOOLTIP</key>
<string>Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles.</string>
<key>STATE</key>
<true/>
</dict>
<dict>
<key>PATTERNS_ARRAY</key>
<array>
<dict>
<key>REGULAR_EXPRESSION</key>
<false/>
<key>STRING</key>
<string>Resources Disabled</string>
<key>TYPE</key>
<integer>1</integer>
</dict>
</array>
<key>PROTECTED</key>
<true/>
<key>PROXY_NAME</key>
<string>Remove Resources Disabled folders</string>
<key>PROXY_TOOLTIP</key>
<string>Remove "Resources Disabled" folders.</string>
<key>STATE</key>
<true/>
</dict>
<dict>
<key>SEPARATOR</key>
<true/>
</dict>
</array>
<key>NAME</key>
<string>SceneSwitcher</string>
<key>PAYLOAD_ONLY</key>
<false/>
</dict>
</dict>
<key>TYPE</key>
<integer>1</integer>
<key>VERSION</key>
<integer>2</integer>
</dict>
</plist>

View File

@@ -1,77 +1,269 @@
project(advanced-scene-switcher)
if(APPLE)
find_library(COCOA Cocoa)
include_directories(${COCOA})
endif()
if(UNIX)
find_package(X11 REQUIRED)
link_libraries(${X11_LIBRARIES})
include_directories(${X11_INCLUDE_DIR})
endif()
set(advanced-scene-switcher_HEADERS
${advanced-scene-switcher_HEADERS}
advanced-scene-switcher.hpp
switcher-data-structs.hpp
utility.hpp
)
set(advanced-scene-switcher_SOURCES
${advanced-scene-switcher_SOURCES}
advanced-scene-switcher.cpp
advanced-scene-switcher-module.c
scene-transitions.cpp
screen-region-switch.cpp
priority.cpp
executable-switch.cpp
idle-switch.cpp
scene-round-trip.cpp
file-switch.cpp
window-title-switch.cpp
hotkey.cpp
general.cpp
pause-switch.cpp
)
set(advanced-scene-switcher_UI
${advanced-scene-switcher_UI}
forms/advanced-scene-switcher.ui
)
if(WIN32)
set(advanced-scene-switcher_PLATFORM_SOURCES
advanced-scene-switcher-win.cpp)
elseif(APPLE)
set(advanced-scene-switcher_PLATFORM_SOURCES
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
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}
)
target_link_libraries(advanced-scene-switcher
${advanced-scene-switcher_PLATFORM_LIBS}
obs-frontend-api
Qt5::Widgets
libobs)
install_obs_plugin_with_data(advanced-scene-switcher data)
cmake_minimum_required(VERSION 3.5)
project(advanced-scene-switcher)
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")
include(external/FindLibObs.cmake)
find_package(LibObs REQUIRED)
find_package(Qt5Core REQUIRED)
find_package(Qt5Widgets REQUIRED)
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} )
if(APPLE)
find_library(COCOA Cocoa)
find_package(Qt5MacExtras REQUIRED)
include_directories(${COCOA})
add_link_options("-v")
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
src/headers/curl-helper.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-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-generic.hpp
)
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/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-generic.cpp
src/curl-helper.cpp
src/volume-control.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})
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})
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}
)
include_directories(
"${LIBOBS_INCLUDE_DIRS}"
"${LIBOBS_FRONTEND_INCLUDE_DIR}"
${Qt5Core_INCLUDES}
${Qt5Widgets_INCLUDES}
${LIBCURL_INCLUDE_DIRS})
target_link_libraries(advanced-scene-switcher
${advanced-scene-switcher_PLATFORM_LIBS}
${LIBOBS_LIB}
${LIBOBS_FRONTEND_API_LIB}
Qt5::Core
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
# place later on.
file(GLOB ASS_TRANSLATION_FILES
"data/locale/*.ini"
)
# Windows
if(WIN32)
string(REGEX REPLACE "/RTC(su|[1su])" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
message("${CMAKE_CXX_FLAGS_RELEASE}")
find_package(w32-pthreads REQUIRED)
target_link_libraries(advanced-scene-switcher
w32-pthreads)
endif()
# OSX
if(APPLE)
set_target_properties(advanced-scene-switcher PROPERTIES PREFIX "")
endif()
# Linux
if(UNIX AND NOT APPLE)
set(CMAKE_C_FLAGS "-Wall -Wextra -Wvla -Wno-unused-function -Werror-implicit-function-declaration -Wno-missing-braces -Wno-missing-field-initializers ${CMAKE_C_FLAGS} -std=c99 -fno-strict-aliasing")
if(ARCH EQUAL 64)
set(ARCH_NAME "x86_64")
else()
set(ARCH_NAME "i686")
endif()
set_target_properties(advanced-scene-switcher PROPERTIES PREFIX "")
install(TARGETS advanced-scene-switcher
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/obs-plugins)
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
src/headers/curl-helper.hpp
src/headers/volume-control.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-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-generic.hpp
)
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/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-generic.cpp
src/curl-helper.cpp
src/volume-control.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}
)
target_link_libraries(advanced-scene-switcher
${advanced-scene-switcher_PLATFORM_LIBS}
obs-frontend-api
Qt5::Widgets
libobs)
install_obs_plugin(advanced-scene-switcher data)
endif()

114
README.md
View File

@@ -2,3 +2,117 @@
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
Add the "SceneSwitcher" source directory to your obs-studio source directory under obs-studio/UI/frontend-plugins/:
```
cd obs-studio/UI/frontend-plugins/
git clone https://github.com/WarmUpTill/SceneSwitcher.git
```
Then modify the obs-studio/UI/frontend-plugins/CMakeLists.txt file and add an entry for the scene switcher:
add_subdirectory(SceneSwitcher)
## Compiling out of tree
### Prerequisites
You'll need CMake and a working development environment for OBS Studio installed
on your computer. Once you've done that, do the following:
```
git clone https://github.com/WarmUpTill/SceneSwitcher.git
cd SceneSwitcher
mkdir build && cd build
```
### Windows
In cmake-gui, you'll have to set these CMake variables :
- **BUILD_OUT_OF_TREE** (bool) : true
- **LIBOBS_INCLUDE_DIR** (path) : location of the libobs subfolder in the source
code of OBS Studio, located at [source_directory]/libobs/.
- **LIBOBS_LIB** (filepath) : location of the obs.dll file (usually set to
C:\Program Files\obs-studio\bin\64bit\obs.dll)
Assuming that you installed Qt via the regular Qt App way:
- **Qt5Core_DIR** (path) : C:/Qt/5.10.1/msvc2017_64/lib/cmake/Qt5Core
- **Qt5Gui_DIR** (path): C:/Qt/5.10.1/msvc2017_64/lib/cmake/Qt5Gui
- **Qt5Widgets_DIR** (path) : C:/Qt/5.10.1/msvc2017_64/lib/cmake/Qt5Widgets
- **LIBOBS_FRONTEND_API_LIB** (filepath) : location of the libobs-frontend-api.dll
file, usually C:/Program Files/obs-studio/bin/64bit/obs-frontend-api.dll
(usually in the same place as LIBOBS_LIB)
- **LIBOBS_FRONTEND_INCLUDE_DIR** (path) : location of the obs-frontend-api
subfolder in the source code of OBS Studio, located at [source_directory]/UI/obs-frontend-api.
Just keep hitting configure until all the vars are filled out. Then hit generate.
### Linux
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)>"
-DCMAKE_INSTALL_PREFIX=/usr ..
make -j4
sudo make install
```
For cmake-gui you'll have to set the following variables:
- **BUILD_OUT_OF_TREE** (bool) : true
- **LIBOBS_INCLUDE_DIR** (path) : location of the libobs subfolder in the source
code of OBS Studio, located at [source_directory]/libobs/.
- **LIBOBS_LIB** (filepath) : location of the libobs.so file (usually CMake finds
this, but if not it'll usually be in /usr/lib/libobs.so)
Assuming that you installed Qt via your system package manager, it should be
found automatically. If not, then usually you'll find it in something like:
- **Qt5Core_DIR** (path) : /usr/lib64/cmake/Qt5Core
- **Qt5Gui_DIR** (path): /usr/lib64/cmake/Qt5Gui
- **Qt5Widgets_DIR** (path) : /usr/lib64/cmake/Qt5Widgets
- **LIBOBS_FRONTEND_API_LIB** (filepath) : location of the libobs-frontend-api.so
file (usually in the same place as LIBOBS_LIB)
- **LIBOBS_FRONTEND_INCLUDE_DIR** (path) : location of the obs-frontend-api
subfolder in the source code of OBS Studio, located at
[source_directory]/UI/obs-frontend-api.
Just keep hitting configure until all the vars are filled out. Then hit generate.
Then open a terminal in the build folder and type:
```
make -j4
sudo make install
```
NOTE: The Linux version of this plugin is dependent on libXScrnSaver.
### OS X
In cmake-gui, you'll have to set these CMake variables :
- **BUILD_OUT_OF_TREE** (bool) : true
- **LIBOBS_INCLUDE_DIR** (path) : location of the libobs subfolder in the source
code of OBS Studio, located at [source_directory]/libobs/.
- **LIBOBS_LIB** (filepath) : location of the libobs.0.dylib file (usually
in /Applications/OBS.app/Contents/Resources/bin/libobs.0.dylib)
Assuming that you installed Qt via the regular Qt App way:
- **Qt5Core_DIR** (path) : Usually /Applications/Qt/5.10.1/clang_64/lib/cmake/Qt5Core
- **Qt5Widgets_DIR** (path) : Usually /Applications/Qt/5.10.1/clang_64/lib/cmake/Qt5Widgets
- **Qt5MacExtras_DIR** (path) : Usually /Applications/Qt/5.10.1/clang_64/lib/cmake/Qt5MacExtras
- **LIBOBS_FRONTEND_API_LIB** (filepath) : location of the libobs-frontend-api.0.dylib
file (usually in usually in /Applications/OBS.app/Contents/Resources/bin/libobs-frontend-api.0.dylib)
- **LIBOBS_FRONTEND_INCLUDE_DIR** (path) : location of the obs-frontend-api subfolder
in the source code of OBS Studio, located at [source_directory]/UI/obs-frontend-api.
Just keep hitting configure until all the vars are filled out. Then hit generate.
Open xcode (or a terminal, depending on the build type you chose), build and copy
the advanced-scene-switcher.so file to /Applications/OBS.app/Contents/PlugIns
Note that you might have to adjust the library search paths using the install_name_tool if you want the plugin to run on machines other than your build machine:
```
install_name_tool -change @rpath/libobs-frontend-api.dylib @executable_path/../Frameworks/libobs-frontend-api.dylib UI/frontend-plugins/SceneSwitcher/advanced-scene-switcher.so
install_name_tool -change @rpath/libobs.0.dylib @executable_path/../Frameworks/libobs.0.dylib UI/frontend-plugins/SceneSwitcher/advanced-scene-switcher.so
install_name_tool -change /usr/local/opt/qt5/lib/QtWidgets.framework/Versions/5/QtWidgets @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets UI/frontend-plugins/SceneSwitcher/advanced-scene-switcher.so
install_name_tool -change /usr/local/opt/qt5/lib/QtGui.framework/Versions/5/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui UI/frontend-plugins/SceneSwitcher/advanced-scene-switcher.so
install_name_tool -change /usr/local/opt/qt5/lib/QtCore.framework/Versions/5/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore UI/frontend-plugins/SceneSwitcher/advanced-scene-switcher.so
```

View File

@@ -1,322 +0,0 @@
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/extensions/scrnsaver.h>
#undef Bool
#undef CursorShape
#undef Expose
#undef KeyPress
#undef KeyRelease
#undef FocusIn
#undef FocusOut
#undef FontChange
#undef None
#undef Status
#undef Unsorted
#include <util/platform.h>
#include "advanced-scene-switcher.hpp"
using namespace std;
static Display* xdisplay = 0;
Display *disp()
{
if (!xdisplay)
xdisplay = XOpenDisplay(NULL);
return xdisplay;
}
void cleanupDisplay()
{
if (!xdisplay)
return;
XCloseDisplay(xdisplay);
xdisplay = 0;
}
static bool ewmhIsSupported()
{
Display *display = disp();
Atom netSupportingWmCheck = XInternAtom(display,
"_NET_SUPPORTING_WM_CHECK", true);
Atom actualType;
int format = 0;
unsigned long num = 0, bytes = 0;
unsigned char *data = NULL;
Window ewmh_window = 0;
int status = XGetWindowProperty(
display,
DefaultRootWindow(display),
netSupportingWmCheck,
0L,
1L,
false,
XA_WINDOW,
&actualType,
&format,
&num,
&bytes,
&data);
if (status == Success) {
if (num > 0) {
ewmh_window = ((Window*)data)[0];
}
if (data) {
XFree(data);
data = NULL;
}
}
if (ewmh_window) {
status = XGetWindowProperty(
display,
ewmh_window,
netSupportingWmCheck,
0L,
1L,
false,
XA_WINDOW,
&actualType,
&format,
&num,
&bytes,
&data);
if (status != Success || num == 0 ||
ewmh_window != ((Window*)data)[0]) {
ewmh_window = 0;
}
if (status == Success && data) {
XFree(data);
}
}
return ewmh_window != 0;
}
static std::vector<Window> getTopLevelWindows()
{
std::vector<Window> res;
res.resize(0);
if (!ewmhIsSupported()) {
return res;
}
Atom netClList = XInternAtom(disp(), "_NET_CLIENT_LIST", true);
Atom actualType;
int format;
unsigned long num, bytes;
Window* data = 0;
for (int i = 0; i < ScreenCount(disp()); ++i) {
Window rootWin = RootWindow(disp(), i);
int status = XGetWindowProperty(
disp(),
rootWin,
netClList,
0L,
~0L,
false,
AnyPropertyType,
&actualType,
&format,
&num,
&bytes,
(uint8_t**)&data);
if (status != Success) {
continue;
}
for (unsigned long i = 0; i < num; ++i)
res.emplace_back(data[i]);
XFree(data);
}
return res;
}
static std::string GetWindowTitle(size_t i)
{
Window w = getTopLevelWindows().at(i);
std::string windowTitle;
char* name;
int status = XFetchName(disp(), w, &name);
if (status >= Success && name != nullptr)
{
std::string str(name);
windowTitle = str;
}
XFree(name);
return windowTitle;
}
void GetWindowList(vector<string> &windows)
{
windows.resize(0);
for (size_t i = 0; i < getTopLevelWindows().size(); ++i){
if (GetWindowTitle(i) != "")
windows.emplace_back(GetWindowTitle(i));
}
}
void GetCurrentWindowTitle(string &title)
{
if (!ewmhIsSupported()) {
return;
}
Atom active = XInternAtom(disp(), "_NET_ACTIVE_WINDOW", true);
Atom actualType;
int format;
unsigned long num, bytes;
Window* data = 0;
char* name;
Window rootWin = RootWindow(disp(), 0);
XGetWindowProperty(
disp(),
rootWin,
active,
0L,
~0L,
false,
AnyPropertyType,
&actualType,
&format,
&num,
&bytes,
(uint8_t**)&data);
int status = XFetchName(disp(), data[0], &name);
if (status >= Success && name != nullptr) {
std::string str(name);
title = str;
}
XFree(name);
}
pair<int, int> getCursorPos()
{
pair<int, int> pos(0, 0);
Display *dpy;
Window root;
Window ret_root;
Window ret_child;
int root_x;
int root_y;
int win_x;
int win_y;
unsigned int mask;
dpy = XOpenDisplay(NULL);
root = XDefaultRootWindow(dpy);
if(XQueryPointer(dpy, root, &ret_root, &ret_child, &root_x, &root_y,
&win_x, &win_y, &mask))
{
pos = pair<int, int> (root_x,root_y);
}
return pos;
}
bool isFullscreen()
{
if (!ewmhIsSupported()) {
return false;
}
Atom active = XInternAtom(disp(), "_NET_ACTIVE_WINDOW", true);
Atom actualType;
int format;
unsigned long num, bytes;
Window* data = 0;
Window rootWin = RootWindow(disp(), 0);
XGetWindowProperty(
disp(),
rootWin,
active,
0L,
~0L,
false,
AnyPropertyType,
&actualType,
&format,
&num,
&bytes,
(uint8_t**)&data);
XWindowAttributes window_attributes_return;
XWindowAttributes screen_attributes_return;
XGetWindowAttributes(disp(), rootWin, &screen_attributes_return);
XGetWindowAttributes(disp(), data[0], &window_attributes_return);
//menu bar is always 24 pixels in height
return (window_attributes_return.width >= screen_attributes_return.width &&
window_attributes_return.height + 24 >= screen_attributes_return.height) ? true : false;
}
//exe switch is not quite what is expected but it works for now
void GetProcessList(QStringList &processes)
{
processes.clear();
for (size_t i = 0; i < getTopLevelWindows().size(); ++i){
string s = GetWindowTitle(i);
if (s != "")
processes << QString::fromStdString(s);
}
}
bool isInFocus(const QString &exeToCheck)
{
string curWindow;
GetCurrentWindowTitle(curWindow);
return (QString::compare(
QString::fromStdString(curWindow),
exeToCheck,
Qt::CaseInsensitive) == 0) ? true : false;
}
int secondsSinceLastInput()
{
time_t idle_time;
static XScreenSaverInfo *mit_info;
Display *display;
int screen;
mit_info = XScreenSaverAllocInfo();
if((display=XOpenDisplay(NULL)) == NULL)
{
return(-1);
}
screen = DefaultScreen(display);
XScreenSaverQueryInfo(display, RootWindow(display,screen), mit_info);
idle_time = (mit_info->idle) / 1000;
XFree(mit_info);
XCloseDisplay(display);
return idle_time;
}

View File

@@ -1,146 +0,0 @@
#import <AppKit/AppKit.h>
#import <CoreFoundation/CoreFoundation.h>
#import <CoreGraphics/CGEvent.h>
#import <Carbon/Carbon.h>
#include <Carbon/Carbon.h>
#include <util/platform.h>
#include "advanced-scene-switcher.hpp"
void GetWindowList(vector<string> &windows)
{
windows.resize(0);
@autoreleasepool {
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
NSArray *array = [ws runningApplications];
for (NSRunningApplication *app in array) {
NSString *name = app.localizedName;
if (!name)
continue;
const char *str = name.UTF8String;
if (str && *str)
windows.emplace_back(str);
}
}
}
void GetCurrentWindowTitle(string &title)
{
title.resize(0);
@autoreleasepool {
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
NSRunningApplication *app = [ws frontmostApplication];
if (app) {
NSString *name = app.localizedName;
if (!name)
return;
const char *str = name.UTF8String;
if (str && *str)
title = str;
}
}
}
pair<int, int> getCursorPos() {
pair<int, int> pos(0, 0);
CGEventRef event = CGEventCreate(NULL);
CGPoint cursorPos = CGEventGetLocation(event);
CFRelease(event);
pos.first = cursorPos.x;
pos.second = cursorPos.y;
return pos;
}
bool isFullscreen() {
@autoreleasepool {
AXValueRef temp;
CGSize windowSize;
CGPoint windowPosition;
AXUIElementRef frontMostApp;
AXUIElementRef frontMostWindow;
pid_t pid;
ProcessSerialNumber psn;
@try {
GetFrontProcess(&psn);
GetProcessPID(&psn, &pid);
frontMostApp = AXUIElementCreateApplication(pid);
AXUIElementCopyAttributeValue(
frontMostApp, kAXFocusedWindowAttribute, (CFTypeRef *)&frontMostWindow);
// Get the window size and position
AXUIElementCopyAttributeValue(
frontMostWindow, kAXSizeAttribute, (CFTypeRef *)&temp);
AXValueGetValue(temp, kAXValueTypeCGSize, &windowSize);
CFRelease(temp);
AXUIElementCopyAttributeValue(
frontMostWindow, kAXPositionAttribute, (CFTypeRef *)&temp);
AXValueGetValue(temp, kAXValueTypeCGPoint, &windowPosition);
CFRelease(temp);
CGRect screenBound = CGDisplayBounds(CGMainDisplayID());
CGSize screenSize = screenBound.size;
if((windowSize.width == screenSize.width) && (windowSize.height == screenSize.height) &&
(windowPosition.x == 0) && (windowPosition.y == 0))
return true;
}
@catch (...) {
// deal with the exception
}
@catch (NSException *exception) {
// deal with the exception
}
}
return false;
}
int secondsSinceLastInput()
{
double time = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGAnyInputEventType) + 0.5;
return (int) time;
}
void GetProcessList(QStringList& list)
{
list.clear();
@autoreleasepool {
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
NSArray *array = [ws runningApplications];
for (NSRunningApplication *app in array) {
NSString *name = app.localizedName;
if (!name)
continue;
const char *str = name.UTF8String;
if (str && *str)
list << (str);
}
}
}
bool isInFocus(QString const& appQName)
{
QByteArray ba = appQName.toLocal8Bit();
const char * appName = ba.data();
@autoreleasepool {
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
NSRunningApplication *app = [ws frontmostApplication];
if (app) {
NSString *name = app.localizedName;
if (!name)
return false;
const char *str = name.UTF8String;
return (str && *str && strcmp(appName,str) == 0 )? true : false;
}
}
return false;
}

View File

@@ -1,167 +0,0 @@
#include <windows.h>
#include <util/platform.h>
#include "advanced-scene-switcher.hpp"
#include <TlHelp32.h>
#include <Psapi.h>
using namespace std;
static bool GetWindowTitle(HWND window, string& title)
{
size_t len = (size_t)GetWindowTextLengthW(window);
wstring wtitle;
wtitle.resize(len);
if (!GetWindowTextW(window, &wtitle[0], (int)len + 1))
return false;
len = os_wcs_to_utf8(wtitle.c_str(), 0, nullptr, 0);
title.resize(len);
os_wcs_to_utf8(wtitle.c_str(), 0, &title[0], len + 1);
return true;
}
static bool WindowValid(HWND window)
{
LONG_PTR styles, ex_styles;
RECT rect;
DWORD id;
if (!IsWindowVisible(window))
return false;
GetWindowThreadProcessId(window, &id);
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)
return false;
return true;
}
void GetWindowList(vector<string>& windows)
{
HWND window = GetWindow(GetDesktopWindow(), GW_CHILD);
while (window)
{
string title;
if (WindowValid(window) && GetWindowTitle(window, title))
windows.emplace_back(title);
window = GetNextWindow(window, GW_HWNDNEXT);
}
}
void GetCurrentWindowTitle(string& title)
{
HWND window = GetForegroundWindow();
DWORD id;
GetWindowThreadProcessId(window, &id);
/*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 (id == GetCurrentProcessId()) {
title = "";
return;
}
GetWindowTitle(window, title);
}
pair<int, int> getCursorPos()
{
pair<int, int> pos(0, 0);
POINT cursorPos;
if (GetPhysicalCursorPos(&cursorPos))
{
pos.first = cursorPos.x;
pos.second = cursorPos.y;
}
return pos;
}
bool isFullscreen()
{
RECT appBounds;
RECT rc;
GetWindowRect(GetDesktopWindow(), &rc);
HWND hwnd = GetForegroundWindow();
if (hwnd != GetDesktopWindow() && hwnd != GetShellWindow())
{
GetWindowRect(hwnd, &appBounds);
if (rc.bottom == appBounds.bottom && rc.top == appBounds.top && rc.left == appBounds.left
&& rc.right == appBounds.right)
{
return true;
}
}
return false;
}
void GetProcessList(QStringList &processes) {
HANDLE procSnapshot;
PROCESSENTRY32 procEntry;
procSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (procSnapshot == INVALID_HANDLE_VALUE) return;
procEntry.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(procSnapshot, &procEntry)) {
CloseHandle(procSnapshot);
return;
}
do {
QString tempexe = QString::fromWCharArray(procEntry.szExeFile);
if (tempexe == "System") continue;
if (tempexe == "[System Process]") continue;
if (processes.contains(tempexe)) continue;
processes.append(tempexe);
} while (Process32Next(procSnapshot, &procEntry));
CloseHandle(procSnapshot);
}
bool isInFocus(const QString &exeToCheck) {
// only checks if the current foreground window is from the same executable,
// may return true for incorrent not meant windows from a program
HWND foregroundWindow = GetForegroundWindow();
DWORD processId = 0;
GetWindowThreadProcessId(foregroundWindow, &processId);
HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
if (process == NULL) return false;
WCHAR executablePath[600];
GetModuleFileNameEx(process, 0, executablePath, 600);
CloseHandle(process);
return exeToCheck == QString::fromWCharArray(executablePath).split(QRegExp("(/|\\\\)")).back();
}
int getLastInputTime()
{
LASTINPUTINFO lastInputInfo;
lastInputInfo.cbSize = sizeof(LASTINPUTINFO);
if (GetLastInputInfo(&lastInputInfo))
return lastInputInfo.dwTime;
return 0;
}
int getTime()
{
return GetTickCount();
}
int secondsSinceLastInput()
{
return (getTime() - getLastInputTime()) / 1000;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,123 +0,0 @@
#pragma once
#include <QDialog>
#include <memory>
#include <vector>
#include <string>
#include "ui_advanced-scene-switcher.h"
#include "switcher-data-structs.hpp"
struct obs_weak_source;
typedef struct obs_weak_source obs_weak_source_t;
class QCloseEvent;
class SceneSwitcher : public QDialog {
Q_OBJECT
public:
std::unique_ptr<Ui_SceneSwitcher> ui;
bool loading = true;
SceneSwitcher(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);
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);
void UpdateNonMatchingScene(const QString &name);
void UpdateAutoStopScene(const QString &name);
void UpdateIdleDataTransition(const QString& name);
void UpdateIdleDataScene(const QString& name);
public slots:
void on_switches_currentRowChanged(int idx);
void on_add_clicked();
void on_remove_clicked();
void on_noMatchDontSwitch_clicked();
void on_noMatchSwitch_clicked();
void on_startAtLaunch_toggled(bool value);
void on_noMatchSwitchScene_currentTextChanged(const QString &text);
void on_checkInterval_valueChanged(int value);
void on_toggleStartButton_clicked();
void on_screenRegions_currentRowChanged(int idx);
void on_screenRegionAdd_clicked();
void on_screenRegionRemove_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_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_autoStopSceneCheckBox_stateChanged(int state);
void on_autoStopScenes_currentTextChanged(const QString &text);
void on_sceneTransitions_currentRowChanged(int idx);
void on_transitionsAdd_clicked();
void on_transitionsRemove_clicked();
void on_defaultTransitions_currentRowChanged(int idx);
void on_defaultTransitionsAdd_clicked();
void on_defaultTransitionsRemove_clicked();
void on_browseButton_clicked();
void on_readFileCheckBox_stateChanged(int state);
void on_readPathLineEdit_textChanged(const QString & text);
void on_writePathLineEdit_textChanged(const QString & text);
void on_browseButton_2_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_priorityUp_clicked();
void on_priorityDown_clicked();
void updateScreenRegionCursorPos();
void on_close_clicked();
};
void GetWindowList(std::vector<std::string> &windows);
void GetCurrentWindowTitle(std::string &title);
pair<int, int> getCursorPos();
bool isFullscreen();
int secondsSinceLastInput();
bool isInFocus(const QString &exeToCheck);
void GetProcessList(QStringList &processes);
struct SwitcherData;
extern SwitcherData* switcher;

View File

@@ -1,140 +0,0 @@
#include "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();
lock_guard<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_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)
{
lock_guard<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);
{
lock_guard<mutex> lock(switcher->m);
for (auto& s : switcher->executableSwitches)
{
if (s.mExe == exeName)
{
s.mScene = source;
s.mTransition = transition;
s.mInFocus = inFocus;
break;
}
}
}
ui->executables->sortItems();
}
}
void SceneSwitcher::on_executableRemove_clicked()
{
QListWidgetItem* item = ui->executables->currentItem();
if (!item)
return;
QString exe = item->data(Qt::UserRole).toString();
{
lock_guard<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)
{
QStringList runningProcesses;
GetProcessList(runningProcesses);
for (ExecutableSceneSwitch& s : executableSwitches)
{
if (runningProcesses.contains(s.mExe))
{
scene = s.mScene;
transition = s.mTransition;
match = !s.mInFocus || (s.mInFocus && isInFocus(s.mExe));
break;
}
}
}

107
external/FindLibObs.cmake vendored Normal file
View File

@@ -0,0 +1,107 @@
# This module can be copied and used by external plugins for OBS
#
# Once done these will be defined:
#
# LIBOBS_FOUND
# LIBOBS_INCLUDE_DIRS
# LIBOBS_LIBRARIES
find_package(PkgConfig QUIET)
if (PKG_CONFIG_FOUND)
pkg_check_modules(_OBS QUIET obs libobs)
endif()
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_lib_suffix 64)
else()
set(_lib_suffix 32)
endif()
if(DEFINED CMAKE_BUILD_TYPE)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(_build_type_base "debug")
else()
set(_build_type_base "release")
endif()
endif()
find_path(LIBOBS_INCLUDE_DIR
NAMES obs.h
HINTS
ENV obsPath${_lib_suffix}
ENV obsPath
${obsPath}
PATHS
/usr/include /usr/local/include /opt/local/include /sw/include
PATH_SUFFIXES
libobs
)
function(find_obs_lib base_name repo_build_path lib_name)
string(TOUPPER "${base_name}" base_name_u)
if(DEFINED _build_type_base)
set(_build_type_${repo_build_path} "${_build_type_base}/${repo_build_path}")
set(_build_type_${repo_build_path}${_lib_suffix} "${_build_type_base}${_lib_suffix}/${repo_build_path}")
endif()
find_library(${base_name_u}_LIB
NAMES ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name}
HINTS
ENV obsPath${_lib_suffix}
ENV obsPath
${obsPath}
${_${base_name_u}_LIBRARY_DIRS}
PATHS
/usr/lib /usr/local/lib /opt/local/lib /sw/lib
PATH_SUFFIXES
lib${_lib_suffix} lib
libs${_lib_suffix} libs
bin${_lib_suffix} bin
../lib${_lib_suffix} ../lib
../libs${_lib_suffix} ../libs
../bin${_lib_suffix} ../bin
# base repo non-msvc-specific search paths
${_build_type_${repo_build_path}}
${_build_type_${repo_build_path}${_lib_suffix}}
build/${repo_build_path}
build${_lib_suffix}/${repo_build_path}
# base repo msvc-specific search paths on windows
build${_lib_suffix}/${repo_build_path}/Debug
build${_lib_suffix}/${repo_build_path}/RelWithDebInfo
build/${repo_build_path}/Debug
build/${repo_build_path}/RelWithDebInfo
)
endfunction()
find_obs_lib(LIBOBS libobs obs)
if(MSVC)
find_obs_lib(W32_PTHREADS deps/w32-pthreads w32-pthreads)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Libobs DEFAULT_MSG LIBOBS_LIB LIBOBS_INCLUDE_DIR)
mark_as_advanced(LIBOBS_INCLUDE_DIR LIBOBS_LIB)
if(LIBOBS_FOUND)
if(MSVC)
if (NOT DEFINED W32_PTHREADS_LIB)
message(FATAL_ERROR "Could not find the w32-pthreads library" )
endif()
set(W32_PTHREADS_INCLUDE_DIR ${LIBOBS_INCLUDE_DIR}/../deps/w32-pthreads)
endif()
set(LIBOBS_INCLUDE_DIRS ${LIBOBS_INCLUDE_DIR} ${W32_PTHREADS_INCLUDE_DIR})
set(LIBOBS_LIBRARIES ${LIBOBS_LIB} ${W32_PTHREADS_LIB})
include(${LIBOBS_INCLUDE_DIR}/../cmake/external/ObsPluginHelpers.cmake)
# allows external plugins to easily use/share common dependencies that are often included with libobs (such as FFmpeg)
if(NOT DEFINED INCLUDED_LIBOBS_CMAKE_MODULES)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${LIBOBS_INCLUDE_DIR}/../cmake/Modules/")
set(INCLUDED_LIBOBS_CMAKE_MODULES true)
endif()
else()
message(FATAL_ERROR "Could not find the libobs library" )
endif()

163
external/ObsPluginHelpers.cmake vendored Normal file
View File

@@ -0,0 +1,163 @@
# Functions for generating external plugins
set(EXTERNAL_PLUGIN_OUTPUT_DIR "${CMAKE_BINARY_DIR}/rundir")
# Fix XCode includes to ignore warnings on system includes
function(target_include_directories_system _target)
if(XCODE)
foreach(_arg ${ARGN})
if("${_arg}" STREQUAL "PRIVATE" OR "${_arg}" STREQUAL "PUBLIC" OR "${_arg}" STREQUAL "INTERFACE")
set(_scope ${_arg})
else()
target_compile_options(${_target} ${_scope} -isystem${_arg})
endif()
endforeach()
else()
target_include_directories(${_target} SYSTEM ${_scope} ${ARGN})
endif()
endfunction()
function(install_external_plugin_data_internal target source_dir target_dir)
install(DIRECTORY ${source_dir}/
DESTINATION "${target}/${target_dir}"
USE_SOURCE_PERMISSIONS)
add_custom_command(TARGET ${target} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${CMAKE_CURRENT_SOURCE_DIR}/${source_dir}" "${EXTERNAL_PLUGIN_OUTPUT_DIR}/$<CONFIGURATION>/${target}/${target_dir}"
VERBATIM)
endfunction()
# Installs data
# 'target' is the destination target project being installed to
# 'data_loc' specifies the directory of the data
function(install_external_plugin_data target data_loc)
install_external_plugin_data_internal(${target} ${data_loc} "data")
endfunction()
# Installs data in an architecture-specific data directory on windows/linux (data/32bit or data/64bit). Does not apply for mac.
# 'target' is the destination target project being installed to
# 'data_loc' specifies the directory of the data being installed
function(install_external_plugin_arch_data target data_loc)
if(APPLE)
set(_bit_suffix "")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_bit_suffix "/64bit")
else()
set(_bit_suffix "/32bit")
endif()
install_external_plugin_data_internal(${target} ${data_loc} "data${_bit_suffix}")
endfunction()
# Installs data in the target's bin directory
# 'target' is the destination target project being installed to
# 'data_loc' specifies the directory of the data being installed
function(install_external_plugin_data_to_bin target data_loc)
if(APPLE)
set(_bit_suffix "")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_bit_suffix "/64bit")
else()
set(_bit_suffix "/32bit")
endif()
install_external_plugin_data_internal(${target} ${data_loc} "bin${_bit_suffix}")
endfunction()
# Installs an additional binary to a target
# 'target' is the destination target project being installed to
# 'additional_target' specifies the additional binary
function(install_external_plugin_additional target additional_target)
if(APPLE)
set(_bit_suffix "")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_bit_suffix "64bit/")
else()
set(_bit_suffix "32bit/")
endif()
set_target_properties(${additional_target} PROPERTIES
PREFIX "")
install(TARGETS ${additional_target}
LIBRARY DESTINATION "bin"
RUNTIME DESTINATION "bin")
add_custom_command(TARGET ${additional_target} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:${additional_target}>"
"${EXTERNAL_PLUGIN_OUTPUT_DIR}/$<CONFIGURATION>/${target}/bin/${_bit_suffix}$<TARGET_FILE_NAME:${additional_target}>"
VERBATIM)
endfunction()
# Installs the binary of the target
# 'target' is the target project being installed
function(install_external_plugin target)
install_external_plugin_additional(${target} ${target})
endfunction()
# Installs the binary and data of the target
# 'target' is the destination target project being installed to
function(install_external_plugin_with_data target data_loc)
install_external_plugin(${target})
install_external_plugin_data(${target} ${data_loc})
endfunction()
# Installs an additional binary to the data of a target
# 'target' is the destination target project being installed to
# 'additional_target' specifies the additional binary
function(install_external_plugin_bin_to_data target additional_target)
install(TARGETS ${additional_target}
LIBRARY DESTINATION "data"
RUNTIME DESTINATION "data")
add_custom_command(TARGET ${additional_target} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:${additional_target}>"
"${EXTERNAL_PLUGIN_OUTPUT_DIR}/$<CONFIGURATION>/${target}/data/$<TARGET_FILE_NAME:${additional_target}>"
VERBATIM)
endfunction()
# Installs an additional binary in an architecture-specific data directory on windows/linux (data/32bit or data/64bit). Does not apply for mac.
# 'target' is the destination target project being installed to
# 'additional_target' specifies the additional binary
function(install_external_plugin_bin_to_arch_data target additional_target)
if(APPLE)
set(_bit_suffix "")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_bit_suffix "/64bit")
else()
set(_bit_suffix "/32bit")
endif()
install(TARGETS ${additional_target}
LIBRARY DESTINATION "data${_bit_suffix}"
RUNTIME DESTINATION "data${_bit_suffix}")
add_custom_command(TARGET ${additional_target} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:${additional_target}>"
"${EXTERNAL_PLUGIN_OUTPUT_DIR}/$<CONFIGURATION>/${target}/data${_bit_suffix}/$<TARGET_FILE_NAME:${additional_target}>"
VERBATIM)
endfunction()
# Installs an additional file in an architecture-specific data directory on windows/linux (data/32bit or data/64bit). Does not apply for mac.
# 'target' is the destination target project being installed to
# 'additional_target' specifies the additional binary
function(install_external_plugin_data_file_to_arch_data target additional_target file_target)
if(APPLE)
set(_bit_suffix "")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_bit_suffix "/64bit")
else()
set(_bit_suffix "/32bit")
endif()
get_filename_component(file_target_name ${file_target} NAME)
install(TARGETS ${additional_target}
LIBRARY DESTINATION "data${_bit_suffix}"
RUNTIME DESTINATION "data${_bit_suffix}")
add_custom_command(TARGET ${additional_target} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"${file_target}"
"${EXTERNAL_PLUGIN_OUTPUT_DIR}/$<CONFIGURATION>/${target}/data${_bit_suffix}/${file_target_name}"
VERBATIM)
endfunction()

View File

@@ -1,113 +0,0 @@
#include <QFileDialog>
#include <QTextStream>
#include <obs.hpp>
#include "advanced-scene-switcher.hpp"
void SceneSwitcher::on_browseButton_clicked()
{
QString directory = QFileDialog::getOpenFileName(
this, tr("Select a file to write to ..."), QDir::currentPath(), tr("Text files (*.txt)"));
if (!directory.isEmpty())
ui->writePathLineEdit->setText(directory);
}
void SceneSwitcher::on_readFileCheckBox_stateChanged(int state)
{
if (loading)
return;
lock_guard<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;
lock_guard<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;
lock_guard<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 directory = QFileDialog::getOpenFileName(
this, tr("Select a file to read from ..."), QDir::currentPath(), tr("Text files (*.txt)"));
if (!directory.isEmpty())
ui->readPathLineEdit->setText(directory);
}
void SwitcherData::writeSceneInfoToFile()
{
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);
}
obs_weak_source_t* getNextTransition(obs_weak_source_t* scene1, obs_weak_source_t* scene2);
void SwitcherData::checkSwitchInfoFromFile(bool& match, OBSWeakSource& scene, OBSWeakSource& transition)
{
if (!fileIO.readEnabled)
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();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,95 +0,0 @@
#include "advanced-scene-switcher.hpp"
#include <obs-module.h>
void SceneSwitcher::on_close_clicked()
{
done(0);
}
void SceneSwitcher::on_startAtLaunch_toggled(bool value)
{
if (loading)
return;
lock_guard<mutex> lock(switcher->m);
switcher->startAtLaunch = value;
}
void SceneSwitcher::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);
switcher->nonMatchingScene = ws;
obs_weak_source_release(ws);
obs_source_release(scene);
}
void SceneSwitcher::on_noMatchDontSwitch_clicked()
{
if (loading)
return;
lock_guard<mutex> lock(switcher->m);
switcher->switchIfNotMatching = false;
}
void SceneSwitcher::on_noMatchSwitch_clicked()
{
if (loading)
return;
lock_guard<mutex> lock(switcher->m);
switcher->switchIfNotMatching = true;
UpdateNonMatchingScene(ui->noMatchSwitchScene->currentText());
}
void SceneSwitcher::on_noMatchSwitchScene_currentTextChanged(const QString& text)
{
if (loading)
return;
lock_guard<mutex> lock(switcher->m);
UpdateNonMatchingScene(text);
}
void SceneSwitcher::on_checkInterval_valueChanged(int value)
{
if (loading)
return;
lock_guard<mutex> lock(switcher->m);
switcher->interval = value;
}
void SceneSwitcher::SetStarted()
{
ui->toggleStartButton->setText(obs_module_text("Stop"));
ui->pluginRunningText->setText(obs_module_text("Active"));
}
void SceneSwitcher::SetStopped()
{
ui->toggleStartButton->setText(obs_module_text("Start"));
ui->pluginRunningText->setText(obs_module_text("Inactive"));
}
void SceneSwitcher::on_toggleStartButton_clicked()
{
if (switcher->th.joinable())
{
switcher->Stop();
SetStopped();
}
else
{
switcher->Start();
SetStarted();
}
}
void SceneSwitcher::closeEvent(QCloseEvent*)
{
obs_frontend_save();
}

View File

@@ -1,72 +0,0 @@
#include <obs-module.h>
#include "advanced-scene-switcher.hpp"
void startStopHotkeyFunc(void* data, obs_hotkey_id id, obs_hotkey_t* hotkey, bool pressed)
{
UNUSED_PARAMETER(data);
UNUSED_PARAMETER(hotkey);
if (pressed)
{
if (switcher->th.joinable())
switcher->Stop();
else
switcher->Start();
}
obs_data_array* hotkeyData = obs_hotkey_save(id);
if (hotkeyData != NULL)
{
char* path = obs_module_config_path("");
ofstream file;
file.open(string(path).append("hotkey.txt"), 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);
string temp = obs_data_get_json(data);
obs_data_release(data);
file << temp;
}
file.close();
}
bfree(path);
}
obs_data_array_release(hotkeyData);
}
string loadConfigFile(string filename)
{
ifstream settingsFile;
char* path = obs_module_config_path("");
string value;
settingsFile.open(string(path).append(filename));
if (settingsFile.is_open())
{
settingsFile.seekg(0, ios::end);
value.reserve(settingsFile.tellg());
settingsFile.seekg(0, ios::beg);
value.assign((istreambuf_iterator<char>(settingsFile)), istreambuf_iterator<char>());
settingsFile.close();
}
bfree(path);
return value;
}
void loadKeybinding(obs_hotkey_id hotkeyId)
{
string bindings = loadConfigFile("hotkey.txt");
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);
}
}

View File

@@ -1,210 +0,0 @@
#include "advanced-scene-switcher.hpp"
void SwitcherData::checkIdleSwitch(bool& match, OBSWeakSource& scene, OBSWeakSource& transition)
{
if (!idleData.idleEnable)
return;
string title;
bool ignoreIdle = false;
//lock.unlock();
GetCurrentWindowTitle(title);
//lock.lock();
for (string& window : ignoreIdleWindows)
{
if (window == title)
{
ignoreIdle = true;
break;
}
}
if (!ignoreIdle)
{
for (string& window : ignoreIdleWindows)
{
try
{
bool matches = regex_match(title, regex(window));
if (matches)
{
ignoreIdle = true;
break;
}
}
catch (const regex_error&)
{
}
}
}
if (!ignoreIdle && secondsSinceLastInput() > idleData.time)
{
scene = idleData.scene;
transition = idleData.transition;
match = true;
}
}
void SceneSwitcher::on_idleCheckBox_stateChanged(int state)
{
if (loading)
return;
lock_guard<mutex> lock(switcher->m);
if (!state)
{
ui->idleScenes->setDisabled(true);
ui->idleSpinBox->setDisabled(true);
ui->idleTransitions->setDisabled(true);
switcher->idleData.idleEnable = false;
}
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());
}
}
void SceneSwitcher::UpdateIdleDataTransition(const QString& name)
{
obs_weak_source_t* transition = GetWeakTransitionByQString(name);
switcher->idleData.transition = transition;
}
void SceneSwitcher::UpdateIdleDataScene(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->idleData.scene = ws;
obs_weak_source_release(ws);
obs_source_release(scene);
}
void SceneSwitcher::on_idleTransitions_currentTextChanged(const QString& text)
{
if (loading)
return;
lock_guard<mutex> lock(switcher->m);
UpdateIdleDataTransition(text);
}
void SceneSwitcher::on_idleScenes_currentTextChanged(const QString& text)
{
if (loading)
return;
lock_guard<mutex> lock(switcher->m);
UpdateIdleDataScene(text);
}
void SceneSwitcher::on_idleSpinBox_valueChanged(int i)
{
if (loading)
return;
lock_guard<mutex> lock(switcher->m);
switcher->idleData.time = i;
}
void SceneSwitcher::on_ignoreIdleWindows_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
return;
QListWidgetItem* item = ui->ignoreIdleWindows->item(idx);
QString window = item->data(Qt::UserRole).toString();
lock_guard<mutex> lock(switcher->m);
for (auto& w : switcher->ignoreIdleWindows)
{
if (window.compare(w.c_str()) == 0)
{
ui->ignoreIdleWindowsWindows->setCurrentText(w.c_str());
break;
}
}
}
void SceneSwitcher::on_ignoreIdleAdd_clicked()
{
QString windowName = ui->ignoreIdleWindowsWindows->currentText();
if (windowName.isEmpty())
return;
QVariant v = QVariant::fromValue(windowName);
QList<QListWidgetItem*> items = ui->ignoreIdleWindows->findItems(windowName, Qt::MatchExactly);
if (items.size() == 0)
{
QListWidgetItem* item = new QListWidgetItem(windowName, ui->ignoreIdleWindows);
item->setData(Qt::UserRole, v);
lock_guard<mutex> lock(switcher->m);
switcher->ignoreIdleWindows.emplace_back(windowName.toUtf8().constData());
ui->ignoreIdleWindows->sortItems();
}
}
void SceneSwitcher::on_ignoreIdleRemove_clicked()
{
QListWidgetItem* item = ui->ignoreIdleWindows->currentItem();
if (!item)
return;
QString windowName = item->data(Qt::UserRole).toString();
{
lock_guard<mutex> lock(switcher->m);
auto& windows = switcher->ignoreIdleWindows;
for (auto it = windows.begin(); it != windows.end(); ++it)
{
auto& s = *it;
if (s == windowName.toUtf8().constData())
{
windows.erase(it);
break;
}
}
}
delete item;
}
int SceneSwitcher::IgnoreIdleWindowsFindByData(const QString& window)
{
int count = ui->ignoreIdleWindows->count();
int idx = -1;
for (int i = 0; i < count; i++)
{
QListWidgetItem* item = ui->ignoreIdleWindows->item(i);
QString itemRegion = item->data(Qt::UserRole).toString();
if (itemRegion == window)
{
idx = i;
break;
}
}
return idx;
}

View File

@@ -1,242 +0,0 @@
#include "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);
lock_guard<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();
{
lock_guard<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);
lock_guard<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();
{
lock_guard<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();
lock_guard<mutex> lock(switcher->m);
for (auto& s : switcher->pauseScenesSwitches)
{
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();
lock_guard<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);
string title;
if (!pause)
{
//lock.unlock();
GetCurrentWindowTitle(title);
//lock.lock();
for (string& window : pauseWindowsSwitches)
{
if (window == title)
{
pause = true;
break;
}
}
}
if (!pause)
{
//lock.unlock();
GetCurrentWindowTitle(title);
//lock.lock();
for (string& window : pauseWindowsSwitches)
{
try
{
bool matches = regex_match(title, regex(window));
if (matches)
{
pause = true;
break;
}
}
catch (const regex_error&)
{
}
}
}
return pause;
}

View File

@@ -1,57 +0,0 @@
#include <algorithm>
#include "advanced-scene-switcher.hpp"
void SceneSwitcher::on_priorityUp_clicked()
{
int currentIndex = ui->priorityList->currentRow();
if (currentIndex != -1 && currentIndex != 0)
{
ui->priorityList->insertItem(currentIndex - 1 ,ui->priorityList->takeItem(currentIndex));
ui->priorityList->setCurrentRow(currentIndex -1);
lock_guard<mutex> lock(switcher->m);
iter_swap(switcher->functionNamesByPriority.begin() + currentIndex, switcher->functionNamesByPriority.begin() + currentIndex - 1);
}
}
void SceneSwitcher::on_priorityDown_clicked()
{
int currentIndex = ui->priorityList->currentRow();
if (currentIndex != -1 && currentIndex != ui->priorityList->count() - 1)
{
ui->priorityList->insertItem(currentIndex + 1, ui->priorityList->takeItem(currentIndex));
ui->priorityList->setCurrentRow(currentIndex + 1);
lock_guard<mutex> lock(switcher->m);
iter_swap(switcher->functionNamesByPriority.begin() + currentIndex, switcher->functionNamesByPriority.begin() + currentIndex + 1);
}
}
bool prioStrValid(string s)
{
if (s == WINDOW_TITLE_FUNC)
return true;
if (s == SCREEN_REGION_FUNC)
return true;
if (s == ROUND_TRIP_FUNC)
return true;
if (s == (IDLE_FUNC))
return true;
if (s == EXE_FUNC)
return true;
if (s == READ_FILE_FUNC)
return true;
return false;
}
bool SwitcherData::prioFuncsValid()
{
for (string f : functionNamesByPriority)
{
if (!prioStrValid(f))
return false;
}
return true;
}

View File

@@ -1,308 +0,0 @@
#include <QFileDialog>
#include <QTextStream>
#include <obs.hpp>
#include "advanced-scene-switcher.hpp"
obs_weak_source_t* getNextTransition(obs_weak_source_t* scene1, obs_weak_source_t* scene2);
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;
int delay = ui->sceneRoundTripSpinBox->value();
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);
lock_guard<mutex> lock(switcher->m);
switcher->sceneRoundTripSwitches.emplace_back(
source1, source2, transition, delay, text.toUtf8().constData());
}
else
{
QListWidgetItem* item = ui->sceneRoundTrips->item(idx);
item->setText(text);
{
lock_guard<mutex> lock(switcher->m);
for (auto& s : switcher->sceneRoundTripSwitches)
{
if (s.scene1 == source1 && s.scene2 == source2)
{
s.delay = delay;
s.transition = transition;
s.sceneRoundTripStr = text.toUtf8().constData();
break;
}
}
}
ui->sceneRoundTrips->sortItems();
}
}
void SceneSwitcher::on_sceneRoundTripRemove_clicked()
{
QListWidgetItem* item = ui->sceneRoundTrips->currentItem();
if (!item)
return;
string text = item->data(Qt::UserRole).toString().toUtf8().constData();
{
lock_guard<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_autoStopSceneCheckBox_stateChanged(int state)
{
if (loading)
return;
lock_guard<mutex> lock(switcher->m);
if (!state)
{
ui->autoStopScenes->setDisabled(true);
switcher->autoStopEnable = false;
}
else
{
ui->autoStopScenes->setDisabled(false);
switcher->autoStopEnable = true;
}
}
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 SceneSwitcher::on_autoStopScenes_currentTextChanged(const QString& text)
{
if (loading)
return;
lock_guard<mutex> lock(switcher->m);
UpdateAutoStopScene(text);
}
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())
{
QFile file(directory);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
return;
QTextStream out(&file);
for (SceneRoundTripSwitch s : switcher->sceneRoundTripSwitches)
{
out << QString::fromStdString(GetWeakSourceName(s.scene1)) << "\n";
out << QString::fromStdString(GetWeakSourceName(s.scene2)) << "\n";
out << s.delay << "\n";
out << QString::fromStdString(s.sceneRoundTripStr) << "\n";
out << QString::fromStdString(GetWeakSourceName(s.transition)) << "\n";
}
}
}
void SceneSwitcher::on_sceneRoundTripLoad_clicked()
{
lock_guard<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())
{
QFile file(directory);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return;
QTextStream in(&file);
vector<QString> lines;
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) && WeakSourceValid(scene2)
&& WeakSourceValid(transition))
{
newSceneRoundTripSwitch.emplace_back(SceneRoundTripSwitch(
GetWeakSourceByQString(lines[0]),
GetWeakSourceByQString(lines[1]),
GetWeakTransitionByQString(lines[4]),
lines[2].toInt(),
lines[3].toStdString()));
}
lines.clear();
}
}
//unvalid amount of lines in file or nothing valid read
if (lines.size() != 0 || newSceneRoundTripSwitch.size() == 0)
return;
switcher->sceneRoundTripSwitches.clear();
ui->sceneRoundTrips->clear();
switcher->sceneRoundTripSwitches = newSceneRoundTripSwitch;
for (SceneRoundTripSwitch s : switcher->sceneRoundTripSwitches)
{
QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(s.sceneRoundTripStr), ui->sceneRoundTrips);
item->setData(Qt::UserRole, QString::fromStdString(s.sceneRoundTripStr));
}
}
}
void SwitcherData::checkSceneRoundTrip(bool& match, OBSWeakSource& scene, OBSWeakSource& transition, unique_lock<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)
{
string s = obs_source_get_name(currentSource);
waitSceneName = s;
cv.wait_for(lock, chrono::milliseconds(dur));
}
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.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->data(Qt::UserRole).toString();
lock_guard<mutex> lock(switcher->m);
for (auto& s : switcher->sceneRoundTripSwitches)
{
if (sceneRoundTrip.compare(s.sceneRoundTripStr.c_str()) == 0)
{
string scene1 = GetWeakSourceName(s.scene1);
string scene2 = GetWeakSourceName(s.scene2);
string transitionName = GetWeakSourceName(s.transition);
int delay = s.delay;
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 + " -> wait for [0-9]* seconds -> .*");
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 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);
}

View File

@@ -1,283 +0,0 @@
#include "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);
lock_guard<mutex> lock(switcher->m);
switcher->sceneTransitions.emplace_back(
source1, source2, transition, text.toUtf8().constData());
}
else
{
QListWidgetItem* item = ui->sceneTransitions->item(idx);
item->setText(text);
{
lock_guard<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;
string text = item->data(Qt::UserRole).toString().toUtf8().constData();
{
lock_guard<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);
lock_guard<mutex> lock(switcher->m);
switcher->defaultSceneTransitions.emplace_back(
source, transition, text.toUtf8().constData());
}
else
{
QListWidgetItem* item = ui->defaultTransitions->item(idx);
item->setText(text);
{
lock_guard<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;
string text = item->data(Qt::UserRole).toString().toUtf8().constData();
{
lock_guard<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(unique_lock<mutex>& lock)
{
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);
//sleep planned but no fitting OBS event to measure transition time
obs_frontend_set_current_transition(transition);
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();
lock_guard<mutex> lock(switcher->m);
for (auto& s : switcher->sceneTransitions)
{
if (sceneTransition.compare(s.sceneTransitionStr.c_str()) == 0)
{
string scene1 = GetWeakSourceName(s.scene1);
string scene2 = GetWeakSourceName(s.scene2);
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();
lock_guard<mutex> lock(switcher->m);
for (auto& s : switcher->defaultSceneTransitions)
{
if (sceneTransition.compare(s.sceneTransitionStr.c_str()) == 0)
{
string scene = GetWeakSourceName(s.scene);
string transitionName = GetWeakSourceName(s.transition);
ui->defaultTransitionsScene->setCurrentText(scene.c_str());
ui->defaultTransitionsTransitions->setCurrentText(transitionName.c_str());
break;
}
}
}
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;
}

View File

@@ -1,165 +0,0 @@
#include "advanced-scene-switcher.hpp"
void SwitcherData::checkScreenRegionSwitch(bool& match, OBSWeakSource& scene, OBSWeakSource& transition)
{
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;
}
}
}
}
void SceneSwitcher::updateScreenRegionCursorPos()
{
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();
string regionStr = to_string(minX) + ", " + to_string(minY) + " x " + to_string(maxX) + ", "
+ 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);
lock_guard<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);
string curRegion = region.toUtf8().constData();
{
lock_guard<mutex> lock(switcher->m);
for (auto& s : switcher->screenRegionSwitches)
{
if (s.regionStr == curRegion)
{
s.scene = source;
s.transition = transition;
break;
}
}
}
ui->screenRegions->sortItems();
}
}
void SceneSwitcher::on_screenRegionRemove_clicked()
{
QListWidgetItem* item = ui->screenRegions->currentItem();
if (!item)
return;
string region = item->data(Qt::UserRole).toString().toUtf8().constData();
{
lock_guard<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();
lock_guard<mutex> lock(switcher->m);
for (auto& s : switcher->screenRegionSwitches)
{
if (region.compare(s.regionStr.c_str()) == 0)
{
string name = GetWeakSourceName(s.scene);
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;
}

View File

@@ -1,18 +1,17 @@
#include <obs-module.h>
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("frontend-tools", "en-US")
void InitSceneSwitcher();
void FreeSceneSwitcher();
bool obs_module_load(void)
{
InitSceneSwitcher();
return true;
}
void obs_module_unload(void)
{
FreeSceneSwitcher();
}
#include <obs-module.h>
OBS_DECLARE_MODULE()
void InitSceneSwitcher();
void FreeSceneSwitcher();
bool obs_module_load(void)
{
InitSceneSwitcher();
return true;
}
void obs_module_unload(void)
{
FreeSceneSwitcher();
}

View File

@@ -0,0 +1,593 @@
#include <QMainWindow>
#include <QAction>
#include <QtGui/qstandarditemmodel.h>
#include <QPropertyAnimation>
#include <QGraphicsColorizeEffect>
#include <obs-frontend-api.h>
#include <util/util.hpp>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/curl-helper.hpp"
SwitcherData *switcher = nullptr;
/********************************************************************************
* Create the Advanced Scene Switcher settings window
********************************************************************************/
AdvSceneSwitcher::AdvSceneSwitcher(QWidget *parent)
: QDialog(parent), ui(new Ui_AdvSceneSwitcher)
{
ui->setupUi(this);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->Prune();
loadUI();
}
void AdvSceneSwitcher::loadUI()
{
#if __APPLE__
setMinimumHeight(700);
#endif
setupGeneralTab();
setupTitleTab();
setupExecutableTab();
setupRegionTab();
setupPauseTab();
setupSequenceTab();
setupTransitionsTab();
setupIdleTab();
setupRandomTab();
setupMediaTab();
setupFileTab();
setupTimeTab();
setupAudioTab();
setTabOrder();
loading = false;
}
/********************************************************************************
* UI helpers
********************************************************************************/
void addSelectionEntry(QComboBox *sel, const char *description,
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);
firstItem->setSelectable(false);
firstItem->setEnabled(false);
}
void AdvSceneSwitcher::populateSceneSelection(QComboBox *sel, bool addPrevious,
bool addSelect)
{
if (addSelect)
addSelectionEntry(sel, "--select scene--",
"invalid entries will not be saved");
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);
}
void AdvSceneSwitcher::populateTransitionSelection(QComboBox *sel,
bool addSelect)
{
if (addSelect)
addSelectionEntry(sel, "--select transition--");
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, "--select window--");
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,
"Use \"OBS\" to specify OBS window\nUse \"Task Switching\"to specify ALT + TAB",
Qt::ToolTipRole);
#endif
}
void AdvSceneSwitcher::populateAudioSelection(QComboBox *sel, bool addSelect)
{
if (addSelect)
addSelectionEntry(sel, "--select audio source--",
"invalid entries will not be saved");
auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */
{
QComboBox *combo = reinterpret_cast<QComboBox *>(data);
uint32_t flags = obs_source_get_output_flags(source);
if ((flags & OBS_SOURCE_AUDIO) != 0) {
const char *name = obs_source_get_name(source);
combo->addItem(name);
}
return true;
};
obs_enum_sources(sourceEnum, sel);
}
void AdvSceneSwitcher::populateMediaSelection(QComboBox *sel, bool addSelect)
{
if (addSelect)
addSelectionEntry(sel, "--select media source--",
"invalid entries will not be saved");
auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */
{
QComboBox *combo = reinterpret_cast<QComboBox *>(data);
std::string sourceId = obs_source_get_id(source);
if (sourceId.compare("ffmpeg_source") == 0 ||
sourceId.compare("vlc_source") == 0) {
const char *name = obs_source_get_name(source);
combo->addItem(name);
}
return true;
};
obs_enum_sources(sourceEnum, sel);
}
void AdvSceneSwitcher::populateProcessSelection(QComboBox *sel, bool addSelect)
{
if (addSelect)
addSelectionEntry(sel, "--select process--");
QStringList processes;
GetProcessList(processes);
for (QString &process : processes)
sel->addItem(process);
}
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 backward 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) {
std::lock_guard<std::mutex> lock(switcher->m);
switcher->Prune();
obs_data_t *obj = obs_data_create();
switcher->saveWindowTitleSwitches(obj);
switcher->saveScreenRegionSwitches(obj);
switcher->savePauseSwitches(obj);
switcher->saveSceneSequenceSwitches(obj);
switcher->saveSceneTransitions(obj);
switcher->saveIdleSwitches(obj);
switcher->saveExecutableSwitches(obj);
switcher->saveRandomSwitches(obj);
switcher->saveFileSwitches(obj);
switcher->saveMediaSwitches(obj);
switcher->saveTimeSwitches(obj);
switcher->saveAudioSwitches(obj);
switcher->saveGeneralSettings(obj);
switcher->saveHotkeys(obj);
obs_data_set_obj(save_data, "advanced-scene-switcher", obj);
obs_data_release(obj);
} else {
switcher->m.lock();
obs_data_t *obj =
obs_data_get_obj(save_data, "advanced-scene-switcher");
if (!obj)
obj = obs_data_create();
switcher->loadWindowTitleSwitches(obj);
switcher->loadScreenRegionSwitches(obj);
switcher->loadPauseSwitches(obj);
switcher->loadSceneSequenceSwitches(obj);
switcher->loadSceneTransitions(obj);
switcher->loadIdleSwitches(obj);
switcher->loadExecutableSwitches(obj);
switcher->loadRandomSwitches(obj);
switcher->loadFileSwitches(obj);
switcher->loadMediaSwitches(obj);
switcher->loadTimeSwitches(obj);
switcher->loadAudioSwitches(obj);
switcher->loadGeneralSettings(obj);
switcher->loadHotkeys(obj);
obs_data_release(obj);
switcher->m.unlock();
// stop the scene switcher at least once
// to avoid issues with scene collection changes
bool start = !switcher->stop;
switcher->Stop();
if (start)
switcher->Start();
}
}
/********************************************************************************
* Main switcher thread
********************************************************************************/
void SwitcherData::Thread()
{
blog(LOG_INFO, "started");
int sleep = 0;
while (true) {
startLoop:
std::unique_lock<std::mutex> lock(m);
bool match = false;
OBSWeakSource scene;
OBSWeakSource transition;
std::chrono::milliseconds duration;
if (sleep > interval) {
duration = std::chrono::milliseconds(sleep);
if (verbose)
blog(LOG_INFO, "sleep for %d", sleep);
} else {
duration = std::chrono::milliseconds(interval);
if (verbose)
blog(LOG_INFO, "sleep for %d", interval);
}
sleep = 0;
switcher->Prune();
writeSceneInfoToFile();
//sleep for a bit
cv.wait_for(lock, duration);
if (switcher->stop) {
break;
}
setDefaultSceneTransitions();
if (autoStopEnable) {
autoStopStreamAndRecording();
}
if (autoStartEnable) {
autoStartStreamRecording();
}
if (checkPause()) {
continue;
}
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,
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;
case audio_func:
checkAudioSwitch(match, scene, transition);
break;
}
if (switcher->stop) {
goto endLoop;
}
if (match) {
break;
}
}
if (!match && switchIfNotMatching == SWITCH &&
nonMatchingScene) {
match = true;
scene = nonMatchingScene;
transition = nullptr;
}
if (!match && switchIfNotMatching == RANDOM_SWITCH) {
checkRandom(match, scene, transition, sleep);
}
if (match) {
switchScene(scene, transition,
tansitionOverrideOverride, lock);
}
}
endLoop:
blog(LOG_INFO, "stopped");
}
void switchScene(OBSWeakSource &scene, OBSWeakSource &transition,
bool &transitionOverrideOverride,
std::unique_lock<std::mutex> &lock)
{
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)
restoreTransitionOverride(source, td);
lock.lock();
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())) {
stop = false;
switcher->th = new SwitcherThread();
switcher->th->start(
(QThread::Priority)switcher->threadPriority);
}
}
void SwitcherData::Stop()
{
if (th && th->isRunning()) {
switcher->stop = true;
transitionCv.notify_one();
cv.notify_one();
th->wait();
delete th;
th = nullptr;
}
}
/********************************************************************************
* 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())
s->cv.notify_one();
//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;
}
//reset events only hanled on scene change
s->autoStartedRecently = false;
s->changedDefTransitionRecently = false;
}
void setLiveTime(SwitcherData *s)
{
s->liveTime = QDateTime::currentDateTime();
}
void resetLiveTime(SwitcherData *s)
{
s->liveTime = QDateTime();
}
static void OBSEvent(enum obs_frontend_event event, void *switcher)
{
switch (event) {
case OBS_FRONTEND_EVENT_EXIT:
FreeSceneSwitcher();
break;
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
handleSceneChange((SwitcherData *)switcher);
break;
case OBS_FRONTEND_EVENT_RECORDING_STARTED:
case OBS_FRONTEND_EVENT_STREAMING_STARTED:
setLiveTime((SwitcherData *)switcher);
break;
case OBS_FRONTEND_EVENT_RECORDING_STOPPED:
case OBS_FRONTEND_EVENT_STREAMING_STOPPED:
resetLiveTime((SwitcherData *)switcher);
break;
default:
break;
}
}
extern "C" void InitSceneSwitcher()
{
QAction *action = (QAction *)obs_frontend_add_tools_menu_qaction(
"Advanced Scene Switcher");
switcher = new SwitcherData;
if (loadCurl() && f_curl_init) {
switcher->curl = f_curl_init();
}
auto cb = []() {
QMainWindow *window =
(QMainWindow *)obs_frontend_get_main_window();
AdvSceneSwitcher ss(window);
ss.exec();
};
obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr);
obs_frontend_add_event_callback(OBSEvent, switcher);
action->connect(action, &QAction::triggered, cb);
}

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

@@ -0,0 +1,67 @@
#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;
}

719
src/general.cpp Normal file
View File

@@ -0,0 +1,719 @@
#include <QFileDialog>
#include <QMessageBox>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
QMetaObject::Connection inactivePluse;
void AdvSceneSwitcher::on_close_clicked()
{
done(0);
}
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);
switcher->nonMatchingScene = ws;
obs_weak_source_release(ws);
obs_source_release(scene);
}
void AdvSceneSwitcher::on_noMatchDontSwitch_clicked()
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
switcher->switchIfNotMatching = NO_SWITCH;
ui->noMatchSwitchScene->setEnabled(false);
ui->randomDisabledWarning->setVisible(true);
}
void AdvSceneSwitcher::on_noMatchSwitch_clicked()
{
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 AdvSceneSwitcher::on_noMatchRandomSwitch_clicked()
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
switcher->switchIfNotMatching = RANDOM_SWITCH;
ui->noMatchSwitchScene->setEnabled(false);
ui->randomDisabledWarning->setVisible(false);
}
void AdvSceneSwitcher::on_startupBehavior_currentIndexChanged(int index)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
switcher->startupBehavior = (StartupBehavior)index;
}
void AdvSceneSwitcher::on_noMatchSwitchScene_currentTextChanged(
const QString &text)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
UpdateNonMatchingScene(text);
}
void AdvSceneSwitcher::on_checkInterval_valueChanged(int value)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
switcher->interval = value;
}
void AdvSceneSwitcher::SetStarted()
{
ui->toggleStartButton->setText("Stop");
ui->pluginRunningText->setText("Active");
ui->pluginRunningText->disconnect(inactivePluse);
}
void AdvSceneSwitcher::SetStopped()
{
ui->toggleStartButton->setText("Start");
ui->pluginRunningText->setText("Inactive");
inactivePluse = PulseWidget(ui->pluginRunningText, QColor(Qt::red),
QColor(0, 0, 0, 0), "QLabel ");
}
void AdvSceneSwitcher::on_toggleStartButton_clicked()
{
if (switcher->th && switcher->th->isRunning()) {
switcher->Stop();
SetStopped();
} else {
switcher->Start();
SetStarted();
}
}
void AdvSceneSwitcher::closeEvent(QCloseEvent *)
{
obs_frontend_save();
}
void AdvSceneSwitcher::on_autoStopScenes_currentTextChanged(const QString &text)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
UpdateAutoStopScene(text);
}
void AdvSceneSwitcher::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 AdvSceneSwitcher::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 AdvSceneSwitcher::on_autoStartType_currentIndexChanged(int index)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
switcher->autoStartType = (AutoStartType)index;
}
void AdvSceneSwitcher::on_autoStartScenes_currentTextChanged(const QString &text)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
UpdateAutoStartScene(text);
}
void AdvSceneSwitcher::on_autoStartSceneCheckBox_stateChanged(int state)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
if (!state) {
ui->autoStartScenes->setDisabled(true);
ui->autoStartType->setDisabled(true);
switcher->autoStartEnable = false;
} else {
ui->autoStartScenes->setDisabled(false);
ui->autoStartType->setDisabled(false);
switcher->autoStartEnable = true;
if (!switcher->autoStartScene)
UpdateAutoStartScene(
ui->autoStartScenes->currentText());
}
}
void AdvSceneSwitcher::UpdateAutoStartScene(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->autoStartScene = ws;
obs_weak_source_release(ws);
obs_source_release(scene);
}
void SwitcherData::autoStartStreamRecording()
{
if (autoStartedRecently)
return;
obs_source_t *currentSource = obs_frontend_get_current_scene();
obs_weak_source_t *ws = obs_source_get_weak_source(currentSource);
if (ws && autoStartScene == ws) {
if ((switcher->autoStartType == STREAMING ||
switcher->autoStartType == RECORINDGSTREAMING) &&
!obs_frontend_streaming_active())
obs_frontend_streaming_start();
if ((switcher->autoStartType == RECORDING ||
switcher->autoStartType == RECORINDGSTREAMING) &&
!obs_frontend_recording_active())
obs_frontend_recording_start();
}
obs_source_release(currentSource);
obs_weak_source_release(ws);
autoStartedRecently = true;
}
void AdvSceneSwitcher::on_verboseLogging_stateChanged(int state)
{
if (loading)
return;
switcher->verbose = state;
}
void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
{
if (loading)
return;
switcher->disableHints = state;
}
void AdvSceneSwitcher::on_exportSettings_clicked()
{
QString directory = QFileDialog::getSaveFileName(
this, tr("Export Advanced Scene Switcher settings 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->saveWindowTitleSwitches(obj);
switcher->saveScreenRegionSwitches(obj);
switcher->savePauseSwitches(obj);
switcher->saveSceneSequenceSwitches(obj);
switcher->saveSceneTransitions(obj);
switcher->saveIdleSwitches(obj);
switcher->saveExecutableSwitches(obj);
switcher->saveRandomSwitches(obj);
switcher->saveFileSwitches(obj);
switcher->saveMediaSwitches(obj);
switcher->saveTimeSwitches(obj);
switcher->saveAudioSwitches(obj);
switcher->saveGeneralSettings(obj);
switcher->saveHotkeys(obj);
obs_data_save_json(obj, file.fileName().toUtf8().constData());
obs_data_release(obj);
}
void AdvSceneSwitcher::on_importSettings_clicked()
{
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())
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;
Msgbox.setText(
"Advanced Scene Switcher failed to import settings");
Msgbox.exec();
return;
}
switcher->loadWindowTitleSwitches(obj);
switcher->loadScreenRegionSwitches(obj);
switcher->loadPauseSwitches(obj);
switcher->loadSceneSequenceSwitches(obj);
switcher->loadSceneTransitions(obj);
switcher->loadIdleSwitches(obj);
switcher->loadExecutableSwitches(obj);
switcher->loadRandomSwitches(obj);
switcher->loadFileSwitches(obj);
switcher->loadMediaSwitches(obj);
switcher->loadTimeSwitches(obj);
switcher->loadAudioSwitches(obj);
switcher->loadGeneralSettings(obj);
switcher->loadHotkeys(obj);
obs_data_release(obj);
QMessageBox Msgbox;
Msgbox.setText(
"Advanced Scene Switcher settings imported successfully");
Msgbox.exec();
close();
}
int findTabIndex(QTabBar *bar, int pos)
{
int at = -1;
QString tabName = "";
switch (pos) {
case 0:
tabName = "General";
break;
case 1:
tabName = "Transition";
break;
case 2:
tabName = "Pause";
break;
case 3:
tabName = "Title";
break;
case 4:
tabName = "Executable";
break;
case 5:
tabName = "Region";
break;
case 6:
tabName = "Media";
break;
case 7:
tabName = "File";
break;
case 8:
tabName = "Random";
break;
case 9:
tabName = "Time";
break;
case 10:
tabName = "Idle";
break;
case 11:
tabName = "Sequence";
break;
case 12:
tabName = "Audio";
break;
}
for (int i = 0; i < bar->count(); ++i) {
if (bar->tabText(i).compare(tabName) == 0) {
at = i;
break;
}
}
if (at == -1)
blog(LOG_INFO, "failed to find tab %s",
tabName.toUtf8().constData());
return at;
}
void AdvSceneSwitcher::setTabOrder()
{
QTabBar *bar = ui->tabWidget->tabBar();
for (int i = 0; i < bar->count(); ++i) {
int curPos = findTabIndex(bar, switcher->tabOrder[i]);
if (i != curPos && curPos != -1)
bar->moveTab(curPos, i);
}
connect(bar, &QTabBar::tabMoved, this, &AdvSceneSwitcher::on_tabMoved);
}
void AdvSceneSwitcher::on_tabMoved(int from, int to)
{
if (loading)
return;
std::swap(switcher->tabOrder[from], switcher->tabOrder[to]);
}
void SwitcherData::saveGeneralSettings(obs_data_t *obj)
{
obs_data_set_int(obj, "interval", switcher->interval);
std::string nonMatchingSceneName =
GetWeakSourceName(switcher->nonMatchingScene);
obs_data_set_string(obj, "non_matching_scene",
nonMatchingSceneName.c_str());
obs_data_set_int(obj, "switch_if_not_matching",
switcher->switchIfNotMatching);
obs_data_set_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());
std::string autoStartSceneName =
GetWeakSourceName(switcher->autoStartScene);
obs_data_set_bool(obj, "autoStartEnable", switcher->autoStartEnable);
obs_data_set_int(obj, "autoStartType", switcher->autoStartType);
obs_data_set_string(obj, "autoStartSceneName",
autoStartSceneName.c_str());
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]);
obs_data_set_int(obj, "priority1",
switcher->functionNamesByPriority[1]);
obs_data_set_int(obj, "priority2",
switcher->functionNamesByPriority[2]);
obs_data_set_int(obj, "priority3",
switcher->functionNamesByPriority[3]);
obs_data_set_int(obj, "priority4",
switcher->functionNamesByPriority[4]);
obs_data_set_int(obj, "priority5",
switcher->functionNamesByPriority[5]);
obs_data_set_int(obj, "priority6",
switcher->functionNamesByPriority[6]);
obs_data_set_int(obj, "priority7",
switcher->functionNamesByPriority[7]);
obs_data_set_int(obj, "priority8",
switcher->functionNamesByPriority[8]);
obs_data_set_int(obj, "threadPriority", switcher->threadPriority);
obs_data_set_int(obj, "generalTabPos", switcher->tabOrder[0]);
obs_data_set_int(obj, "transitionTabPos", switcher->tabOrder[1]);
obs_data_set_int(obj, "pauseTabPos", switcher->tabOrder[2]);
obs_data_set_int(obj, "titleTabPos", switcher->tabOrder[3]);
obs_data_set_int(obj, "exeTabPos", switcher->tabOrder[4]);
obs_data_set_int(obj, "regionTabPos", switcher->tabOrder[5]);
obs_data_set_int(obj, "mediaTabPos", switcher->tabOrder[6]);
obs_data_set_int(obj, "fileTabPos", switcher->tabOrder[7]);
obs_data_set_int(obj, "randomTabPos", switcher->tabOrder[8]);
obs_data_set_int(obj, "timeTabPos", switcher->tabOrder[9]);
obs_data_set_int(obj, "idleTabPos", switcher->tabOrder[10]);
obs_data_set_int(obj, "sequenceTabPos", switcher->tabOrder[11]);
obs_data_set_int(obj, "audioTabPos", switcher->tabOrder[12]);
}
void SwitcherData::loadGeneralSettings(obs_data_t *obj)
{
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);
switcher->switchIfNotMatching =
(NoMatch)obs_data_get_int(obj, "switch_if_not_matching");
std::string nonMatchingScene =
obs_data_get_string(obj, "non_matching_scene");
switcher->nonMatchingScene =
GetWeakSourceByName(nonMatchingScene.c_str());
switcher->stop = !obs_data_get_bool(obj, "active");
switcher->startupBehavior =
(StartupBehavior)obs_data_get_int(obj, "startup_behavior");
if (switcher->startupBehavior == START)
switcher->stop = false;
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());
std::string autoStartScene =
obs_data_get_string(obj, "autoStartSceneName");
switcher->autoStartEnable = obs_data_get_bool(obj, "autoStartEnable");
switcher->autoStartType =
(AutoStartType)obs_data_get_int(obj, "autoStartType");
switcher->autoStartScene = GetWeakSourceByName(autoStartScene.c_str());
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, "priority8", default_priority_8);
switcher->functionNamesByPriority[0] =
(obs_data_get_int(obj, "priority0"));
switcher->functionNamesByPriority[1] =
(obs_data_get_int(obj, "priority1"));
switcher->functionNamesByPriority[2] =
(obs_data_get_int(obj, "priority2"));
switcher->functionNamesByPriority[3] =
(obs_data_get_int(obj, "priority3"));
switcher->functionNamesByPriority[4] =
(obs_data_get_int(obj, "priority4"));
switcher->functionNamesByPriority[5] =
(obs_data_get_int(obj, "priority5"));
switcher->functionNamesByPriority[6] =
(obs_data_get_int(obj, "priority6"));
switcher->functionNamesByPriority[7] =
(obs_data_get_int(obj, "priority7"));
switcher->functionNamesByPriority[8] =
(obs_data_get_int(obj, "priority8"));
if (!switcher->prioFuncsValid()) {
switcher->functionNamesByPriority[0] = (default_priority_0);
switcher->functionNamesByPriority[1] = (default_priority_1);
switcher->functionNamesByPriority[2] = (default_priority_2);
switcher->functionNamesByPriority[3] = (default_priority_3);
switcher->functionNamesByPriority[4] = (default_priority_4);
switcher->functionNamesByPriority[5] = (default_priority_5);
switcher->functionNamesByPriority[6] = (default_priority_6);
switcher->functionNamesByPriority[7] = (default_priority_7);
switcher->functionNamesByPriority[8] = (default_priority_8);
}
obs_data_set_default_int(obj, "threadPriority",
QThread::NormalPriority);
switcher->threadPriority = obs_data_get_int(obj, "threadPriority");
obs_data_set_default_int(obj, "generalTabPos", 0);
obs_data_set_default_int(obj, "transitionTabPos", 1);
obs_data_set_default_int(obj, "pauseTabPos", 2);
obs_data_set_default_int(obj, "titleTabPos", 3);
obs_data_set_default_int(obj, "exeTabPos", 4);
obs_data_set_default_int(obj, "regionTabPos", 5);
obs_data_set_default_int(obj, "mediaTabPos", 6);
obs_data_set_default_int(obj, "fileTabPos", 7);
obs_data_set_default_int(obj, "randomTabPos", 8);
obs_data_set_default_int(obj, "timeTabPos", 9);
obs_data_set_default_int(obj, "idleTabPos", 10);
obs_data_set_default_int(obj, "sequenceTabPos", 11);
obs_data_set_default_int(obj, "audioTabPos", 12);
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "generalTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "transitionTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "pauseTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "titleTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "exeTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "regionTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "mediaTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "fileTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "randomTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "timeTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "idleTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "sequenceTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "audioTabPos")));
}
void AdvSceneSwitcher::setupGeneralTab()
{
populateSceneSelection(ui->noMatchSwitchScene, false);
populateSceneSelection(ui->autoStopScenes, false);
populateSceneSelection(ui->autoStartScenes, false);
if (switcher->switchIfNotMatching == SWITCH) {
ui->noMatchSwitch->setChecked(true);
ui->noMatchSwitchScene->setEnabled(true);
} else if (switcher->switchIfNotMatching == NO_SWITCH) {
ui->noMatchDontSwitch->setChecked(true);
ui->noMatchSwitchScene->setEnabled(false);
} else {
ui->noMatchRandomSwitch->setChecked(true);
ui->noMatchSwitchScene->setEnabled(false);
}
ui->noMatchSwitchScene->setCurrentText(
GetWeakSourceName(switcher->nonMatchingScene).c_str());
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->autoStartType->addItem("Recording");
ui->autoStartType->addItem("Streaming");
ui->autoStartType->addItem("Recording and Streaming");
ui->autoStartSceneCheckBox->setChecked(switcher->autoStartEnable);
ui->autoStartScenes->setCurrentText(
GetWeakSourceName(switcher->autoStartScene).c_str());
ui->autoStartType->setCurrentIndex(switcher->autoStartType);
if (ui->autoStartSceneCheckBox->checkState()) {
ui->autoStartScenes->setDisabled(false);
ui->autoStartType->setDisabled(false);
} else {
ui->autoStartScenes->setDisabled(true);
ui->autoStartType->setDisabled(true);
}
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";
break;
case round_trip_func:
s = "Scene Sequence";
break;
case idle_func:
s = "Idle Detection";
break;
case exe_func:
s = "Executable";
break;
case screen_region_func:
s = "Screen Region";
break;
case window_title_func:
s = "Window Title";
break;
case media_func:
s = "Media";
break;
case time_func:
s = "Time";
break;
case audio_func:
s = "Audio";
break;
}
QString text(s.c_str());
QListWidgetItem *item =
new QListWidgetItem(text, ui->priorityList);
item->setData(Qt::UserRole, text);
}
for (int i = 0; i < (int)switcher->threadPriorities.size(); ++i) {
ui->threadPriority->addItem(
switcher->threadPriorities[i].name.c_str());
ui->threadPriority->setItemData(
i, switcher->threadPriorities[i].description.c_str(),
Qt::ToolTipRole);
if (switcher->threadPriority ==
switcher->threadPriorities[i].value) {
ui->threadPriority->setCurrentText(
switcher->threadPriorities[i].name.c_str());
}
}
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");
ui->startupBehavior->setCurrentIndex(switcher->startupBehavior);
if (switcher->th && switcher->th->isRunning())
SetStarted();
else
SetStopped();
}

View File

@@ -0,0 +1,240 @@
#pragma once
#ifdef BUILD_OUT_OF_TREE
#include "../../forms/ui_advanced-scene-switcher.h"
#else
#include "ui_advanced-scene-switcher.h"
#endif
#include "switcher-data-structs.hpp"
#define blog(level, msg, ...) blog(level, "[adv-ss] " msg, ##__VA_ARGS__)
class QCloseEvent;
/*******************************************************************************
* Advanced Scene Switcher window
*******************************************************************************/
class AdvSceneSwitcher : public QDialog {
Q_OBJECT
public:
std::unique_ptr<Ui_AdvSceneSwitcher> ui;
bool loading = true;
AdvSceneSwitcher(QWidget *parent);
void closeEvent(QCloseEvent *event) override;
void SetStarted();
void SetStopped();
int PauseScenesFindByData(const QString &scene);
int PauseWindowsFindByData(const QString &window);
int IgnoreWindowsFindByData(const QString &window);
int IgnoreIdleWindowsFindByData(const QString &window);
void UpdateNonMatchingScene(const QString &name);
void UpdateAutoStopScene(const QString &name);
void UpdateAutoStartScene(const QString &name);
void UpdateIdleDataTransition(const QString &name);
void UpdateIdleDataScene(const QString &name);
void loadUI();
void setupGeneralTab();
void setupTitleTab();
void setupExecutableTab();
void setupRegionTab();
void setupPauseTab();
void setupSequenceTab();
void setupTransitionsTab();
void setupIdleTab();
void setupRandomTab();
void setupMediaTab();
void setupFileTab();
void setupTimeTab();
void setupAudioTab();
void setTabOrder();
static void populateSceneSelection(QComboBox *sel,
bool addPrevious = false,
bool addSelect = true);
static void populateTransitionSelection(QComboBox *sel,
bool addSelect = true);
static void populateWindowSelection(QComboBox *sel,
bool addSelect = true);
static void populateAudioSelection(QComboBox *sel,
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 ");
bool listMoveUp(QListWidget *list);
bool listMoveDown(QListWidget *list);
public slots:
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_startupBehavior_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_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_ignoreWindows_currentRowChanged(int idx);
void on_ignoreWindowsAdd_clicked();
void on_ignoreWindowsRemove_clicked();
void on_sceneSequenceAdd_clicked();
void on_sceneSequenceRemove_clicked();
void on_sceneSequenceSave_clicked();
void on_sceneSequenceLoad_clicked();
void on_sceneSequenceUp_clicked();
void on_sceneSequenceDown_clicked();
void on_autoStopSceneCheckBox_stateChanged(int state);
void on_autoStopScenes_currentTextChanged(const QString &text);
void on_autoStartSceneCheckBox_stateChanged(int state);
void on_autoStartType_currentIndexChanged(int index);
void on_autoStartScenes_currentTextChanged(const QString &text);
void on_verboseLogging_stateChanged(int state);
void on_uiHintsDisable_stateChanged(int state);
void on_exportSettings_clicked();
void on_importSettings_clicked();
void on_transitionsAdd_clicked();
void on_transitionsRemove_clicked();
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();
void on_executableUp_clicked();
void on_executableDown_clicked();
void on_executableAdd_clicked();
void on_executableRemove_clicked();
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_fileAdd_clicked();
void on_fileRemove_clicked();
void on_fileScenesList_currentRowChanged(int idx);
void on_browseButton_3_clicked();
void on_fileUp_clicked();
void on_fileDown_clicked();
void on_mediaAdd_clicked();
void on_mediaRemove_clicked();
void on_mediaUp_clicked();
void on_mediaDown_clicked();
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_priorityUp_clicked();
void on_priorityDown_clicked();
void on_threadPriority_currentTextChanged(const QString &text);
void updateScreenRegionCursorPos();
void on_close_clicked();
private:
};
/********************************************************************************
* Windowtitle helper
********************************************************************************/
void GetWindowList(std::vector<std::string> &windows);
void GetWindowList(QStringList &windows); // Overloaded
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
********************************************************************************/
void setNextTransition(OBSWeakSource &targetScene, obs_source_t *currentSource,
OBSWeakSource &transition,
bool &transitionOverrideOverride, transitionData &td);
void overwriteTransitionOverride(obs_weak_source_t *sceneWs,
obs_source_t *transition, transitionData &td);
void restoreTransitionOverride(obs_source_t *scene, transitionData td);
void switchScene(OBSWeakSource &scene, OBSWeakSource &transition,
bool &transitionOverrideOverride,
std::unique_lock<std::mutex> &lock);
/********************************************************************************
* 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();

View File

@@ -0,0 +1,65 @@
#pragma once
#include <QSpinBox>
#include "switch-generic.hpp"
#include "volume-control.hpp"
constexpr auto audio_func = 8;
constexpr auto default_priority_8 = audio_func;
struct AudioSwitch : virtual SceneSwitcherEntry {
OBSWeakSource audioSource = nullptr;
int volumeThreshold = 0;
float peak = -1;
obs_volmeter_t *volmeter = nullptr;
const char *getType() { return "audio"; }
bool initialized();
bool valid();
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(OBSWeakSource scene_, OBSWeakSource transition_,
OBSWeakSource audioSource_, int volumeThreshold_,
bool usePreviousScene_);
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);
};
class AudioSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
AudioSwitchWidget(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);
private:
QComboBox *audioSources;
QSpinBox *audioVolumeThreshold;
VolControl *volMeter;
QLabel *whenLabel;
QLabel *aboveLabel;
QLabel *switchLabel;
QLabel *usingLabel;
AudioSwitch *switchData;
};

View File

@@ -0,0 +1,48 @@
#pragma once
#include <QCheckBox>
#include "switch-generic.hpp"
constexpr auto exe_func = 3;
constexpr auto default_priority_3 = exe_func;
struct ExecutableSwitch : SceneSwitcherEntry {
QString exe = "";
bool inFocus = false;
const char *getType() { return "exec"; }
inline ExecutableSwitch(){};
inline ExecutableSwitch(OBSWeakSource scene_, OBSWeakSource transition_,
const QString &exe_, bool inFocus_)
: SceneSwitcherEntry(scene_, transition_),
exe(exe_),
inFocus(inFocus_)
{
}
};
class ExecutableSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
ExecutableSwitchWidget(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;
QLabel *whenLabel;
QLabel *switchLabel;
QLabel *usingLabel;
ExecutableSwitch *switchData;
};

View File

@@ -0,0 +1,42 @@
#pragma once
#include "switch-generic.hpp"
constexpr auto read_file_func = 0;
constexpr auto default_priority_0 = read_file_func;
struct FileSwitch : SceneSwitcherEntry {
std::string file;
std::string text;
bool remote = false;
bool useRegex = false;
bool useTime = false;
QDateTime lastMod;
const char *getType() { return "file"; }
inline FileSwitch(OBSWeakSource scene_, OBSWeakSource transition_,
const char *file_, const char *text_, bool remote_,
bool useRegex_, bool useTime_)
: SceneSwitcherEntry(scene_, 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;
};
static inline QString MakeFileSwitchName(const QString &scene,
const QString &transition,
const QString &fileName,
const QString &text, bool useRegex,
bool useTime);

View File

@@ -0,0 +1,50 @@
#pragma once
#include <obs.hpp>
#include <QComboBox>
struct SceneSwitcherEntry {
OBSWeakSource scene = nullptr;
OBSWeakSource transition = nullptr;
bool usePreviousScene = false;
virtual const char *getType() = 0;
virtual bool initialized();
virtual bool valid();
virtual void logMatch();
inline SceneSwitcherEntry() {}
inline SceneSwitcherEntry(OBSWeakSource scene_,
OBSWeakSource transition_,
bool usePreviousScene_ = false)
: scene(scene_),
transition(transition_),
usePreviousScene(usePreviousScene_)
{
}
virtual ~SceneSwitcherEntry() {}
};
class SwitchWidget : public QWidget {
Q_OBJECT
public:
SwitchWidget(SceneSwitcherEntry *s, bool usePreviousScene = true);
virtual SceneSwitcherEntry *getSwitchData();
virtual void setSwitchData(SceneSwitcherEntry *s);
static void swapSwitchData(SwitchWidget *s1, SwitchWidget *s2);
private slots:
void SceneChanged(const QString &text);
void TransitionChanged(const QString &text);
protected:
bool loading = true;
QComboBox *scenes;
QComboBox *transitions;
SceneSwitcherEntry *switchData;
};

View File

@@ -0,0 +1,14 @@
#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 {
bool idleEnable = false;
int time = default_idle_time;
bool alreadySwitched = false;
const char *getType() { return "idle"; }
};

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 {
OBSWeakSource source = nullptr;
obs_media_state state = OBS_MEDIA_STATE_NONE;
bool anyState = false;
time_restriction restriction = TIME_RESTRICTION_NONE;
int64_t time = 0;
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;
const char *getType() { return "media"; }
bool initialized();
bool valid();
void clearSignalHandler();
void resetSignalHandler();
static void MediaStopped(void *data, calldata_t *);
static void MediaEnded(void *data, calldata_t *);
inline MediaSwitch(){};
inline MediaSwitch(OBSWeakSource scene_, OBSWeakSource source_,
OBSWeakSource transition_, obs_media_state state_,
time_restriction restriction_, uint64_t time_,
bool usePreviousScene_);
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(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 *meidaSources;
QComboBox *states;
QComboBox *timeRestrictions;
QSpinBox *time;
QLabel *whenLabel;
QLabel *stateLabel;
QLabel *andLabel;
QLabel *switchLabel;
QLabel *usingLabel;
MediaSwitch *switchData;
};

View File

@@ -0,0 +1,39 @@
#pragma once
#include "switch-generic.hpp"
struct RandomSwitch : SceneSwitcherEntry {
double delay = 0.0;
const char *getType() { return "random"; }
inline RandomSwitch() {}
inline RandomSwitch(OBSWeakSource scene_, OBSWeakSource transition_,
double delay_)
: SceneSwitcherEntry(scene_, transition_), delay(delay_)
{
}
};
class RandomSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
RandomSwitchWidget(RandomSwitch *s);
RandomSwitch *getSwitchData();
void setSwitchData(RandomSwitch *s);
static void swapSwitchData(RandomSwitchWidget *s1,
RandomSwitchWidget *s2);
private slots:
void DelayChanged(double d);
private:
QDoubleSpinBox *delay;
QLabel *switchLabel;
QLabel *usingLabel;
QLabel *forLabel;
RandomSwitch *switchData;
};

View File

@@ -0,0 +1,62 @@
#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 {
int minX = 0, minY = 0, maxX = 0, maxY = 0;
const char *getType() { return "region"; }
inline ScreenRegionSwitch(){};
inline ScreenRegionSwitch(OBSWeakSource scene_,
OBSWeakSource transition_, int minX_,
int minY_, int maxX_, int maxY_)
: SceneSwitcherEntry(scene_, transition_),
minX(minX_),
minY(minY_),
maxX(maxX_),
maxY(maxY_)
{
}
};
class ScreenRegionWidget : public SwitchWidget {
Q_OBJECT
public:
ScreenRegionWidget(ScreenRegionSwitch *s);
ScreenRegionSwitch *getSwitchData();
void setSwitchData(ScreenRegionSwitch *s);
static void swapSwitchData(ScreenRegionWidget *s1,
ScreenRegionWidget *s2);
void showFrame();
void hideFrame();
private slots:
void MinXChanged(int pos);
void MinYChanged(int pos);
void MaxXChanged(int pos);
void MaxYChanged(int pos);
private:
QSpinBox *minX;
QSpinBox *minY;
QSpinBox *maxX;
QSpinBox *maxY;
QFrame helperFrame;
QLabel *cursorLabel;
QLabel *xLabel;
QLabel *switchLabel;
QLabel *usingLabel;
ScreenRegionSwitch *switchData;
void drawFrame();
};

View File

@@ -0,0 +1,64 @@
#pragma once
#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 {
OBSWeakSource startScene = nullptr;
double delay = 0;
int delayMultiplier = 1;
const char *getType() { return "sequence"; }
bool initialized();
bool valid();
void logSleep(int dur);
inline SceneSequenceSwitch(){};
inline SceneSequenceSwitch(OBSWeakSource startScene_,
OBSWeakSource scene_,
OBSWeakSource transition_, double delay_,
int delayMultiplier_, bool usePreviousScene_)
: SceneSwitcherEntry(scene_, transition_, usePreviousScene_),
startScene(startScene_),
delay(delay_),
delayMultiplier(delayMultiplier_)
{
}
};
class SequenceWidget : public SwitchWidget {
Q_OBJECT
public:
SequenceWidget(SceneSequenceSwitch *s);
SceneSequenceSwitch *getSwitchData();
void setSwitchData(SceneSequenceSwitch *s);
static void swapSwitchData(SequenceWidget *s1, SequenceWidget *s2);
void UpdateDelay();
private slots:
void DelayChanged(double delay);
void DelayUnitsChanged(int idx);
void StartSceneChanged(const QString &text);
private:
QDoubleSpinBox *delay;
QComboBox *delayUnits;
QComboBox *startScenes;
QLabel *whenLabel;
QLabel *switchLabel;
QLabel *afterLabel;
QLabel *usingLabel;
SceneSequenceSwitch *switchData;
};

View File

@@ -0,0 +1,61 @@
#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 {
timeTrigger trigger = ANY_DAY;
QTime time = QTime();
const char *getType() { return "time"; }
inline TimeSwitch(){};
inline TimeSwitch(OBSWeakSource scene_, OBSWeakSource transition_,
timeTrigger trigger_, QTime time_,
bool usePreviousScene_)
: SceneSwitcherEntry(scene_, transition_, usePreviousScene_),
trigger(trigger_),
time(time_)
{
}
};
class TimeSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
TimeSwitchWidget(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;
QLabel *atLabel;
QLabel *switchLabel;
QLabel *usingLabel;
TimeSwitch *switchData;
};

View File

@@ -0,0 +1,71 @@
#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 {
const char *getType() { return "def_transition"; }
inline DefaultSceneTransition(){};
inline DefaultSceneTransition(OBSWeakSource scene_,
OBSWeakSource transition_)
: SceneSwitcherEntry(scene_, transition_)
{
}
};
class TransitionSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
TransitionSwitchWidget(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;
QLabel *switchLabel;
QLabel *toLabel;
QLabel *usingLabel;
SceneTransition *switchData;
};
class DefTransitionSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
DefTransitionSwitchWidget(DefaultSceneTransition *s);
DefaultSceneTransition *getSwitchData();
void setSwitchData(DefaultSceneTransition *s);
static void swapSwitchData(DefTransitionSwitchWidget *s1,
DefTransitionSwitchWidget *s2);
private:
QLabel *whenLabel;
QLabel *switchLabel;
DefaultSceneTransition *switchData;
};

View File

@@ -0,0 +1,52 @@
#pragma once
#include "switch-generic.hpp"
constexpr auto window_title_func = 5;
constexpr auto default_priority_5 = window_title_func;
struct WindowSwitch : SceneSwitcherEntry {
std::string window = "";
bool fullscreen = false;
bool maximized = false;
bool focus = true;
const char *getType() { return "window"; }
inline WindowSwitch() {}
inline WindowSwitch(OBSWeakSource scene_, const char *window_,
OBSWeakSource transition_, bool fullscreen_,
bool maximized_, bool focus_)
: SceneSwitcherEntry(scene_, transition_),
window(window_),
fullscreen(fullscreen_),
maximized(maximized_),
focus(focus_)
{
}
};
class WindowSwitchWidget : public SwitchWidget {
Q_OBJECT
public:
WindowSwitchWidget(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

@@ -0,0 +1,224 @@
#pragma once
#include <condition_variable>
#include <vector>
#include <deque>
#include <mutex>
#include <QDateTime>
#include <QThread>
#include <curl/curl.h>
#include "switch-audio.hpp"
#include "switch-executable.hpp"
#include "switch-file.hpp"
#include "switch-idle.hpp"
#include "switch-media.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"
constexpr auto default_interval = 300;
constexpr auto previous_scene_name = "Previous Scene";
typedef enum { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 } NoMatch;
typedef enum { PERSIST = 0, START = 1, STOP = 2 } StartupBehavior;
typedef enum {
RECORDING = 0,
STREAMING = 1,
RECORINDGSTREAMING = 2
} AutoStartType;
typedef struct transitionData {
std::string name = "";
int duration = 0;
} transitionData;
class SwitcherThread;
/********************************************************************************
* SwitcherData
********************************************************************************/
struct SwitcherData {
SwitcherThread *th = nullptr;
std::condition_variable cv;
std::mutex m;
bool transitionActive = false;
bool waitForTransition = false;
std::condition_variable transitionCv;
bool stop = false;
bool verbose = false;
bool disableHints = false;
bool showFrame = false;
bool tansitionOverrideOverride = false;
int interval = default_interval;
obs_source_t *waitScene = NULL;
OBSWeakSource previousScene = NULL;
OBSWeakSource PreviousScene2 = NULL;
OBSWeakSource lastRandomScene;
OBSWeakSource nonMatchingScene;
NoMatch switchIfNotMatching = NO_SWITCH;
StartupBehavior startupBehavior = PERSIST;
std::deque<WindowSwitch> windowSwitches;
std::vector<std::string> ignoreIdleWindows;
std::string lastTitle;
std::deque<ScreenRegionSwitch> screenRegionSwitches;
std::vector<OBSWeakSource> pauseScenesSwitches;
std::vector<std::string> pauseWindowsSwitches;
std::vector<std::string> ignoreWindowsSwitches;
std::deque<SceneSequenceSwitch> sceneSequenceSwitches;
std::deque<RandomSwitch> randomSwitches;
IdleData idleData;
FileIOData fileIO;
std::vector<FileSwitch> fileSwitches;
CURL *curl = nullptr;
std::deque<ExecutableSwitch> executableSwitches;
bool autoStopEnable = false;
OBSWeakSource autoStopScene;
bool autoStartEnable = false;
AutoStartType autoStartType = RECORDING;
OBSWeakSource autoStartScene;
bool autoStartedRecently = false;
std::deque<SceneTransition> sceneTransitions;
std::deque<DefaultSceneTransition> defaultSceneTransitions;
bool changedDefTransitionRecently = false;
std::deque<MediaSwitch> mediaSwitches;
std::deque<TimeSwitch> timeSwitches;
QDateTime liveTime;
std::deque<AudioSwitch> audioSwitches;
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_8};
struct ThreadPrio {
std::string name;
std::string description;
uint32_t value;
};
std::vector<ThreadPrio> threadPriorities{
{"Idle",
"scheduled only when no other threads are running (lowest CPU load)",
QThread::IdlePriority},
{"Lowest", "scheduled less often than LowPriority",
QThread::LowestPriority},
{"Low", "scheduled less often than NormalPriority",
QThread::LowPriority},
{"Normal", "the default priority of the operating system",
QThread::NormalPriority},
{"High", "scheduled more often than NormalPriority",
QThread::HighPriority},
{"Highest", "scheduled more often than HighPriority",
QThread::HighestPriority},
{"Time critical",
"scheduled as often as possible (highest CPU load)",
QThread::TimeCriticalPriority},
};
uint32_t threadPriority = QThread::NormalPriority;
std::vector<int> tabOrder;
bool hotkeysRegistered = false;
obs_hotkey_id startHotkey;
obs_hotkey_id stopHotkey;
obs_hotkey_id toggleHotkey;
void Thread();
void Start();
void Stop();
bool sceneChangedDuringWait();
bool prioFuncsValid();
void writeSceneInfoToFile();
void setDefaultSceneTransitions();
void autoStopStreamAndRecording();
void autoStartStreamRecording();
bool checkPause();
void checkSceneSequence(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition,
std::unique_lock<std::mutex> &lock);
void checkIdleSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkWindowTitleSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkExeSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkScreenRegionSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkSwitchInfoFromFile(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkFileContent(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkRandom(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition, int &delay);
void checkMediaSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkTimeSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void checkAudioSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition);
void saveWindowTitleSwitches(obs_data_t *obj);
void saveScreenRegionSwitches(obs_data_t *obj);
void savePauseSwitches(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);
void saveRandomSwitches(obs_data_t *obj);
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 saveGeneralSettings(obs_data_t *obj);
void saveHotkeys(obs_data_t *obj);
void loadWindowTitleSwitches(obs_data_t *obj);
void loadScreenRegionSwitches(obs_data_t *obj);
void loadPauseSwitches(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);
void loadRandomSwitches(obs_data_t *obj);
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 loadGeneralSettings(obs_data_t *obj);
void loadHotkeys(obs_data_t *obj);
void Prune();
inline ~SwitcherData() { Stop(); }
};
extern SwitcherData *switcher;
class SwitcherThread : public QThread {
public:
explicit SwitcherThread(){};
void run() { switcher->Thread(); };
};

84
src/headers/utility.hpp Normal file
View File

@@ -0,0 +1,84 @@
#pragma once
#include <obs.hpp>
#include <QString>
#include <obs-frontend-api.h>
static inline bool WeakSourceValid(obs_weak_source_t *ws)
{
obs_source_t *source = obs_weak_source_get_source(ws);
if (source)
obs_source_release(source);
return !!source;
}
static inline std::string GetWeakSourceName(obs_weak_source_t *weak_source)
{
std::string name;
obs_source_t *source = obs_weak_source_get_source(weak_source);
if (source) {
name = obs_source_get_name(source);
obs_source_release(source);
}
return name;
}
static inline OBSWeakSource GetWeakSourceByName(const char *name)
{
OBSWeakSource weak;
obs_source_t *source = obs_get_source_by_name(name);
if (source) {
weak = obs_source_get_weak_source(source);
obs_weak_source_release(weak);
obs_source_release(source);
}
return weak;
}
static inline OBSWeakSource GetWeakSourceByQString(const QString &name)
{
return GetWeakSourceByName(name.toUtf8().constData());
}
static inline OBSWeakSource GetWeakTransitionByName(const char *transitionName)
{
OBSWeakSource weak;
obs_source_t *source = nullptr;
if (strcmp(transitionName, "Default") == 0) {
source = obs_frontend_get_current_transition();
weak = obs_source_get_weak_source(source);
obs_source_release(source);
obs_weak_source_release(weak);
return weak;
}
obs_frontend_source_list *transitions = new obs_frontend_source_list();
obs_frontend_get_transitions(transitions);
bool match = false;
for (size_t i = 0; i < transitions->sources.num; i++) {
const char *name =
obs_source_get_name(transitions->sources.array[i]);
if (strcmp(transitionName, name) == 0) {
match = true;
source = transitions->sources.array[i];
break;
}
}
if (match) {
weak = obs_source_get_weak_source(source);
obs_weak_source_release(weak);
}
obs_frontend_source_list_free(transitions);
return weak;
}
static inline OBSWeakSource GetWeakTransitionByQString(const QString &name)
{
return GetWeakTransitionByName(name.toUtf8().constData());
}

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;
};

101
src/hotkey.cpp Normal file
View File

@@ -0,0 +1,101 @@
#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()))
switcher->Start();
}
}
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())
switcher->Stop();
}
}
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())
switcher->Stop();
else
switcher->Start();
}
}
void registerHotkeys()
{
switcher->startHotkey = obs_hotkey_register_frontend(
"startSwitcherHotkey", "Start the Advanced Scene Switcher",
startHotkeyFunc, NULL);
switcher->stopHotkey = obs_hotkey_register_frontend(
"stopSwitcherHotkey", "Stop the Advanced Scene Switcher",
stopHotkeyFunc, NULL);
switcher->toggleHotkey = obs_hotkey_register_frontend(
"startStopToggleSwitcherHotkey",
"Toggle Start/Stop for the Advanced Scene Switcher",
startStopToggleHotkeyFunc, NULL);
switcher->hotkeysRegistered = true;
}
void SwitcherData::saveHotkeys(obs_data_t *obj)
{
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

@@ -0,0 +1,389 @@
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/extensions/scrnsaver.h>
#undef Bool
#undef CursorShape
#undef Expose
#undef KeyPress
#undef KeyRelease
#undef FocusIn
#undef FocusOut
#undef FontChange
#undef None
#undef Status
#undef Unsorted
#include <util/platform.h>
#include "../headers/advanced-scene-switcher.hpp"
static Display *xdisplay = 0;
Display *disp()
{
if (!xdisplay)
xdisplay = XOpenDisplay(NULL);
return xdisplay;
}
void cleanupDisplay()
{
if (!xdisplay)
return;
XCloseDisplay(xdisplay);
xdisplay = 0;
}
static bool ewmhIsSupported()
{
Display *display = disp();
Atom netSupportingWmCheck =
XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", true);
Atom actualType;
int format = 0;
unsigned long num = 0, bytes = 0;
unsigned char *data = NULL;
Window ewmh_window = 0;
int status = XGetWindowProperty(display, DefaultRootWindow(display),
netSupportingWmCheck, 0L, 1L, false,
XA_WINDOW, &actualType, &format, &num,
&bytes, &data);
if (status == Success) {
if (num > 0) {
ewmh_window = ((Window *)data)[0];
}
if (data) {
XFree(data);
data = NULL;
}
}
if (ewmh_window) {
status = XGetWindowProperty(display, ewmh_window,
netSupportingWmCheck, 0L, 1L, false,
XA_WINDOW, &actualType, &format,
&num, &bytes, &data);
if (status != Success || num == 0 ||
ewmh_window != ((Window *)data)[0]) {
ewmh_window = 0;
}
if (status == Success && data) {
XFree(data);
}
}
return ewmh_window != 0;
}
static QStringList getStates(Window window)
{
QStringList states;
if (!ewmhIsSupported())
return states;
Atom wmState = XInternAtom(disp(), "_NET_WM_STATE", true), type;
int format;
unsigned long num, bytes;
unsigned char *data;
int status = XGetWindowProperty(disp(), window, wmState, 0, ~0L, false,
AnyPropertyType, &type, &format, &num,
&bytes, &data);
if (status == Success)
for (unsigned long i = 0; i < num; i++)
states.append(QString(
XGetAtomName(disp(), ((Atom *)data)[i])));
return states;
}
static std::vector<Window> getTopLevelWindows()
{
std::vector<Window> res;
res.resize(0);
if (!ewmhIsSupported()) {
return res;
}
Atom netClList = XInternAtom(disp(), "_NET_CLIENT_LIST", true);
Atom actualType;
int format;
unsigned long num, bytes;
Window *data = 0;
for (int i = 0; i < ScreenCount(disp()); ++i) {
Window rootWin = RootWindow(disp(), i);
int status = XGetWindowProperty(disp(), rootWin, netClList, 0L,
~0L, false, AnyPropertyType,
&actualType, &format, &num,
&bytes, (uint8_t **)&data);
if (status != Success) {
continue;
}
for (unsigned long i = 0; i < num; ++i)
res.emplace_back(data[i]);
XFree(data);
}
return res;
}
static std::string GetWindowTitle(size_t i)
{
Window w = getTopLevelWindows().at(i);
std::string windowTitle;
char *name;
XTextProperty text;
int status = XGetTextProperty(
disp(), w, &text, XInternAtom(disp(), "_NET_WM_NAME", true));
if (status == 0)
status = XGetTextProperty(disp(), w, &text,
XInternAtom(disp(), "WM_NAME", true));
name = reinterpret_cast<char *>(text.value);
if (status != 0 && name != nullptr) {
std::string str(name);
windowTitle = str;
XFree(name);
}
return windowTitle;
}
void GetWindowList(std::vector<std::string> &windows)
{
windows.resize(0);
for (size_t i = 0; i < getTopLevelWindows().size(); ++i) {
if (GetWindowTitle(i) != "")
windows.emplace_back(GetWindowTitle(i));
}
}
// Overloaded
void GetWindowList(QStringList &windows)
{
windows.clear();
for (size_t i = 0; i < getTopLevelWindows().size(); ++i) {
if (GetWindowTitle(i) != "")
windows << QString::fromStdString(GetWindowTitle(i));
}
}
void GetCurrentWindowTitle(std::string &title)
{
if (!ewmhIsSupported()) {
return;
}
Atom active = XInternAtom(disp(), "_NET_ACTIVE_WINDOW", true);
Atom actualType;
int format;
unsigned long num, bytes;
Window *data = 0;
char *name;
Window rootWin = RootWindow(disp(), 0);
int xstatus = XGetWindowProperty(disp(), rootWin, active, 0L, ~0L,
false, AnyPropertyType, &actualType,
&format, &num, &bytes,
(uint8_t **)&data);
int status = 0;
XTextProperty text;
if (xstatus == Success) {
status = XGetTextProperty(disp(), data[0], &text,
XInternAtom(disp(), "_NET_WM_NAME",
true));
if (status == 0)
status = XGetTextProperty(disp(), data[0], &text,
XInternAtom(disp(), "WM_NAME",
true));
}
name = reinterpret_cast<char *>(text.value);
if (status != 0 && name != nullptr) {
std::string str(name);
title = str;
XFree(name);
}
}
std::pair<int, int> getCursorPos()
{
std::pair<int, int> pos(0, 0);
Display *dpy;
Window root;
Window ret_root;
Window ret_child;
int root_x;
int root_y;
int win_x;
int win_y;
unsigned int mask;
dpy = XOpenDisplay(NULL);
root = XDefaultRootWindow(dpy);
if (XQueryPointer(dpy, root, &ret_root, &ret_child, &root_x, &root_y,
&win_x, &win_y, &mask)) {
pos = std::pair<int, int>(root_x, root_y);
}
XCloseDisplay(dpy);
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())
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 fullscreen
if (equals || matches) {
QStringList states = getStates(window);
if (!states.isEmpty()) {
// True if window is fullscreen
bool fullscreen = states.contains(
"_NET_WM_STATE_FULLSCREEN");
return (fullscreen);
}
break;
}
}
return false;
}
//exe switch is not quite what is expected but it works for now
void GetProcessList(QStringList &processes)
{
processes.clear();
for (size_t i = 0; i < getTopLevelWindows().size(); ++i) {
std::string s = GetWindowTitle(i);
if (s != "")
processes << QString::fromStdString(s);
}
}
bool isInFocus(const QString &executable)
{
std::string current;
GetCurrentWindowTitle(current);
// True if executable switch equals current window
bool equals = (executable.toStdString() == current);
// True if executable switch matches current window
bool matches = QString::fromStdString(current).contains(
QRegularExpression(executable));
return (equals || matches);
}
int secondsSinceLastInput()
{
time_t idle_time;
static XScreenSaverInfo *mit_info;
Display *display;
int screen;
mit_info = XScreenSaverAllocInfo();
if ((display = XOpenDisplay(NULL)) == NULL) {
return (-1);
}
screen = DefaultScreen(display);
XScreenSaverQueryInfo(display, RootWindow(display, screen), mit_info);
idle_time = (mit_info->idle) / 1000;
XFree(mit_info);
XCloseDisplay(display);
return idle_time;
}

View File

@@ -0,0 +1,244 @@
#import <AppKit/AppKit.h>
#import <CoreFoundation/CoreFoundation.h>
#import <CoreGraphics/CGEvent.h>
#import <Carbon/Carbon.h>
#include <Carbon/Carbon.h>
#include <util/platform.h>
#include "../headers/advanced-scene-switcher.hpp"
void GetWindowList(std::vector<std::string> &windows)
{
windows.resize(0);
@autoreleasepool {
NSMutableArray *apps =
(__bridge NSMutableArray *)CGWindowListCopyWindowInfo(
kCGWindowListOptionAll, kCGNullWindowID);
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]);
// Check if name exists
if (!name.empty() &&
find(windows.begin(), windows.end(), name) ==
windows.end())
windows.emplace_back(name);
// Check if owner exists
else if (!owner.empty() &&
find(windows.begin(), windows.end(), owner) ==
windows.end())
windows.emplace_back(owner);
}
}
}
// Overloaded
void GetWindowList(QStringList &windows)
{
windows.clear();
@autoreleasepool {
NSMutableArray *apps =
(__bridge NSMutableArray *)CGWindowListCopyWindowInfo(
kCGWindowListOptionAll, kCGNullWindowID);
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]);
// Check if name exists
if (!name.empty() &&
!windows.contains(QString::fromStdString(name)))
windows << QString::fromStdString(name);
// Check if owner exists
else if (!owner.empty() &&
!windows.contains(
QString::fromStdString(name)))
windows << QString::fromStdString(owner);
}
}
}
void GetCurrentWindowTitle(std::string &title)
{
title.resize(0);
@autoreleasepool {
NSMutableArray *apps =
(__bridge NSMutableArray *)CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly,
kCGNullWindowID);
for (NSDictionary *app in apps) {
int layer =
[[app objectForKey:@"kCGWindowLayer"] intValue];
// True if window is frontmost
if (layer == 0) {
// 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]);
if (!name.empty())
title = name;
else if (!owner.empty())
title = owner;
break;
}
}
}
}
std::pair<int, int> getCursorPos()
{
std::pair<int, int> pos(0, 0);
CGEventRef event = CGEventCreate(NULL);
CGPoint cursorPos = CGEventGetLocation(event);
CFRelease(event);
pos.first = cursorPos.x;
pos.second = cursorPos.y;
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
@autoreleasepool {
NSArray *screens = [NSScreen screens];
NSMutableArray *apps =
(__bridge NSMutableArray *)CGWindowListCopyWindowInfo(
kCGWindowListOptionAll, kCGNullWindowID);
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]);
// True if switch equals app
bool equals = (title == name || title == owner);
// True if switch matches app
bool matches = (QString::fromStdString(name).contains(
QRegularExpression(
QString::fromStdString(
title))) ||
QString::fromStdString(owner).contains(
QRegularExpression(
QString::fromStdString(
title))));
// If found, check if fullscreen
if (equals || matches) {
// Get window bounds
NSRect bounds;
CGRectMakeWithDictionaryRepresentation(
(CFDictionaryRef)[app
objectForKey:@"kCGWindowBounds"],
&bounds);
// Compare to screen bounds
for (NSScreen *screen in screens) {
NSRect frame = [screen visibleFrame];
// True if flipped window origin equals screen origin
bool origin =
(bounds.origin.x ==
frame.origin.x &&
([screens[0] visibleFrame]
.size.height -
frame.size.height -
bounds.origin.y ==
frame.origin.y));
// True if window size equals screen size
bool size = NSEqualSizes(bounds.size,
frame.size);
if (origin && size)
return true;
}
}
}
}
return false;
}
int secondsSinceLastInput()
{
double time = CGEventSourceSecondsSinceLastEventType(
kCGEventSourceStateCombinedSessionState,
kCGAnyInputEventType) +
0.5;
return (int)time;
}
void GetProcessList(QStringList &list)
{
list.clear();
@autoreleasepool {
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
NSArray *array = [ws runningApplications];
for (NSRunningApplication *app in array) {
NSString *name = app.localizedName;
if (!name)
continue;
const char *str = name.UTF8String;
if (str && *str)
list << (str);
}
}
}
bool isInFocus(const QString &executable)
{
std::string current;
GetCurrentWindowTitle(current);
// True if executable switch equals current window
bool equals = (executable.toStdString() == current);
// True if executable switch matches current window
bool matches = QString::fromStdString(current).contains(
QRegularExpression(executable));
return (equals || matches);
}

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

@@ -0,0 +1,442 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/volume-control.hpp"
#include "headers/utility.hpp"
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::on_audioAdd_clicked()
{
ui->audioAdd->disconnect(addPulse);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->audioSwitches.emplace_back();
QListWidgetItem *item;
item = new QListWidgetItem(ui->audioSwitches);
ui->audioSwitches->addItem(item);
AudioSwitchWidget *sw =
new AudioSwitchWidget(&switcher->audioSwitches.back());
item->setSizeHint(sw->minimumSizeHint());
ui->audioSwitches->setItemWidget(item, sw);
}
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 SwitcherData::checkAudioSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (audioSwitches.size() == 0)
return;
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 = ((double)s.peak + 60) * 1.7 >
s.volumeThreshold;
if (volumeThresholdreached && audioActive) {
scene = (s.usePreviousScene) ? previousScene : s.scene;
transition = s.transition;
match = true;
if (verbose)
s.logMatch();
break;
}
}
}
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();
obs_source_t *sceneSource = obs_weak_source_get_source(s.scene);
obs_source_t *transition =
obs_weak_source_get_source(s.transition);
obs_source_t *audioSrouce =
obs_weak_source_get_source(s.audioSource);
if ((s.usePreviousScene || sceneSource) && transition &&
audioSrouce) {
const char *sceneName =
obs_source_get_name(sceneSource);
const char *transitionName =
obs_source_get_name(transition);
const char *audioSourceName =
obs_source_get_name(audioSrouce);
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, "audioSource",
audioSourceName);
obs_data_set_int(array_obj, "volume",
s.volumeThreshold);
obs_data_array_push_back(audioArray, array_obj);
}
obs_source_release(sceneSource);
obs_source_release(transition);
obs_source_release(audioSrouce);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "audioSwitches", audioArray);
obs_data_array_release(audioArray);
}
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);
const char *scene = obs_data_get_string(array_obj, "scene");
const char *transition =
obs_data_get_string(array_obj, "transition");
const char *audioSource =
obs_data_get_string(array_obj, "audioSource");
int vol = obs_data_get_int(array_obj, "volume");
switcher->audioSwitches.emplace_back(
GetWeakSourceByName(scene),
GetWeakTransitionByName(transition),
GetWeakSourceByName(audioSource), vol,
(strcmp(scene, previous_scene_name) == 0));
obs_data_release(array_obj);
}
obs_data_array_release(audioArray);
}
void AdvSceneSwitcher::setupAudioTab()
{
for (auto &s : switcher->audioSwitches) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->audioSwitches);
ui->audioSwitches->addItem(item);
AudioSwitchWidget *sw = new AudioSwitchWidget(&s);
item->setSizeHint(sw->minimumSizeHint());
ui->audioSwitches->setItemWidget(item, sw);
}
if (switcher->audioSwitches.size() == 0)
addPulse = PulseWidget(ui->audioAdd, QColor(Qt::green));
}
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);
s->peak = peak[0];
for (int i = 1; i < MAX_AUDIO_CHANNELS; i++)
if (peak[i] > s->peak)
s->peak = peak[i];
}
void AudioSwitch::resetVolmeter()
{
obs_volmeter_remove_callback(volmeter, setVolumeLevel, this);
obs_volmeter_destroy(volmeter);
volmeter = obs_volmeter_create(OBS_FADER_LOG);
obs_volmeter_add_callback(volmeter, setVolumeLevel, this);
obs_source_t *as = obs_weak_source_get_source(audioSource);
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);
}
bool AudioSwitch::initialized()
{
return SceneSwitcherEntry::initialized() && audioSource;
}
bool AudioSwitch::valid()
{
return !initialized() ||
(SceneSwitcherEntry::valid() && WeakSourceValid(audioSource));
}
AudioSwitch::AudioSwitch(OBSWeakSource scene_, OBSWeakSource transition_,
OBSWeakSource audioSource_, int volumeThreshold_,
bool usePreviousScene_)
: SceneSwitcherEntry(scene_, transition_, usePreviousScene_),
audioSource(audioSource_),
volumeThreshold(volumeThreshold_)
{
volmeter = obs_volmeter_create(OBS_FADER_LOG);
obs_volmeter_add_callback(volmeter, setVolumeLevel, this);
obs_source_t *as = obs_weak_source_get_source(audioSource);
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);
}
AudioSwitch::AudioSwitch(const AudioSwitch &other)
: SceneSwitcherEntry(other.scene, other.transition,
other.usePreviousScene),
audioSource(other.audioSource),
volumeThreshold(other.volumeThreshold)
{
volmeter = obs_volmeter_create(OBS_FADER_LOG);
obs_volmeter_add_callback(volmeter, setVolumeLevel, this);
obs_source_t *as = obs_weak_source_get_source(other.audioSource);
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);
}
AudioSwitch::AudioSwitch(AudioSwitch &&other)
: SceneSwitcherEntry(other.scene, other.transition,
other.usePreviousScene),
audioSource(other.audioSource),
volumeThreshold(other.volumeThreshold),
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.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.peak, second.peak);
std::swap(first.volmeter, second.volmeter);
first.resetVolmeter();
second.resetVolmeter();
}
AudioSwitchWidget::AudioSwitchWidget(AudioSwitch *s) : SwitchWidget(s)
{
audioSources = new QComboBox();
audioVolumeThreshold = new QSpinBox();
obs_source_t *soruce = nullptr;
if (s)
soruce = obs_weak_source_get_source(s->audioSource);
volMeter = new VolControl(soruce);
obs_source_release(soruce);
whenLabel = new QLabel("When the volume of");
aboveLabel = new QLabel("is above");
switchLabel = new QLabel("switch to");
usingLabel = new QLabel("using");
audioVolumeThreshold->setSuffix("%");
audioVolumeThreshold->setMaximum(100);
audioVolumeThreshold->setMinimum(0);
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(audioSources,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(SourceChanged(const QString &)));
AdvSceneSwitcher::populateAudioSelection(audioSources);
if (s) {
audioSources->setCurrentText(
GetWeakSourceName(s->audioSource).c_str());
audioVolumeThreshold->setValue(s->volumeThreshold);
}
setStyleSheet("* { background-color: transparent; }");
QHBoxLayout *switchLayout = new QHBoxLayout;
switchLayout->addWidget(whenLabel);
switchLayout->addWidget(audioSources);
switchLayout->addWidget(aboveLabel);
switchLayout->addWidget(audioVolumeThreshold);
switchLayout->addWidget(switchLabel);
switchLayout->addWidget(scenes);
switchLayout->addWidget(usingLabel);
switchLayout->addWidget(transitions);
switchLayout->addStretch();
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;
}

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

@@ -0,0 +1,284 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::on_executableAdd_clicked()
{
ui->executableAdd->disconnect(addPulse);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->executableSwitches.emplace_back();
QListWidgetItem *item;
item = new QListWidgetItem(ui->executables);
ui->executables->addItem(item);
ExecutableSwitchWidget *sw = new ExecutableSwitchWidget(
&switcher->executableSwitches.back());
item->setSizeHint(sw->minimumSizeHint());
ui->executables->setItemWidget(item, sw);
}
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)
{
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;
// True if executable switch is running (direct)
bool equals = runningProcesses.contains(s.exe);
// True if executable switch is running (regex)
bool matches = (runningProcesses.indexOf(
QRegularExpression(s.exe)) != -1);
// True if focus is disabled OR switch is focused
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.scene;
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();
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, "exefile",
s.exe.toUtf8());
obs_data_set_bool(array_obj, "infocus", s.inFocus);
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 AdvSceneSwitcher::setupExecutableTab()
{
for (auto &s : switcher->executableSwitches) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->executables);
ui->executables->addItem(item);
ExecutableSwitchWidget *sw = new ExecutableSwitchWidget(&s);
item->setSizeHint(sw->minimumSizeHint());
ui->executables->setItemWidget(item, sw);
}
if (switcher->executableSwitches.size() == 0)
addPulse = PulseWidget(ui->executableAdd, QColor(Qt::green));
}
ExecutableSwitchWidget::ExecutableSwitchWidget(ExecutableSwitch *s)
: SwitchWidget(s, false)
{
processes = new QComboBox();
requiresFocus = new QCheckBox("only if focused");
whenLabel = new QLabel("When");
switchLabel = new QLabel("is running switch to");
usingLabel = new QLabel("using");
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);
}
setStyleSheet("* { background-color: transparent; }");
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(whenLabel);
mainLayout->addWidget(processes);
mainLayout->addWidget(switchLabel);
mainLayout->addWidget(scenes);
mainLayout->addWidget(usingLabel);
mainLayout->addWidget(transitions);
mainLayout->addWidget(requiresFocus);
mainLayout->addStretch();
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;
}

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

@@ -0,0 +1,497 @@
#include <QFileDialog>
#include <QTextStream>
#include <QDateTime>
#include <curl/curl.h>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/curl-helper.hpp"
#include "headers/utility.hpp"
#define LOCAL_FILE_IDX 0
#define REMOTE_FILE_IDX 1
void AdvSceneSwitcher::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 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("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)
{
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 (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)
s.logMatch();
break;
}
}
}
void AdvSceneSwitcher::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 AdvSceneSwitcher::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 AdvSceneSwitcher::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 AdvSceneSwitcher::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 AdvSceneSwitcher::on_fileScenesList_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];
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 AdvSceneSwitcher::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 AdvSceneSwitcher::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 AdvSceneSwitcher::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");
if (switcher->curl)
ui->libcurlWarning->setVisible(false);
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);
}
}
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;
}

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

@@ -0,0 +1,89 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
bool SceneSwitcherEntry::initialized()
{
return (usePreviousScene || WeakSourceValid(scene)) && transition;
}
bool SceneSwitcherEntry::valid()
{
return !initialized() ||
((usePreviousScene || WeakSourceValid(scene)) &&
WeakSourceValid(transition));
}
void SceneSwitcherEntry::logMatch()
{
const char *sceneName = previous_scene_name;
if (!usePreviousScene) {
obs_source_t *s = obs_weak_source_get_source(scene);
sceneName = obs_source_get_name(s);
obs_source_release(s);
}
blog(LOG_INFO, "match for '%s' - switch to scene '%s'", getType(),
sceneName);
}
SwitchWidget::SwitchWidget(SceneSwitcherEntry *s, bool usePreviousScene)
{
scenes = new QComboBox();
transitions = new QComboBox();
QWidget::connect(scenes, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(SceneChanged(const QString &)));
QWidget::connect(transitions,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(TransitionChanged(const QString &)));
AdvSceneSwitcher::populateSceneSelection(scenes, usePreviousScene);
AdvSceneSwitcher::populateTransitionSelection(transitions);
if (s) {
if (s->usePreviousScene)
scenes->setCurrentText(previous_scene_name);
else
scenes->setCurrentText(
GetWeakSourceName(s->scene).c_str());
transitions->setCurrentText(
GetWeakSourceName(s->transition).c_str());
}
switchData = s;
}
SceneSwitcherEntry *SwitchWidget::getSwitchData()
{
return switchData;
}
void SwitchWidget::setSwitchData(SceneSwitcherEntry *s)
{
switchData = s;
}
void SwitchWidget::swapSwitchData(SwitchWidget *s1, SwitchWidget *s2)
{
SceneSwitcherEntry *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
void SwitchWidget::SceneChanged(const QString &text)
{
if (loading || !switchData)
return;
std::lock_guard<std::mutex> lock(switcher->m);
switchData->usePreviousScene = text.compare(previous_scene_name) == 0;
if (switchData->usePreviousScene)
return;
switchData->scene = GetWeakSourceByQString(text);
}
void SwitchWidget::TransitionChanged(const QString &text)
{
if (loading || !switchData)
return;
std::lock_guard<std::mutex> lock(switcher->m);
switchData->transition = GetWeakTransitionByQString(text);
}

300
src/switch-idle.cpp Normal file
View File

@@ -0,0 +1,300 @@
#include <regex>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
void SwitcherData::checkIdleSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (!idleData.idleEnable)
return;
std::string title;
bool ignoreIdle = false;
//lock.unlock();
GetCurrentWindowTitle(title);
//lock.lock();
for (std::string &window : ignoreIdleWindows) {
if (window == title) {
ignoreIdle = true;
break;
}
}
if (!ignoreIdle) {
for (std::string &window : ignoreIdleWindows) {
try {
bool matches = std::regex_match(
title, std::regex(window));
if (matches) {
ignoreIdle = true;
break;
}
} catch (const std::regex_error &) {
}
}
}
if (!ignoreIdle && secondsSinceLastInput() > idleData.time) {
if (idleData.alreadySwitched)
return;
scene = (idleData.usePreviousScene) ? previousScene
: idleData.scene;
transition = idleData.transition;
match = true;
idleData.alreadySwitched = true;
if (verbose)
idleData.logMatch();
} else
idleData.alreadySwitched = false;
}
void AdvSceneSwitcher::on_idleCheckBox_stateChanged(int state)
{
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;
} 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());
}
}
void AdvSceneSwitcher::UpdateIdleDataTransition(const QString &name)
{
obs_weak_source_t *transition = GetWeakTransitionByQString(name);
switcher->idleData.transition = transition;
}
void AdvSceneSwitcher::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 AdvSceneSwitcher::on_idleTransitions_currentTextChanged(const QString &text)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
UpdateIdleDataTransition(text);
}
void AdvSceneSwitcher::on_idleScenes_currentTextChanged(const QString &text)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
UpdateIdleDataScene(text);
}
void AdvSceneSwitcher::on_idleSpinBox_valueChanged(int i)
{
if (loading)
return;
std::lock_guard<std::mutex> lock(switcher->m);
switcher->idleData.time = i;
}
void AdvSceneSwitcher::on_ignoreIdleWindows_currentRowChanged(int idx)
{
if (loading)
return;
if (idx == -1)
return;
QListWidgetItem *item = ui->ignoreIdleWindows->item(idx);
QString window = item->data(Qt::UserRole).toString();
std::lock_guard<std::mutex> lock(switcher->m);
for (auto &w : switcher->ignoreIdleWindows) {
if (window.compare(w.c_str()) == 0) {
ui->ignoreIdleWindowsWindows->setCurrentText(w.c_str());
break;
}
}
}
void AdvSceneSwitcher::on_ignoreIdleAdd_clicked()
{
QString windowName = ui->ignoreIdleWindowsWindows->currentText();
if (windowName.isEmpty())
return;
QVariant v = QVariant::fromValue(windowName);
QList<QListWidgetItem *> items =
ui->ignoreIdleWindows->findItems(windowName, Qt::MatchExactly);
if (items.size() == 0) {
QListWidgetItem *item =
new QListWidgetItem(windowName, ui->ignoreIdleWindows);
item->setData(Qt::UserRole, v);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->ignoreIdleWindows.emplace_back(
windowName.toUtf8().constData());
ui->ignoreIdleWindows->sortItems();
}
}
void AdvSceneSwitcher::on_ignoreIdleRemove_clicked()
{
QListWidgetItem *item = ui->ignoreIdleWindows->currentItem();
if (!item)
return;
QString windowName = item->data(Qt::UserRole).toString();
{
std::lock_guard<std::mutex> lock(switcher->m);
auto &windows = switcher->ignoreIdleWindows;
for (auto it = windows.begin(); it != windows.end(); ++it) {
auto &s = *it;
if (s == windowName.toUtf8().constData()) {
windows.erase(it);
break;
}
}
}
delete item;
}
int AdvSceneSwitcher::IgnoreIdleWindowsFindByData(const QString &window)
{
int count = ui->ignoreIdleWindows->count();
int idx = -1;
for (int i = 0; i < count; i++) {
QListWidgetItem *item = ui->ignoreIdleWindows->item(i);
QString itemRegion = item->data(Qt::UserRole).toString();
if (itemRegion == window) {
idx = i;
break;
}
}
return idx;
}
void SwitcherData::saveIdleSwitches(obs_data_t *obj)
{
obs_data_array_t *ignoreIdleWindowsArray = obs_data_array_create();
for (std::string &window : switcher->ignoreIdleWindows) {
obs_data_t *array_obj = obs_data_create();
obs_data_set_string(array_obj, "window", window.c_str());
obs_data_array_push_back(ignoreIdleWindowsArray, array_obj);
obs_data_release(array_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);
}
void SwitcherData::loadIdleSwitches(obs_data_t *obj)
{
switcher->ignoreIdleWindows.clear();
obs_data_array_t *ignoreIdleWindowsArray =
obs_data_get_array(obj, "ignoreIdleWindows");
size_t count = obs_data_array_count(ignoreIdleWindowsArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj =
obs_data_array_item(ignoreIdleWindowsArray, i);
const char *window = obs_data_get_string(array_obj, "window");
switcher->ignoreIdleWindows.emplace_back(window);
obs_data_release(array_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);
}
void AdvSceneSwitcher::setupIdleTab()
{
populateSceneSelection(ui->idleScenes, true);
populateTransitionSelection(ui->idleTransitions);
populateWindowSelection(ui->ignoreIdleWindowsWindows);
for (auto &window : switcher->ignoreIdleWindows) {
QString text = QString::fromStdString(window);
QListWidgetItem *item =
new QListWidgetItem(text, ui->ignoreIdleWindows);
item->setData(Qt::UserRole, text);
}
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);
} else {
ui->idleScenes->setDisabled(true);
ui->idleSpinBox->setDisabled(true);
ui->idleTransitions->setDisabled(true);
}
}

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

@@ -0,0 +1,535 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::on_mediaAdd_clicked()
{
ui->mediaAdd->disconnect(addPulse);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->mediaSwitches.emplace_back();
QListWidgetItem *item;
item = new QListWidgetItem(ui->mediaSwitches);
ui->mediaSwitches->addItem(item);
MediaSwitchWidget *sw =
new MediaSwitchWidget(&switcher->mediaSwitches.back());
item->setSizeHint(sw->minimumSizeHint());
ui->mediaSwitches->setItemWidget(item, sw);
}
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)
{
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 matchedEnded = false;
if (state == OBS_MEDIA_STATE_ENDED) {
matchedEnded = mediaSwitch.previousStateEnded;
mediaSwitch.previousStateEnded = true;
} else {
mediaSwitch.previousStateEnded = false;
}
// reset for next check
mediaSwitch.stopped = false;
mediaSwitch.ended = false;
bool matchedState = ((state == mediaSwitch.state) ||
mediaSwitch.anyState) ||
matchedStopped || matchedEnded;
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) {
match = true;
scene = (mediaSwitch.usePreviousScene)
? previousScene
: mediaSwitch.scene;
transition = mediaSwitch.transition;
if (verbose)
mediaSwitch.logMatch();
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();
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");
switcher->mediaSwitches.emplace_back(
GetWeakSourceByName(scene), GetWeakSourceByName(source),
GetWeakTransitionByName(transition), state, restriction,
time, (strcmp(scene, previous_scene_name) == 0));
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(&s);
item->setSizeHint(sw->minimumSizeHint());
ui->mediaSwitches->setItemWidget(item, sw);
}
if (switcher->mediaSwitches.size() == 0)
addPulse = PulseWidget(ui->mediaAdd, QColor(Qt::green));
}
bool MediaSwitch::initialized()
{
return SceneSwitcherEntry::initialized() && source;
}
bool MediaSwitch::valid()
{
return !initialized() ||
(SceneSwitcherEntry::valid() && WeakSourceValid(source));
}
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;
}
inline MediaSwitch::MediaSwitch(OBSWeakSource scene_, OBSWeakSource source_,
OBSWeakSource transition_,
obs_media_state state_,
time_restriction restriction_, uint64_t time_,
bool usePreviousScene_)
: SceneSwitcherEntry(scene_, transition_, usePreviousScene_),
source(source_),
state(state_),
restriction(restriction_),
time(time_)
{
anyState = state > 7;
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(const MediaSwitch &other)
: SceneSwitcherEntry(other.scene, other.transition,
other.usePreviousScene),
source(other.source),
state(other.state),
restriction(other.restriction),
time(other.time)
{
anyState = state > 7;
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.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.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("None");
list->addItem("Playing");
list->addItem("Opening");
list->addItem("Buffering");
list->addItem("Paused");
list->addItem("Stopped");
list->addItem("Ended");
list->addItem("Error");
list->addItem("Any");
}
void populateTimeRestrictions(QComboBox *list)
{
list->addItem("None");
list->addItem("Time shorter");
list->addItem("Time longer");
list->addItem("Time remaining shorter");
list->addItem("Time remaining longer");
}
MediaSwitchWidget::MediaSwitchWidget(MediaSwitch *s) : SwitchWidget(s)
{
meidaSources = new QComboBox();
states = new QComboBox();
timeRestrictions = new QComboBox();
time = new QSpinBox();
whenLabel = new QLabel("When");
stateLabel = new QLabel("state is");
andLabel = new QLabel("and");
switchLabel = new QLabel("switch to");
usingLabel = new QLabel("using");
time->setSuffix("ms");
time->setMaximum(99999999);
time->setMinimum(0);
QWidget::connect(meidaSources,
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(meidaSources);
populateMediaStates(states);
populateTimeRestrictions(timeRestrictions);
if (s) {
meidaSources->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);
}
setStyleSheet("* { background-color: transparent; }");
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(whenLabel);
mainLayout->addWidget(meidaSources);
mainLayout->addWidget(stateLabel);
mainLayout->addWidget(states);
mainLayout->addWidget(andLabel);
mainLayout->addWidget(timeRestrictions);
mainLayout->addWidget(time);
mainLayout->addWidget(switchLabel);
mainLayout->addWidget(scenes);
mainLayout->addWidget(usingLabel);
mainLayout->addWidget(transitions);
mainLayout->addStretch();
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 > 7;
}
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;
}

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

@@ -0,0 +1,324 @@
#include <regex>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
void AdvSceneSwitcher::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 AdvSceneSwitcher::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 AdvSceneSwitcher::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 AdvSceneSwitcher::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 AdvSceneSwitcher::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 AdvSceneSwitcher::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 AdvSceneSwitcher::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 AdvSceneSwitcher::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, "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 AdvSceneSwitcher::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);
}
}

69
src/switch-priority.cpp Normal file
View File

@@ -0,0 +1,69 @@
#include <algorithm>
#include "headers/advanced-scene-switcher.hpp"
void AdvSceneSwitcher::on_threadPriority_currentTextChanged(const QString &text)
{
if (loading || ui->threadPriority->count() !=
(int)switcher->threadPriorities.size())
return;
std::lock_guard<std::mutex> lock(switcher->m);
for (auto p : switcher->threadPriorities) {
if (p.name == text.toUtf8().constData()) {
switcher->threadPriority = p.value;
break;
}
}
}
void AdvSceneSwitcher::on_priorityUp_clicked()
{
int currentIndex = ui->priorityList->currentRow();
if (currentIndex != -1 && currentIndex != 0) {
ui->priorityList->insertItem(
currentIndex - 1,
ui->priorityList->takeItem(currentIndex));
ui->priorityList->setCurrentRow(currentIndex - 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->functionNamesByPriority.begin() +
currentIndex,
switcher->functionNamesByPriority.begin() +
currentIndex - 1);
}
}
void AdvSceneSwitcher::on_priorityDown_clicked()
{
int currentIndex = ui->priorityList->currentRow();
if (currentIndex != -1 &&
currentIndex != ui->priorityList->count() - 1) {
ui->priorityList->insertItem(
currentIndex + 1,
ui->priorityList->takeItem(currentIndex));
ui->priorityList->setCurrentRow(currentIndex + 1);
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(switcher->functionNamesByPriority.begin() +
currentIndex,
switcher->functionNamesByPriority.begin() +
currentIndex + 1);
}
}
bool SwitcherData::prioFuncsValid()
{
auto it = std::unique(functionNamesByPriority.begin(),
functionNamesByPriority.end());
bool wasUnique = (it == functionNamesByPriority.end());
if (!wasUnique)
return false;
for (int p : functionNamesByPriority) {
if (p < 0 || p > 8)
return false;
}
return true;
}

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

@@ -0,0 +1,205 @@
#include <random>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::on_randomAdd_clicked()
{
ui->randomAdd->disconnect(addPulse);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->randomSwitches.emplace_back();
QListWidgetItem *item;
item = new QListWidgetItem(ui->randomSwitches);
ui->randomSwitches->addItem(item);
RandomSwitchWidget *sw =
new RandomSwitchWidget(&switcher->randomSwitches.back());
item->setSizeHint(sw->minimumSizeHint());
ui->randomSwitches->setItemWidget(item, sw);
}
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)
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)
continue;
scene = r.scene;
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();
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_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");
switcher->randomSwitches.emplace_back(
GetWeakSourceByName(scene),
GetWeakTransitionByName(transition), delay);
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(&s);
item->setSizeHint(sw->minimumSizeHint());
ui->randomSwitches->setItemWidget(item, sw);
}
if (switcher->randomSwitches.size() == 0)
addPulse = PulseWidget(ui->randomAdd, QColor(Qt::green));
if (switcher->switchIfNotMatching != RANDOM_SWITCH)
PulseWidget(ui->randomDisabledWarning, QColor(Qt::red),
QColor(0, 0, 0, 0), "QLabel ");
else
ui->randomDisabledWarning->setVisible(false);
}
RandomSwitchWidget::RandomSwitchWidget(RandomSwitch *s) : SwitchWidget(s, false)
{
delay = new QDoubleSpinBox();
switchLabel = new QLabel("If no switch condition is met switch to ");
usingLabel = new QLabel("using");
forLabel = new QLabel("for");
QWidget::connect(delay, SIGNAL(valueChanged(double)), this,
SLOT(DelayChanged(double)));
delay->setSuffix("s");
delay->setMaximum(999999999.9);
if (s) {
delay->setValue(s->delay);
}
setStyleSheet("* { background-color: transparent; }");
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(switchLabel);
mainLayout->addWidget(scenes);
mainLayout->addWidget(usingLabel);
mainLayout->addWidget(transitions);
mainLayout->addWidget(forLabel);
mainLayout->addWidget(delay);
mainLayout->addStretch();
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,394 @@
#include <QTimer>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
static QMetaObject::Connection addPulse;
void 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::on_showFrame_clicked()
{
switcher->showFrame = !switcher->showFrame;
if (switcher->showFrame) {
ui->showFrame->setText("Hide guide frames");
showCurrentFrame(ui->screenRegionSwitches);
} else {
ui->showFrame->setText("Show guide frames");
clearFrames(ui->screenRegionSwitches);
}
}
void AdvSceneSwitcher::on_screenRegionSwitches_currentRowChanged(int idx)
{
UNUSED_PARAMETER(idx);
if (loading)
return;
if (switcher->showFrame) {
clearFrames(ui->screenRegionSwitches);
showCurrentFrame(ui->screenRegionSwitches);
}
}
void AdvSceneSwitcher::on_screenRegionAdd_clicked()
{
ui->screenRegionAdd->disconnect(addPulse);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->screenRegionSwitches.emplace_back();
QListWidgetItem *item;
item = new QListWidgetItem(ui->screenRegionSwitches);
ui->screenRegionSwitches->addItem(item);
ScreenRegionWidget *sw =
new ScreenRegionWidget(&switcher->screenRegionSwitches.back());
item->setSizeHint(sw->minimumSizeHint());
ui->screenRegionSwitches->setItemWidget(item, sw);
}
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]);
}
void SwitcherData::checkScreenRegionSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
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) {
match = true;
scene = s.scene;
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();
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_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");
switcher->screenRegionSwitches.emplace_back(
GetWeakSourceByName(scene),
GetWeakTransitionByName(transition), minX, minY, maxX,
maxY);
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(&s);
item->setSizeHint(sw->minimumSizeHint());
ui->screenRegionSwitches->setItemWidget(item, sw);
}
if (switcher->screenRegionSwitches.size() == 0)
addPulse = PulseWidget(ui->screenRegionAdd, QColor(Qt::green));
// screen region cursor position
QTimer *screenRegionTimer = new QTimer(this);
connect(screenRegionTimer, SIGNAL(timeout()), this,
SLOT(updateScreenRegionCursorPos()));
screenRegionTimer->start(1000);
}
ScreenRegionWidget::ScreenRegionWidget(ScreenRegionSwitch *s)
: SwitchWidget(s, false)
{
minX = new QSpinBox();
minY = new QSpinBox();
maxX = new QSpinBox();
maxY = new QSpinBox();
cursorLabel = new QLabel("If cursor is in");
xLabel = new QLabel("x");
switchLabel = new QLabel("switch to");
usingLabel = new QLabel("using");
minX->setPrefix("Min X: ");
minY->setPrefix("Min Y: ");
maxX->setPrefix("Max X: ");
maxY->setPrefix("Max Y: ");
minX->setMinimum(-1000000);
minY->setMinimum(-1000000);
maxX->setMinimum(-1000000);
maxY->setMinimum(-1000000);
minX->setMaximum(1000000);
minY->setMaximum(1000000);
maxX->setMaximum(1000000);
maxY->setMaximum(1000000);
QWidget::connect(minX, SIGNAL(valueChanged(int)), this,
SLOT(MinXChanged(int)));
QWidget::connect(minY, SIGNAL(valueChanged(int)), this,
SLOT(MinYChanged(int)));
QWidget::connect(maxX, SIGNAL(valueChanged(int)), this,
SLOT(MaxXChanged(int)));
QWidget::connect(maxY, SIGNAL(valueChanged(int)), this,
SLOT(MaxYChanged(int)));
if (s) {
minX->setValue(s->minX);
minY->setValue(s->minY);
maxX->setValue(s->maxX);
maxY->setValue(s->maxY);
}
setStyleSheet("* { background-color: transparent; }");
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(cursorLabel);
mainLayout->addWidget(minX);
mainLayout->addWidget(minY);
mainLayout->addWidget(xLabel);
mainLayout->addWidget(maxX);
mainLayout->addWidget(maxY);
mainLayout->addWidget(switchLabel);
mainLayout->addWidget(scenes);
mainLayout->addWidget(usingLabel);
mainLayout->addWidget(transitions);
mainLayout->addStretch();
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::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);
}

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

@@ -0,0 +1,419 @@
#include <QFileDialog>
#include <QTextStream>
#include <QMessageBox>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::on_sceneSequenceAdd_clicked()
{
ui->sceneSequenceAdd->disconnect(addPulse);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->sceneSequenceSwitches.emplace_back();
QListWidgetItem *item;
item = new QListWidgetItem(ui->sceneSequenceSwitches);
ui->sceneSequenceSwitches->addItem(item);
SequenceWidget *sw =
new SequenceWidget(&switcher->sceneSequenceSwitches.back());
item->setSizeHint(sw->minimumSizeHint());
ui->sceneSequenceSwitches->setItemWidget(item, sw);
}
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("Save Scene Sequence 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->saveSceneSequenceSwitches(obj);
obs_data_save_json(obj, file.fileName().toUtf8().constData());
obs_data_release(obj);
}
void AdvSceneSwitcher::on_sceneSequenceLoad_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
QString directory = QFileDialog::getOpenFileName(
this, tr("Select a file to read Scene Sequence 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;
Msgbox.setText(
"Advanced Scene Switcher failed to import settings!");
Msgbox.exec();
return;
}
switcher->loadSceneSequenceSwitches(obj);
obs_data_release(obj);
QMessageBox Msgbox;
Msgbox.setText(
"Advanced Scene Switcher settings imported successfully!");
Msgbox.exec();
close();
}
void SwitcherData::checkSceneSequence(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition,
std::unique_lock<std::mutex> &lock)
{
obs_source_t *currentSource = obs_frontend_get_current_scene();
obs_weak_source_t *ws = obs_source_get_weak_source(currentSource);
for (SceneSequenceSwitch &s : sceneSequenceSwitches) {
if (!s.initialized())
continue;
if (s.startScene == ws) {
int dur = s.delay * 1000 - interval;
if (dur > 0) {
waitScene = currentSource;
if (verbose)
s.logSleep(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.scene;
transition = s.transition;
if (verbose)
s.logMatch();
}
obs_source_release(currentSource2);
break;
}
}
obs_source_release(currentSource);
obs_weak_source_release(ws);
}
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();
obs_source_t *source1 =
obs_weak_source_get_source(s.startScene);
obs_source_t *source2 = obs_weak_source_get_source(s.scene);
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_int(array_obj, "delayMultiplier",
s.delayMultiplier);
obs_data_array_push_back(sceneSequenceArray, 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", 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);
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 = obs_data_get_double(array_obj, "delay");
int delayMultiplier =
obs_data_get_int(array_obj, "delayMultiplier");
if (delayMultiplier == 0 ||
(delayMultiplier != 1 && delayMultiplier % 60 != 0))
delayMultiplier = 1;
switcher->sceneSequenceSwitches.emplace_back(
GetWeakSourceByName(scene1),
GetWeakSourceByName(scene2),
GetWeakTransitionByName(transition), delay,
delayMultiplier,
(strcmp(scene2, previous_scene_name) == 0));
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(&s);
item->setSizeHint(sw->minimumSizeHint());
ui->sceneSequenceSwitches->setItemWidget(item, sw);
}
if (switcher->sceneSequenceSwitches.size() == 0)
addPulse = PulseWidget(ui->sceneSequenceAdd, QColor(Qt::green));
}
bool SceneSequenceSwitch::initialized()
{
return SceneSwitcherEntry::initialized() && startScene;
}
bool SceneSequenceSwitch::valid()
{
return !initialized() ||
(SceneSwitcherEntry::valid() && WeakSourceValid(startScene));
}
void SceneSequenceSwitch::logSleep(int dur)
{
blog(LOG_INFO, "sequence sleep %d", dur);
}
void populateDelayUnits(QComboBox *list)
{
list->addItem("seconds");
list->addItem("minutes");
list->addItem("hours");
}
SequenceWidget::SequenceWidget(SceneSequenceSwitch *s) : SwitchWidget(s)
{
delay = new QDoubleSpinBox();
delayUnits = new QComboBox();
startScenes = new QComboBox();
whenLabel = new QLabel("When");
switchLabel = new QLabel("is active switch to");
afterLabel = new QLabel("after");
usingLabel = new QLabel("using");
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 &)));
delay->setMaximum(99999.000000);
AdvSceneSwitcher::populateSceneSelection(startScenes, false);
populateDelayUnits(delayUnits);
if (s) {
switch (s->delayMultiplier) {
case 1:
delayUnits->setCurrentIndex(0);
break;
case 60:
delayUnits->setCurrentIndex(1);
break;
case 3600:
delayUnits->setCurrentIndex(2);
break;
default:
delayUnits->setCurrentIndex(0);
}
startScenes->setCurrentText(
GetWeakSourceName(s->startScene).c_str());
}
setStyleSheet("* { background-color: transparent; }");
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(whenLabel);
mainLayout->addWidget(startScenes);
mainLayout->addWidget(switchLabel);
mainLayout->addWidget(scenes);
mainLayout->addWidget(afterLabel);
mainLayout->addWidget(delay);
mainLayout->addWidget(delayUnits);
mainLayout->addWidget(usingLabel);
mainLayout->addWidget(transitions);
mainLayout->addStretch();
setLayout(mainLayout);
switchData = s;
loading = false;
UpdateDelay();
}
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::StartSceneChanged(const QString &text)
{
if (loading || !switchData)
return;
std::lock_guard<std::mutex> lock(switcher->m);
switchData->startScene = GetWeakSourceByQString(text);
}

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

@@ -0,0 +1,309 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::on_timeAdd_clicked()
{
ui->timeAdd->disconnect(addPulse);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->timeSwitches.emplace_back();
QListWidgetItem *item;
item = new QListWidgetItem(ui->timeSwitches);
ui->timeSwitches->addItem(item);
TimeSwitchWidget *sw =
new TimeSwitchWidget(&switcher->timeSwitches.back());
item->setSizeHint(sw->minimumSizeHint());
ui->timeSwitches->setItemWidget(item, sw);
}
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)
{
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 (timeSwitches.size() == 0)
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.usePreviousScene) ? previousScene : s.scene;
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();
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_int(array_obj, "trigger", s.trigger);
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");
timeTrigger trigger =
(timeTrigger)obs_data_get_int(array_obj, "trigger");
QTime time = QTime::fromString(
obs_data_get_string(array_obj, "time"));
switcher->timeSwitches.emplace_back(
GetWeakSourceByName(scene),
GetWeakTransitionByName(transition), trigger, time,
(strcmp(scene, previous_scene_name) == 0));
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(&s);
item->setSizeHint(sw->minimumSizeHint());
ui->timeSwitches->setItemWidget(item, sw);
}
if (switcher->timeSwitches.size() == 0)
addPulse = PulseWidget(ui->timeAdd, QColor(Qt::green));
}
void populateTriggers(QComboBox *list)
{
list->addItem("On any day");
list->addItem("Mondays");
list->addItem("Tuesdays");
list->addItem("Wednesdays");
list->addItem("Thursdays");
list->addItem("Fridays");
list->addItem("Saturdays");
list->addItem("Sundays");
list->addItem("Atfer streaming/recording start");
list->setItemData(
8,
"The time relative to the start of streaming / recording will be used",
Qt::ToolTipRole);
}
TimeSwitchWidget::TimeSwitchWidget(TimeSwitch *s) : SwitchWidget(s)
{
triggers = new QComboBox();
time = new QTimeEdit();
atLabel = new QLabel("at");
switchLabel = new QLabel("switch to");
usingLabel = new QLabel("using");
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);
}
setStyleSheet("* { background-color: transparent; }");
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(triggers);
mainLayout->addWidget(atLabel);
mainLayout->addWidget(time);
mainLayout->addWidget(switchLabel);
mainLayout->addWidget(scenes);
mainLayout->addWidget(usingLabel);
mainLayout->addWidget(transitions);
mainLayout->addStretch();
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;
}

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

@@ -0,0 +1,519 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
void AdvSceneSwitcher::on_transitionsAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->sceneTransitions.emplace_back();
QListWidgetItem *item;
item = new QListWidgetItem(ui->sceneTransitions);
ui->sceneTransitions->addItem(item);
TransitionSwitchWidget *sw =
new TransitionSwitchWidget(&switcher->sceneTransitions.back());
item->setSizeHint(sw->minimumSizeHint());
ui->sceneTransitions->setItemWidget(item, sw);
}
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();
QListWidgetItem *item;
item = new QListWidgetItem(ui->defaultTransitions);
ui->defaultTransitions->addItem(item);
DefTransitionSwitchWidget *sw = new DefTransitionSwitchWidget(
&switcher->defaultSceneTransitions.back());
item->setSizeHint(sw->minimumSizeHint());
ui->defaultTransitions->setItemWidget(item, sw);
}
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::setDefaultSceneTransitions()
{
if (changedDefTransitionRecently)
return;
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) {
if (!s.initialized())
continue;
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)
s.logMatch();
obs_source_release(transition);
break;
}
}
obs_source_release(currentSource);
obs_weak_source_release(ws);
changedDefTransitionRecently = true;
}
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(&s);
item->setSizeHint(sw->minimumSizeHint());
ui->sceneTransitions->setItemWidget(item, sw);
}
for (auto &s : switcher->defaultSceneTransitions) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->defaultTransitions);
ui->defaultTransitions->addItem(item);
DefTransitionSwitchWidget *sw =
new DefTransitionSwitchWidget(&s);
item->setSizeHint(sw->minimumSizeHint());
ui->defaultTransitions->setItemWidget(item, sw);
}
ui->transitionOverridecheckBox->setChecked(
switcher->tansitionOverrideOverride);
}
bool SceneTransition::initialized()
{
return SceneSwitcherEntry::initialized() && scene2;
}
bool SceneTransition::valid()
{
return !initialized() ||
(SceneSwitcherEntry::valid() && WeakSourceValid(scene2));
}
TransitionSwitchWidget::TransitionSwitchWidget(SceneTransition *s)
: SwitchWidget(s, false)
{
scenes2 = new QComboBox();
switchLabel = new QLabel("Switch from scene");
toLabel = new QLabel("to scene");
usingLabel = new QLabel("using transition");
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());
}
setStyleSheet("* { background-color: transparent; }");
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(switchLabel);
mainLayout->addWidget(scenes);
mainLayout->addWidget(toLabel);
mainLayout->addWidget(scenes2);
mainLayout->addWidget(usingLabel);
mainLayout->addWidget(transitions);
mainLayout->addStretch();
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(DefaultSceneTransition *s)
: SwitchWidget(s, false)
{
whenLabel = new QLabel("When scene");
switchLabel =
new QLabel("is active change default scene transition to ");
setStyleSheet("* { background-color: transparent; }");
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(whenLabel);
mainLayout->addWidget(scenes);
mainLayout->addWidget(switchLabel);
mainLayout->addWidget(transitions);
mainLayout->addStretch();
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);
}

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

@@ -0,0 +1,477 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
static QMetaObject::Connection addPulse;
void AdvSceneSwitcher::on_windowAdd_clicked()
{
ui->windowAdd->disconnect(addPulse);
std::lock_guard<std::mutex> lock(switcher->m);
switcher->windowSwitches.emplace_back();
QListWidgetItem *item;
item = new QListWidgetItem(ui->windowSwitches);
ui->windowSwitches->addItem(item);
WindowSwitchWidget *sw =
new WindowSwitchWidget(&switcher->windowSwitches.back());
item->setSizeHint(sw->minimumSizeHint());
ui->windowSwitches->setItemWidget(item, sw);
}
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());
}
}
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;
}
}
}
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 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 (WindowSwitch &s : windowSwitches) {
if (!s.initialized())
continue;
// True if fullscreen is disabled OR current window is fullscreen
bool fullscreen = (!s.fullscreen || isFullscreen(s.window));
// True if maximized is disabled OR current window is maximized
bool max = (!s.maximized || isMaximized(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 && (max && (focus || ignore)))) {
match = true;
scene = s.scene;
transition = s.transition;
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();
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, "maximized", s.maximized);
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");
#if __APPLE__
// TODO:
// not implemented on MacOS as I cannot test it
bool maximized = false;
#else
bool maximized = obs_data_get_bool(array_obj, "maximized");
#endif
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,
maximized, 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 AdvSceneSwitcher::setupTitleTab()
{
for (auto &s : switcher->windowSwitches) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->windowSwitches);
ui->windowSwitches->addItem(item);
WindowSwitchWidget *sw = new WindowSwitchWidget(&s);
item->setSizeHint(sw->minimumSizeHint());
ui->windowSwitches->setItemWidget(item, sw);
}
if (switcher->windowSwitches.size() == 0)
addPulse = PulseWidget(ui->windowAdd, QColor(Qt::green));
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);
}
}
WindowSwitchWidget::WindowSwitchWidget(WindowSwitch *s) : SwitchWidget(s, false)
{
windows = new QComboBox();
fullscreen = new QCheckBox("if fullscreen");
maximized = new QCheckBox("if maximized");
focused = new QCheckBox("if 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);
}
setStyleSheet("* { background-color: transparent; }");
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(windows);
mainLayout->addWidget(scenes);
mainLayout->addWidget(transitions);
mainLayout->addWidget(scenes);
mainLayout->addWidget(fullscreen);
mainLayout->addWidget(maximized);
mainLayout->addWidget(focused);
mainLayout->addStretch();
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,96 @@
#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 < pauseScenesSwitches.size(); i++) {
OBSWeakSource &scene = pauseScenesSwitches[i];
if (!WeakSourceValid(scene))
pauseScenesSwitches.erase(pauseScenesSwitches.begin() +
i--);
}
for (size_t i = 0; i < sceneSequenceSwitches.size(); i++) {
SceneSequenceSwitch &s = sceneSequenceSwitches[i];
if (!s.valid())
sceneSequenceSwitches.erase(
sceneSequenceSwitches.begin() + i--);
}
if (!WeakSourceValid(autoStopScene)) {
autoStopScene = nullptr;
autoStopEnable = false;
}
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--);
}
}

1003
src/volume-control.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,267 @@
#include <windows.h>
#include <util/platform.h>
#include "../headers/advanced-scene-switcher.hpp"
#include <TlHelp32.h>
#include <Psapi.h>
#include <locale>
#include <codecvt>
#include <string>
#define MAX_SEARCH 1000
static bool GetWindowTitle(HWND window, std::string &title)
{
size_t len = (size_t)GetWindowTextLengthW(window);
std::wstring wtitle;
wtitle.resize(len);
if (!GetWindowTextW(window, &wtitle[0], (int)len + 1))
return false;
len = os_wcs_to_utf8(wtitle.c_str(), 0, nullptr, 0);
title.resize(len);
os_wcs_to_utf8(wtitle.c_str(), 0, &title[0], len + 1);
return true;
}
static bool WindowValid(HWND window)
{
LONG_PTR styles;
DWORD id;
if (!IsWindowVisible(window))
return false;
GetWindowThreadProcessId(window, &id);
if (id == GetCurrentProcessId())
return false;
styles = GetWindowLongPtr(window, GWL_STYLE);
if (styles & WS_CHILD)
return false;
return true;
}
BOOL CALLBACK GetTitleCB(HWND hwnd, LPARAM lParam)
{
if (!WindowValid(hwnd)) {
return TRUE;
}
std::string title;
GetWindowTitle(hwnd, title);
if (title.empty())
return TRUE;
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++;
}
}
void GetWindowList(std::vector<std::string> &windows)
{
windows.resize(0);
EnumWindowsWithMetro(GetTitleCB, reinterpret_cast<LPARAM>(&windows));
}
void GetWindowList(QStringList &windows)
{
windows.clear();
std::vector<std::string> w;
GetWindowList(w);
for (auto window : w) {
windows << QString::fromStdString(window);
}
}
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.*/
if (GetCurrentProcessId() == pid) {
title = "OBS";
return;
}
GetWindowTitle(window, title);
}
std::pair<int, int> getCursorPos()
{
std::pair<int, int> pos(0, 0);
POINT cursorPos;
if (GetPhysicalCursorPos(&cursorPos)) {
pos.first = cursorPos.x;
pos.second = cursorPos.y;
}
return pos;
}
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 = getHWNDfromTitle(title);
if (!hwnd)
return false;
monitorInfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST),
&monitorInfo);
if (hwnd && hwnd != GetDesktopWindow() && hwnd != GetShellWindow()) {
if (IsZoomed(hwnd)) {
return true;
}
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;
}
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;
}
void GetProcessList(QStringList &processes)
{
HANDLE procSnapshot;
PROCESSENTRY32 procEntry;
procSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (procSnapshot == INVALID_HANDLE_VALUE)
return;
procEntry.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(procSnapshot, &procEntry)) {
CloseHandle(procSnapshot);
return;
}
do {
QString tempexe = QString::fromWCharArray(procEntry.szExeFile);
if (tempexe == "System")
continue;
if (tempexe == "[System Process]")
continue;
if (processes.contains(tempexe))
continue;
processes.append(tempexe);
} while (Process32Next(procSnapshot, &procEntry));
CloseHandle(procSnapshot);
}
bool isInFocus(const QString &executable)
{
// only checks if the current foreground window is from the same executable,
// may return true for any window from a program
HWND foregroundWindow = GetForegroundWindow();
DWORD processId = 0;
GetWindowThreadProcessId(foregroundWindow, &processId);
HANDLE process = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
if (process == NULL)
return false;
WCHAR executablePath[600];
GetModuleFileNameEx(process, 0, executablePath, 600);
CloseHandle(process);
QString file = QString::fromWCharArray(executablePath)
.split(QRegularExpression("(/|\\\\)"))
.back();
// True if executable switch equals current window
bool equals = (executable == file);
// True if executable switch matches current window
bool matches = file.contains(QRegularExpression(executable));
return (equals || matches);
}
int getLastInputTime()
{
LASTINPUTINFO lastInputInfo;
lastInputInfo.cbSize = sizeof(LASTINPUTINFO);
if (GetLastInputInfo(&lastInputInfo))
return lastInputInfo.dwTime;
return 0;
}
int getTime()
{
return GetTickCount();
}
int secondsSinceLastInput()
{
return (getTime() - getLastInputTime()) / 1000;
}

View File

@@ -1,288 +0,0 @@
#pragma once
#include <condition_variable>
#include <chrono>
#include <string>
#include <vector>
#include <thread>
#include <regex>
#include <mutex>
#include <fstream>
#include "utility.hpp"
#define DEFAULT_INTERVAL 300
#define READ_FILE_FUNC "File based"
#define ROUND_TRIP_FUNC "Scene Sequence"
#define IDLE_FUNC "Idle detection"
#define EXE_FUNC "Executable"
#define SCREEN_REGION_FUNC "Screen region"
#define WINDOW_TITLE_FUNC "Window title"
#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
using namespace std;
struct SceneSwitch
{
OBSWeakSource scene;
string window;
OBSWeakSource transition;
bool fullscreen;
inline SceneSwitch(
OBSWeakSource scene_, const char* window_, OBSWeakSource transition_, bool fullscreen_)
: scene(scene_)
, window(window_)
, transition(transition_)
, fullscreen(fullscreen_)
{
}
};
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;
string regionStr;
inline ScreenRegionSwitch(OBSWeakSource scene_, OBSWeakSource transition_, int minX_, int minY_,
int maxX_, int maxY_, string regionStr_)
: scene(scene_)
, transition(transition_)
, minX(minX_)
, minY(minY_)
, maxX(maxX_)
, maxY(maxY_)
, regionStr(regionStr_)
{
}
};
struct SceneRoundTripSwitch
{
OBSWeakSource scene1;
OBSWeakSource scene2;
OBSWeakSource transition;
int delay;
string sceneRoundTripStr;
inline SceneRoundTripSwitch(OBSWeakSource scene1_, OBSWeakSource scene2_,
OBSWeakSource transition_, int delay_, string str)
: scene1(scene1_)
, scene2(scene2_)
, transition(transition_)
, delay(delay_)
, sceneRoundTripStr(str)
{
}
};
struct SceneTransition
{
OBSWeakSource scene1;
OBSWeakSource scene2;
OBSWeakSource transition;
string sceneTransitionStr;
inline SceneTransition(OBSWeakSource scene1_, OBSWeakSource scene2_, OBSWeakSource transition_,
string sceneTransitionStr_)
: scene1(scene1_)
, scene2(scene2_)
, transition(transition_)
, sceneTransitionStr(sceneTransitionStr_)
{
}
};
struct DefaultSceneTransition
{
OBSWeakSource scene;
OBSWeakSource transition;
string sceneTransitionStr;
inline DefaultSceneTransition(OBSWeakSource scene_, OBSWeakSource transition_,
string sceneTransitionStr_)
: scene(scene_)
, transition(transition_)
, sceneTransitionStr(sceneTransitionStr_)
{
}
};
struct FileIOData
{
bool readEnabled;
string readPath;
bool writeEnabled;
string writePath;
};
struct IdleData
{
bool idleEnable;
int time;
OBSWeakSource scene;
OBSWeakSource transition;
};
struct SwitcherData
{
thread th;
condition_variable cv;
mutex m;
mutex transitionMutex;
bool transitionActive = false;
bool waitForTransition = false;
condition_variable transitionCv;
bool stop = false;
vector<SceneSwitch> switches;
string lastTitle;
OBSWeakSource nonMatchingScene;
int interval = DEFAULT_INTERVAL;
bool switchIfNotMatching = false;
bool startAtLaunch = false;
vector<ScreenRegionSwitch> screenRegionSwitches;
vector<OBSWeakSource> pauseScenesSwitches;
vector<string> pauseWindowsSwitches;
vector<string> ignoreWindowsSwitches;
vector<SceneRoundTripSwitch> sceneRoundTripSwitches;
bool autoStopEnable = false;
OBSWeakSource autoStopScene;
string waitSceneName; //indicates which scene was active when we startet waiting on something
vector<SceneTransition> sceneTransitions;
vector<DefaultSceneTransition> defaultSceneTransitions;
vector<ExecutableSceneSwitch> executableSwitches;
FileIOData fileIO;
IdleData idleData;
vector<string> ignoreIdleWindows;
vector<string> functionNamesByPriority = vector<string>{
string(DEFAULT_PRIORITY_0),
string(DEFAULT_PRIORITY_1),
string(DEFAULT_PRIORITY_2),
string(DEFAULT_PRIORITY_3),
string(DEFAULT_PRIORITY_4),
string(DEFAULT_PRIORITY_5),
};
void Thread();
void Start();
void Stop();
bool sceneChangedDuringWait();
bool prioFuncsValid();
void writeSceneInfoToFile();
void setDefaultSceneTransitions(unique_lock<mutex>& lock);
void autoStopStreamAndRecording();
bool checkPause();
void checkSceneRoundTrip(bool& match, OBSWeakSource& scene, OBSWeakSource& transition, unique_lock<mutex>& lock);
void checkIdleSwitch(bool& match, OBSWeakSource& scene, OBSWeakSource& transition);
void checkWindowTitleSwitch(bool& match, OBSWeakSource& scene, OBSWeakSource& transition);
void checkExeSwitch(bool& match, OBSWeakSource& scene, OBSWeakSource& transition);
void checkScreenRegionSwitch(bool& match, OBSWeakSource& scene, OBSWeakSource& transition);
void checkSwitchInfoFromFile(bool& match, OBSWeakSource& scene, OBSWeakSource& transition);
void Prune()
{
for (size_t i = 0; i < switches.size(); i++)
{
SceneSwitch& s = switches[i];
if (!WeakSourceValid(s.scene) || !WeakSourceValid(s.transition))
switches.erase(switches.begin() + i--);
}
if (nonMatchingScene && !WeakSourceValid(nonMatchingScene))
{
switchIfNotMatching = false;
nonMatchingScene = nullptr;
}
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) || !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--);
}
if (!WeakSourceValid(idleData.scene) || !WeakSourceValid(idleData.transition))
{
idleData.idleEnable = false;
}
}
inline ~SwitcherData()
{
Stop();
}
};

View File

@@ -1,140 +0,0 @@
#pragma once
#include <obs.hpp>
#include <QString>
#include <obs-frontend-api.h>
#include "switcher-data-structs.hpp"
using namespace std;
static inline bool WeakSourceValid(obs_weak_source_t* ws)
{
obs_source_t* source = obs_weak_source_get_source(ws);
if (source)
obs_source_release(source);
return !!source;
}
static inline QString MakeSwitchName(
const QString& scene, const QString& value, const QString& transition, bool fullscreen)
{
if (!fullscreen)
return QStringLiteral("[") + scene + QStringLiteral(", ") + transition
+ QStringLiteral("]: ") + value;
return QStringLiteral("[") + scene + QStringLiteral(", ") + transition + QStringLiteral("]: ")
+ value + QStringLiteral(" (only if window is fullscreen)");
}
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 window is 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, int 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 string GetWeakSourceName(obs_weak_source_t* weak_source)
{
string name;
obs_source_t* source = obs_weak_source_get_source(weak_source);
if (source)
{
name = obs_source_get_name(source);
obs_source_release(source);
}
return name;
}
static inline OBSWeakSource GetWeakSourceByName(const char* name)
{
OBSWeakSource weak;
obs_source_t* source = obs_get_source_by_name(name);
if (source)
{
weak = obs_source_get_weak_source(source);
obs_weak_source_release(weak);
obs_source_release(source);
}
return weak;
}
static inline OBSWeakSource GetWeakSourceByQString(const QString& name)
{
return GetWeakSourceByName(name.toUtf8().constData());
}
static inline OBSWeakSource GetWeakTransitionByName(const char* transitionName)
{
OBSWeakSource weak;
obs_source_t* source = nullptr;
if (strcmp(transitionName, "Default") == 0)
{
source = obs_frontend_get_current_transition();
weak = obs_source_get_weak_source(source);
obs_source_release(source);
obs_weak_source_release(weak);
return weak;
}
obs_frontend_source_list* transitions = new obs_frontend_source_list();
obs_frontend_get_transitions(transitions);
bool match = false;
for (size_t i = 0; i < transitions->sources.num; i++)
{
const char* name = obs_source_get_name(transitions->sources.array[i]);
if (strcmp(transitionName, name) == 0)
{
match = true;
source = transitions->sources.array[i];
break;
}
}
if (match){
weak = obs_source_get_weak_source(source);
obs_weak_source_release(weak);
}
obs_frontend_source_list_free(transitions);
return weak;
}
static inline OBSWeakSource GetWeakTransitionByQString(const QString& name)
{
return GetWeakTransitionByName(name.toUtf8().constData());
}

View File

@@ -1,276 +0,0 @@
#include <obs-module.h>
#include "advanced-scene-switcher.hpp"
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();
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);
int idx = FindByData(windowName);
if (idx == -1)
{
lock_guard<mutex> lock(switcher->m);
switcher->switches.emplace_back(
source, windowName.toUtf8().constData(), transition, fullscreen);
QListWidgetItem* item = new QListWidgetItem(text, ui->switches);
item->setData(Qt::UserRole, v);
}
else
{
QListWidgetItem* item = ui->switches->item(idx);
item->setText(text);
string window = windowName.toUtf8().constData();
{
lock_guard<mutex> lock(switcher->m);
for (auto& s : switcher->switches)
{
if (s.window == window)
{
s.scene = source;
s.transition = transition;
s.fullscreen = fullscreen;
break;
}
}
}
ui->switches->sortItems();
}
}
void SceneSwitcher::on_remove_clicked()
{
QListWidgetItem* item = ui->switches->currentItem();
if (!item)
return;
string window = item->data(Qt::UserRole).toString().toUtf8().constData();
{
lock_guard<mutex> lock(switcher->m);
auto& switches = switcher->switches;
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);
lock_guard<mutex> lock(switcher->m);
switcher->ignoreWindowsSwitches.emplace_back(windowName.toUtf8().constData());
ui->ignoreWindows->sortItems();
}
}
void SceneSwitcher::on_ignoreWindowsRemove_clicked()
{
QListWidgetItem* item = ui->ignoreWindows->currentItem();
if (!item)
return;
QString windowName = item->data(Qt::UserRole).toString();
{
lock_guard<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();
lock_guard<mutex> lock(switcher->m);
for (auto& s : switcher->switches)
{
if (window.compare(s.window.c_str()) == 0)
{
string name = GetWeakSourceName(s.scene);
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);
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();
lock_guard<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)
{
//check if title should be ignored
string title;
GetCurrentWindowTitle(title);
for (auto& window : ignoreWindowsSwitches)
{
if (window == title)
{
title = lastTitle;
break;
}
}
lastTitle = title;
//direct match
for (SceneSwitch& s : switches)
{
if (s.window == title)
{
match = !s.fullscreen || (s.fullscreen && isFullscreen());
scene = s.scene;
transition = s.transition;
return;
}
}
//regex match
for (SceneSwitch& s : switches)
{
try
{
bool matches = regex_match(title, regex(s.window));
if (matches)
{
match = !s.fullscreen || (s.fullscreen && isFullscreen());
scene = s.scene;
transition = s.transition;
}
}
catch (const regex_error&)
{
}
}
}