Compare commits

...

236 Commits

Author SHA1 Message Date
WarmUpTill
f13beaead2 Reduce log spam in case of invalid token
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
The caching mechanism for the token validity checks did not have the
desired effect, and the Twitch channel to user id mapping function was
attempting to resolve names even with an invalid token.
2025-05-08 21:10:13 +02:00
WarmUpTill
13dba6527d Exclude jsoncons files from release folder 2025-05-08 21:10:13 +02:00
WarmUpTill
b78a6510a9 Start the plugin on first boot 2025-05-08 21:10:13 +02:00
WarmUpTill
6c22f438b6 Disable macro add button highlight when importing macros 2025-05-08 21:10:13 +02:00
WarmUpTill
347abe6c84 Enable macro highlighting by default 2025-05-08 21:10:13 +02:00
WarmUpTill
230863adda Lazy initialize macro segment widgets
This should make it less time consuming to accidentally switch between
macros with a large amount of macro segments
2025-05-08 21:10:13 +02:00
WarmUpTill
f59478fac7 Cleanup 2025-05-08 21:10:13 +02:00
WarmUpTill
8adac79cd8 Rework option to run macro actions in parallel to other macros 2025-05-08 20:44:56 +02:00
WarmUpTill
ea93c44db7 Add option to check macro conditions in parallel to other macros 2025-05-08 20:44:56 +02:00
WarmUpTill
9e20b341d8 Rework Macro constructors to support passing GlobalMacroSettings 2025-05-08 20:44:56 +02:00
WarmUpTill
232cbb06f6 Disable Twitch event timestamp verification by default
Some checks are pending
debian-build / build (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
It brakes too often to be worth it as an option to be enabled by
default.
2025-05-07 18:33:04 +02:00
WarmUpTill
e9d57a0f4a Refactor log helpers and add option to disable logging
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-05-01 19:55:28 +02:00
WarmUpTill
9633a61a65 Add option to disable Twitch event timestamp verification
Some checks are pending
debian-build / build (push) Waiting to run
Check locale / ubuntu64 (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
2025-05-01 12:46:20 +02:00
WarmUpTill
0643b250e1 Fix typos 2025-05-01 12:46:20 +02:00
WarmUpTill
b52738881f Fix "HTTP" action failing to send data on MacOS
Some checks are pending
debian-build / build (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
2025-04-30 21:25:44 +02:00
WarmUpTill
470d5ba3d7 Log HTTP errors when verbose logging is enabled 2025-04-30 21:25:44 +02:00
WarmUpTill
8326e72047 Add link to installation wiki page
Some checks failed
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-04-23 11:48:17 +02:00
WarmUpTill
12ab4d8cf9 Add option to query JSON strings and access JSON array elements
Some checks failed
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
2025-04-22 15:35:45 +02:00
WarmUpTill
295ec9eb81 Add more JSON tests 2025-04-22 15:35:45 +02:00
WarmUpTill
4c5dbd4b7c Add more JSON helpers 2025-04-22 15:35:45 +02:00
WarmUpTill
5568f92ad0 Move JSON helpers to lib 2025-04-22 15:35:45 +02:00
WarmUpTill
7c4c0056ce Remove libremidi patch 2025-04-22 15:35:45 +02:00
WarmUpTill
d2b4b1cc07 Add jsoncons 2025-04-22 15:35:45 +02:00
WarmUpTill
a3ca22d238 Cleanup CMakeLists.txt 2025-04-22 15:35:45 +02:00
WarmUpTill
c73542a3cc Fix variables not being re-resolved on Load()
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-04-17 19:22:52 +02:00
WarmUpTill
70bbc7cdac Implement proper timestamp validation for Twitch messages
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-04-11 19:02:37 +02:00
WarmUpTill
d892298995 Add missing "[adv-ss]" log tag 2025-04-11 19:02:37 +02:00
WarmUpTill
0fe31432be Add "previous scene" to the "scene has (not) changed" checks 2025-04-11 18:57:58 +02:00
WarmUpTill
aaa0113ccb Ignore Xerrors 2025-04-11 18:57:25 +02:00
WarmUpTill
b908954b46 CI: Add cmake setup step to Linux build
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-04-02 13:48:23 +02:00
WarmUpTill
b0eede8a85 Add "disable" effect to macro conditions using "ignore" logic selection 2025-04-02 13:48:23 +02:00
WarmUpTill
aa87911b71 Use cpp-httplib based HTTP action type
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
The goal is to remove the older, more limited version of the HTTP action
at some point in the future.
2025-03-30 14:06:19 +02:00
WarmUpTill
1b05019acc Add option to hide entries from action selection
Intended to be used to hide legacy version of actions types (e.g. HTTP)
2025-03-30 14:01:41 +02:00
WarmUpTill
78a5a2629d Hide the "remote" file check option
This option will be removed at some point in the future.
The http action should be used instead.
2025-03-30 14:01:41 +02:00
WarmUpTill
634270a978 Cleanup includes 2025-03-30 14:01:41 +02:00
WarmUpTill
78ba22e1e4 Hide "get settings" button when setting macro property value 2025-03-30 14:01:41 +02:00
WarmUpTill
aba5737a60 Update and clean up locale (qwe1154323937) 2025-03-30 14:01:41 +02:00
WarmUpTill
4315f7f621 Exclude unwanted files from sources archive 2025-03-30 14:01:41 +02:00
WarmUpTill
53c535962f Use tab key to switch to dialog controls and set default focus to input 2025-03-30 14:01:41 +02:00
WarmUpTill
1e718b78c7 Fall back to project version if git tag cannot be queried 2025-03-30 14:01:41 +02:00
WarmUpTill
b1d2156228 Update libremidi to v4.3.0 2025-03-30 14:01:41 +02:00
WarmUpTill
9c3c953c6b Ignore deprecation warnings for Qt 6.7 and above
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-03-22 18:09:51 +01:00
WarmUpTill
ae74f68db7 Add option to close projector windows 2025-03-22 18:09:51 +01:00
WarmUpTill
213f1bba36 Add "Any" entry of the reward selection only for Twitch condition 2025-03-22 18:09:51 +01:00
WarmUpTill
5a2cb0bd68 Add more scripting signals / procedures 2025-03-22 18:09:51 +01:00
WarmUpTill
23b461828b Add start start / stop callbacks 2025-03-22 18:09:51 +01:00
WarmUpTill
9944a1b03b Move interval reset handling 2025-03-22 18:09:51 +01:00
WarmUpTill
eaad4d1bbd Fix Twitch helper caches misbehaving
API calls with different sets of arguments could map to the same key
value pair, which resulted in unexpected behavior for functions using
those caches
2025-03-22 18:09:51 +01:00
WarmUpTill
17d9b73b9a Fix thread flood when Twitch event sub connection is lost
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-03-15 12:31:13 +01:00
WarmUpTill
d6185a6099 Add handling for Twitch API throttling 2025-03-15 12:31:13 +01:00
WarmUpTill
41c06b9edb Refactor Twitch condition event handling 2025-03-15 12:31:13 +01:00
WarmUpTill
d8807077da Add option to query reward information 2025-03-15 12:31:13 +01:00
WarmUpTill
34e125f82d Rework chache handling
* No longer provide multiple HTTP helper functions with the same name
* Default to cache not being used
* Explicitly use the cache for certain Twitch actions and conditions
* Clear caches on shutdown to avoid leaks being reported
2025-03-15 12:31:13 +01:00
WarmUpTill
d670b6d07e Move JSON temp var heper functions 2025-03-15 12:31:13 +01:00
WarmUpTill
7b0f985f18 Add macro properties for 'reward' object in redemption events 2025-03-15 12:31:13 +01:00
WarmUpTill
212be923f6 Add option to query user information 2025-03-15 12:31:13 +01:00
WarmUpTill
88fcb57e9f Cleanup 2025-03-15 12:31:13 +01:00
WarmUpTill
9b609c118f Fix disabling chat emote only mode attempting to send chat messages 2025-03-15 12:31:13 +01:00
WarmUpTill
37c398d37a Indicate that "Set to condition/action value" should be avoided 2025-03-15 12:31:13 +01:00
WarmUpTill
a51b7f6b13 Fix UI theming issues
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-03-11 22:12:05 +01:00
WarmUpTill
808fd84b83 Remove Qt5 support 2025-03-11 22:12:05 +01:00
WarmUpTill
57bcea15f5 cmake: Replace find_qt with find_package 2025-03-10 19:48:01 +01:00
WarmUpTill
4e51b56b9b Add game capture condition 2025-03-10 18:49:07 +01:00
WarmUpTill
ef5cf41d34 Add OBS 31 support 2025-03-10 18:49:07 +01:00
WarmUpTill
911e79ea6a Fix OCR controls showing in place of color controls
This happened when OCR support was disabled at build time
2025-03-10 18:33:13 +01:00
WarmUpTill
d586177de0 Add screenshot condition
Some checks are pending
debian-build / build (push) Waiting to run
Check locale / ubuntu64 (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
2025-03-09 20:57:51 +01:00
WarmUpTill
202c36646c Fix UI issues when opening ItemSettingsDialogs
Some widgets were displayed incorrectly if the parent of the dialog
window is not the main settings window
2025-03-09 20:05:07 +01:00
WarmUpTill
ab5102f5ca Fix typo 2025-03-09 20:05:07 +01:00
WarmUpTill
e612fb99f6 Cleanup
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
* Reorder action type selection
* Refactor
2025-03-06 21:30:03 +01:00
WarmUpTill
691e77a69a Add option to generate random number 2025-03-06 21:30:03 +01:00
Kaito Udagawa
930118c61f Call Setup() after Load()
Some checks are pending
debian-build / build (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
Tesseract API depends on the languageCode and this must be reinitialized after Load
2025-03-06 08:43:54 +01:00
Kaito Udagawa
1e05f2f2ce Fix languageCode
LanguageCode is not respected by OCRParameters and settings properly.
2025-03-06 08:43:54 +01:00
WarmUpTill
ae25b4d023 Improve setting selection handling
Some checks are pending
debian-build / build (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
Will now try to keep the current selection if possible.
This can be useful when switching between source of the same time.
2025-03-05 14:16:04 +01:00
WarmUpTill
af055a12a1 Cleanup 2025-03-05 14:16:04 +01:00
WarmUpTill
7de28eedd3 Improve settings button selection handling
Will now try to keep the current button selection if possible.
This can be useful when switching between source of the same time.
2025-03-05 14:16:04 +01:00
WarmUpTill
8dfe81f522 Display failure if Twitch token account name cannot be fetched
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-02-27 19:52:41 +01:00
WarmUpTill
a1d8ae291d Add option to hide empty name warning in ItemSettingsDialog 2025-02-27 19:52:41 +01:00
WarmUpTill
eb6989527d Add support for libproc2 version 4.0.5
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-02-10 18:12:08 +01:00
WarmUpTill
2986a5dd96 Fix crash when actions were copied into a action queue
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
The steps added via AddPostLoadStep() from the copy were never executed.
Thus they might be invalid when RunPostLoadSteps() is next called.

This could result in a crash when a new action or condition is added
after e.g. a "Filter" action's TempVariableRef was copied, which adds a
PostLoadStep to resolve the macro segment reference.
2025-02-04 08:26:04 +01:00
WarmUpTill
816ee9b244 Fix crash when changing Twitch condition type 2025-02-04 08:26:04 +01:00
WarmUpTill
51b53bf948 Fix script actions / conditions not working after stopping macro
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-01-30 09:39:50 +01:00
WarmUpTill
2c5121ee94 Prevent crash when macro contains invalid action or condition 2025-01-30 09:39:50 +01:00
WarmUpTill
6a58684854 Fix import of incomplete settings not resetting duration to zero
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-01-23 19:00:51 +01:00
WarmUpTill
0b622fdbed Fix crash when importing settings 2025-01-23 19:00:51 +01:00
WarmUpTill
3bce8e075f CI: Switch to and adapt to Ubuntu 24 runner
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-01-15 21:36:54 +01:00
WarmUpTill
f598d84ea1 Fix freeze due to ResizingPlainTextEdit constantly resizing incorrectly 2025-01-15 21:36:35 +01:00
WarmUpTill
d7a244e80e Fix transition condition not considering transition overrides
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2024-12-31 12:47:20 +01:00
WarmUpTill
408002e96a Add support for OBS 31 in-tree builds
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2024-12-12 21:41:21 +01:00
WarmUpTill
50da3d3210 Fix "Media" condition showing the incorrect macro property selection
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2024-11-30 12:11:14 +01:00
WarmUpTill
9e13067769 Fix typo
Some checks failed
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2024-11-23 17:11:05 +01:00
WarmUpTill
821e768e76 Reduce screenshot selection area to area to be checked
Some checks failed
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
debian-build / build (push) Has been cancelled
2024-11-22 20:52:15 +01:00
WarmUpTill
4d2179448b Rename files 2024-11-22 20:52:15 +01:00
WarmUpTill
40d9002470 Update scripting examples
* Add more comments
* Add example showcasing how to register and set temp vars
2024-11-22 20:36:36 +01:00
WarmUpTill
7f1e310ca6 Add support to register, deregister, and set temp vars from scripts 2024-11-22 20:36:36 +01:00
WarmUpTill
66d02f4683 Fix typos 2024-11-22 20:36:36 +01:00
Arash Partow
0aae1b9978 Update ExprTk to 0.0.3
Some checks are pending
debian-build / build (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
2024-11-22 19:56:02 +01:00
WarmUpTill
db50822b05 Prepare to fix deprecation warning when using OBS 31
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
The following log message will be present in OBS 31 when
CheckIfTrayIsDisabled() is called:

DEPRECATION: obs_frontend_get_global_config is deprecated. Read from
global or user configuration explicitly instead.

It will be resolved when the minimum OBS version is bumped to OBS 31.
2024-11-02 08:28:52 +01:00
WarmUpTill
63a545b293 Fix "Date" condition returning true unexpectedly
This could happen if the plugin was stopped on the General tab and
restarted or the first time the plugin was started.
2024-11-02 08:28:52 +01:00
WarmUpTill
bc0497d2c9 Add MacroWasCheckedSinceLastStart() 2024-11-02 08:28:52 +01:00
WarmUpTill
37226a3a4c Fix macro group icon not being displayed correctly in OBS 31
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2024-10-31 19:39:38 +01:00
WarmUpTill
9f15fbe47c Recursively search for source button in property groups
Some checks are pending
debian-build / build (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
2024-10-30 18:29:23 +01:00
WarmUpTill
0ee6feb529 Add option to check the current stream key
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2024-10-26 23:42:59 +02:00
WarmUpTill
3b82b55090 Fix Queue condition not displaying the correct size selection 2024-10-26 23:42:59 +02:00
WarmUpTill
071c3309c2 Add option to check current streaming service
Some checks are pending
debian-build / build (push) Waiting to run
Check locale / ubuntu64 (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
2024-10-26 11:09:06 +02:00
WarmUpTill
ee7ee7a846 Move AutoUpdateTooltipLabel definition 2024-10-26 11:09:06 +02:00
WarmUpTill
d10300bd4b Fix typo 2024-10-26 11:09:06 +02:00
WarmUpTill
e262672876 Add "Last empty" row to Queue tab 2024-10-26 11:08:51 +02:00
WarmUpTill
392e775c7b Add temp var support for size and running state 2024-10-26 11:08:51 +02:00
WarmUpTill
f641c20564 Add SetTempVarValue() overload to support boolean values 2024-10-26 11:08:51 +02:00
WarmUpTill
39327be88e Add option to move scene item above / below specified scene item
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2024-10-21 20:46:27 +02:00
WarmUpTill
ed0963799c Fix "Screenshot" action variable selection not being loaded
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2024-10-18 18:30:49 +02:00
WarmUpTill
f9e73a2b8f Fix crash when clearing Twitch chat buffer 2024-10-18 18:29:46 +02:00
Przemek Pawlas
dfaa704b71 Remove some unused includes 2024-10-12 17:09:33 +02:00
Przemek Pawlas
d54edddde4 Enable use of variables as input for "Variable" action 2024-10-12 17:09:33 +02:00
Przemek Pawlas
37cc289204 Rename function 2024-10-12 17:09:33 +02:00
WarmUpTill
8d9519c849 Adapt to OBS switch from "themeID" to "class" properties
OBS commit: cb026964b00c366943a3c16dfb1511eafc24c035
2024-10-10 20:02:35 +02:00
WarmUpTill
0fd5f47982 Add option to check if users leave or join Twitch chat 2024-10-08 19:57:50 +02:00
Przemek Pawlas
62ce83a7f6 Add one more Twitch chat tag temp var 2024-10-05 23:19:53 +02:00
Przemek Pawlas
8ecee0070d Add more Twitch chat tag temp vars and properties 2024-10-05 16:01:32 +02:00
WarmUpTill
a4970b1b07 Match any Twitch chat message by default 2024-10-05 14:30:30 +02:00
WarmUpTill
140dc9c6f4 Add option to set enabled state for partial match regex config 2024-10-05 14:30:30 +02:00
Przemek Pawlas
9a4d2935f8 Disable custom macro interval by default 2024-10-04 18:56:57 +02:00
WarmUpTill
335c964419 Fix connecting Twitch account leading to OBS freeze
In rare cases the close() call for the Twitch chat connection might fail
due to a race condition I have not fully figured out yet.
As a workaround the connection to the chat server will be force closed
for now.
2024-10-04 12:33:17 +02:00
WarmUpTill
72b2a7a07a Add option to assign screenshot to variable 2024-10-03 19:10:14 +02:00
WarmUpTill
f863909f34 Fix incorrect websocket request callback being called 2024-10-02 19:52:07 +02:00
WarmUpTill
621ca201ef Cleanup 2024-10-02 19:52:07 +02:00
WarmUpTill
563ea1a5e7 Add workaround for obs_source_set_sync_offset()
Calling obs_source_set_sync_offset() once with the desired value has no
effect.
The function needs to be called multiple times with different values in
hopes of the sync offset being changed successfully.

See https://github.com/obsproject/obs-studio/issues/7912
2024-10-01 22:19:27 +02:00
WarmUpTill
2af6ee883b Fix process condition not respecting regex options 2024-10-01 22:18:29 +02:00
Przemek Pawlas
0e9875e6b5 Allow using regex capture groups in replacement 2024-10-01 21:11:16 +02:00
WarmUpTill
c2d2e883c6 Add option to configure macro pause state on startup 2024-09-29 12:54:35 +02:00
WarmUpTill
a196e56078 Fix macro action paste happening in the wrong section
Instead of checking the cursor position at the time of the paste, it
needed to be checked at the time of the context menu creation to ensure
the segment gets pasted into the correct section
2024-09-24 23:00:47 +02:00
WarmUpTill
0536e4a60b Add option to trim screenshot created in Video condition 2024-09-24 23:00:47 +02:00
WarmUpTill
55ab096e49 Cleanup screenshot helper 2024-09-24 23:00:47 +02:00
WarmUpTill
c39ea0854a Fix crash when changing position of scene item transform 2024-09-24 23:00:47 +02:00
WarmUpTill
aef080984e Add new macros after current selection 2024-09-24 23:00:47 +02:00
WarmUpTill
1aa2425e81 Cleanup locale 2024-09-24 23:00:47 +02:00
WarmUpTill
f5eadfa39c Add option to disable saving when switching macros 2024-09-24 23:00:47 +02:00
WarmUpTill
faa65facfe Add Stream Deck condition type
The Stream Deck plugin to enable communication with the Advanced Scene
Switcher can be found here:
https://github.com/WarmUpTill/advanced-scene-switcher-streamdeck-plugin
2024-09-24 23:00:47 +02:00
WarmUpTill
b60235d964 Export VariableSpinbox symbols 2024-09-24 23:00:47 +02:00
WarmUpTill
718b18248e Add context menu entry to remove selected macro segment 2024-09-24 23:00:47 +02:00
WarmUpTill
4316e53039 Cleanup constness of MacroSegmentList 2024-09-24 23:00:47 +02:00
WarmUpTill
790ac50385 Add warning if temp vars are not available for "Run" action 2024-09-24 23:00:47 +02:00
WarmUpTill
3a87a8d155 Hide priority settings when legacy tabs are hidden 2024-09-24 23:00:47 +02:00
WarmUpTill
91912f2aa5 Add/Move helpers to register OBS websocket vendor requests
Adds websocket vendor requests to start and stop the plugin.
Adds vendor events to indicate when the plugin is started or stopped.

These are ntended to be used for the StreamDeck plugin support.
2024-09-24 23:00:47 +02:00
WarmUpTill
624b841ca1 Add option for custom macro condition check interval 2024-09-24 23:00:47 +02:00
WarmUpTill
9eb4e90291 Rename and refactor macro class functions
LastConditionCheckTime() will be required to support custom condition
check intervals per macro
2024-09-24 23:00:47 +02:00
WarmUpTill
94263cc7a0 Work around crash in std::filesystem due to unexpected char encoding 2024-09-24 23:00:47 +02:00
WarmUpTill
2d035758f4 Fix build issues with macos-14 runner
Default runner switched from X86_64 based to arm64 based starting with
the macos-14 runner images
2024-09-24 23:00:47 +02:00
WarmUpTill
86697002f7 Add option to wait for media playback to end 2024-09-24 23:00:47 +02:00
WarmUpTill
2247152aa8 Remove legacy tab "Scene Triggers" 2024-09-24 23:00:47 +02:00
WarmUpTill
2528449c84 Cleanup 2024-09-24 23:00:47 +02:00
WarmUpTill
f2df3d18be Rename locale key to better reflect its purpose 2024-09-24 23:00:47 +02:00
WarmUpTill
c7c48d03e9 Add option for short circuit evaluation of conditions 2024-09-24 23:00:47 +02:00
WarmUpTill
0906c6bb3f Add regex settings option to scene name pattern check 2024-09-24 23:00:47 +02:00
WarmUpTill
9f2daf1a15 Add option to select all items in a source group 2024-09-24 23:00:47 +02:00
WarmUpTill
6fb8619671 Clear outdated highlight triggers when enabling macro segment highlights 2024-09-24 23:00:47 +02:00
WarmUpTill
36fb83246b Add log level to log executed macros 2024-09-24 23:00:47 +02:00
WarmUpTill
e729d15e97 Remove legacy tab "Network" 2024-09-24 23:00:47 +02:00
WarmUpTill
7836298076 buildspec: Update OBS Studio dependency to 30.1.2
This is necessary to support OBS 31, which removes a bunch of deprecates
functions
2024-09-24 23:00:47 +02:00
WarmUpTill
354880ad52 Prepare for deprecations in OBS 31
See https://github.com/obsproject/obs-studio/pull/11164
2024-09-24 23:00:47 +02:00
WarmUpTill
c1ac9bb890 Resolve QCssParser::parseColorValue warning 2024-09-24 23:00:47 +02:00
WarmUpTill
49bf1ad379 Fix transition condition timing issues
The condition type was able to miss transition starts of very fast
conditions (e.g. Cut) when the selection "Any transition" was made.

In that case no signal handler was installed (as the condition type
might change) and only the transition scenes were compared at the time
of the condition check.
If that moment where the transition scenes differ was missed no
transition start was recognized.

Additionally, the "Transitioning to" and "Transitioning from" checks
faced a similar issue, if the scene transition was happening so fast,
that the condition check was not performed during the time of the
transition.
2024-09-24 23:00:47 +02:00
WarmUpTill
a6e30a1e20 Add temp var support to media condition 2024-09-24 20:27:11 +02:00
WarmUpTill
2f54c2ee62 Increase volume meter scale from -100 to 0 dB
This was done to enable users to to use higher precision values in the
"Audio" condition type.
Previously, the UI would reset the selected values to the minimum value
possible to display in the volume meter widget, which was -60 dB.
2024-09-20 23:05:45 +02:00
Przemek Pawlas
6ccbaad41c Add enhanced relative time to variable tab cells 2024-09-18 17:38:53 +02:00
Przemek Pawlas
57d78a996a Add variable trim and case change actions 2024-09-16 19:42:41 +02:00
炭酸コーラ
2d3dada30e Updated Japanese translation to the latest version 2024-09-11 17:59:20 +02:00
WarmUpTill
5bf20d155f Ignore comments 2024-09-11 17:59:20 +02:00
WarmUpTill
2b95598958 CI: Replace deprecated actions/upload-artifact action 2024-09-11 17:59:20 +02:00
WarmUpTill
b561a20dde Rename issue template file 2024-08-24 00:28:23 +02:00
WarmUpTill
835722af34 Update issue templates and links 2024-08-24 00:24:09 +02:00
WarmUpTill
93efc7cab3 Add option to check for addition chat message properties 2024-08-24 00:23:54 +02:00
WarmUpTill
d69364ec19 Fix crash on IRC disconnect due to maintenance
Reworked the (dis)connect handling
2024-08-24 00:23:54 +02:00
WarmUpTill
d75066df5f Fix crash when adding new entries to the legacy tabs 2024-08-24 00:23:54 +02:00
WarmUpTill
863c84026e Move GetSettingsWindow() to ui-helpers 2024-08-24 00:23:54 +02:00
WarmUpTill
a523081d77 Move GetIndexOfSignal() to ListEditor 2024-08-24 00:23:54 +02:00
WarmUpTill
66534519d6 Export complete RegexConfigWidget class 2024-08-24 00:23:54 +02:00
WarmUpTill
b3b2114c45 Fix SetHeightToContentHeight() not respecting diffrently sized rows 2024-08-24 00:23:54 +02:00
WarmUpTill
510f83246e Only emit signals when value changed
* Fixes crash when deleting macro in case macro selection is in focus
* Prevent unnecessary create operation when switching macro segment type
2024-08-16 06:49:27 +02:00
WarmUpTill
c969b21f93 Fix MacroSelection not properly unhiding first macro in selection 2024-08-16 06:49:27 +02:00
WarmUpTill
6ec40ef8e9 Cleanup 2024-08-16 06:49:27 +02:00
WarmUpTill
27859e83b3 Fix crash when creating macro which uses macro segment factories 2024-08-16 06:49:27 +02:00
WarmUpTill
263565700a Fix crashes related to widget highlighting 2024-08-16 06:49:27 +02:00
WarmUpTill
5bb10a4aef Resolve unused parameter warnings 2024-08-15 01:02:03 +02:00
WarmUpTill
ee72bb192c Improve corrupted installation detection 2024-08-15 01:02:03 +02:00
WarmUpTill
1a1028cbaa Paste copied macro action depending on cursor position
If cursor is above else action section paste it there.
If cursor is above regular action section paste it there.

In case of neither paste in original position.
2024-08-14 22:02:55 +02:00
WarmUpTill
2f4f8bcc74 Add IsCursorInWidgetArea() 2024-08-14 22:02:55 +02:00
WarmUpTill
4113615cd6 Add context menu to change custom label for macro segments 2024-08-14 22:02:55 +02:00
WarmUpTill
9522d7c0b4 Adapt to new OBS dock API
* The plugin will no longer attempt to restore dock positions on scene
  collection change.
* When a macro is being renamed the dock widget with the current name
  will be removed.
  A new dock with the new name will have to be opened manually.
* The status dock position will be when updating to a version containing
  this changei from an older version.
2024-08-14 21:51:42 +02:00
WarmUpTill
cc3ea79836 buildspec: Update OBS Studio dependency to 30.0.2 2024-08-14 21:51:42 +02:00
WarmUpTill
f72e802d00 Add API link 2024-08-14 21:51:42 +02:00
WarmUpTill
36201cbfb4 Add API to register new macro condition and action types 2024-08-14 21:51:42 +02:00
WarmUpTill
685e28d161 Make read and write functions of variables thread-safe 2024-08-14 21:51:42 +02:00
WarmUpTill
82d23dcf0e Exclude python cache directories 2024-08-14 21:51:42 +02:00
WarmUpTill
ad322d54f0 Fix crash in case of high frequency resize events
I am not sure if this could really happen in normal use.
Was discovered after implementing the script properties UI widgets.
2024-08-14 21:51:42 +02:00
WarmUpTill
8bac6fe829 Set Run condition default timeout to one second
The action is often used in combination with python scripts which
usually take longer to initialize than 100ms
2024-08-14 21:51:42 +02:00
WarmUpTill
ac7531decb Fix crash in MIDI initialization 2024-08-14 21:51:42 +02:00
Junior Anzolin
7af4969ec6 Add portuguese brazil translation (#1168)
* Add portuguese brazil translation
2024-08-13 19:36:27 +02:00
WarmUpTill
a088325968 Refresh pattern data when pattern file changes 2024-08-11 16:03:08 +02:00
WarmUpTill
57bc58be8e Add duration modifier tests 2024-08-11 16:03:08 +02:00
WarmUpTill
f9730b1bc2 Refactor DurationModifier to enable testing 2024-08-11 16:03:08 +02:00
WarmUpTill
d4025214e5 Cleanup duplicate "[advss]" log prefix 2024-08-11 16:03:08 +02:00
WarmUpTill
c431e6c31d Show warning if default action or condition cannot be created 2024-08-11 16:03:08 +02:00
WarmUpTill
9941cfe9b5 Install libproc2-dev in Linux build environment
This ensures that the plugin will attempt to load either libprocps-dev
or libproc2-dev libraries at runtime, instead of just the libprocps-dev
library

Dep packages taken from:
 * https://packages.debian.org/bookworm/libproc2-0
 * https://packages.debian.org/bookworm/libproc2-dev
2024-08-11 16:03:08 +02:00
WarmUpTill
15998012e5 Change default verbosity of busy loop detection log
This is mostly relevant when analysing issues, so it does not make sense
to potentially spam the OBS log with this message
2024-08-11 16:03:08 +02:00
WarmUpTill
bcb043f997 Add condition logic tests 2024-08-11 16:03:08 +02:00
WarmUpTill
74116382b1 Enable include during unit testing 2024-08-11 16:03:08 +02:00
WarmUpTill
24f33fb0d2 Refactor condition logic to enable testing 2024-08-11 16:03:08 +02:00
WarmUpTill
d80df57ef8 Increase default threshold value for image pattern matching
The previous default value could cause a lot of false positive
detections and thus cause confusion if the image detection is working
correctly or not.
2024-08-11 16:03:08 +02:00
WarmUpTill
240c47975c Rework Media condition
* Split state and time check
* Improve signal handling
* Prevent exponential memory groth when using the "any" / "all" check
* Improve layout
2024-08-07 21:22:45 +02:00
WarmUpTill
bedb3b8dc6 Change plugin folder name
Requested by lindenkron to ease development of a plugin manager
2024-07-09 21:21:10 +02:00
WarmUpTill
8032dde045 Prevent users from accidentally enabling action cooldowns 2024-07-09 21:21:10 +02:00
WarmUpTill
bfc51b2df5 Add option to set individual transform settings 2024-07-09 21:00:25 +02:00
WarmUpTill
2160da59f3 Add support to check values of individual transform settings 2024-07-09 21:00:25 +02:00
WarmUpTill
d3164e3ed6 Add TransformSettingSelection 2024-07-09 21:00:25 +02:00
WarmUpTill
17c72b772d Display plugin load failure warning only once 2024-07-09 19:10:51 +02:00
WarmUpTill
3ff9a1f270 Add USB condition type
It allows users to check if a given USB device is currently connected
2024-07-08 20:54:26 +02:00
WarmUpTill
7d120d8b6a Fix MacOS version of install_advss_plugin_dependency_file 2024-07-08 20:54:26 +02:00
WarmUpTill
53cfe4b030 Add libusb dependency 2024-07-08 20:54:26 +02:00
WarmUpTill
4dcd748a1f Switch to github repo of openssl 2024-07-08 20:54:26 +02:00
WarmUpTill
223ce0d0a0 Fix potential crash in StatusControl 2024-07-03 13:02:34 +02:00
WarmUpTill
5bd3341681 Add option to clear message buffer on match 2024-07-03 13:02:34 +02:00
WarmUpTill
bb527d6910 Improve error logging of commercial start via Twitch action 2024-07-03 12:55:49 +02:00
WarmUpTill
dfedbcf31a Add option to set maximum replay buffer time 2024-06-26 19:19:41 +02:00
WarmUpTill
1bbfd16b3f Add option to check disk space available
Based on the OBS stats dock implementation, which queries the drive to
which outputs would write to
2024-06-22 15:40:47 +02:00
WarmUpTill
7f5737d03f Use default cursor icon on macro segment control hover
Previously the cursor icon would be set to Qt::SplitVCursor as those
widgets were moved into the splitter handle layout
2024-06-20 21:41:28 +02:00
WarmUpTill
7702541d81 Cleanup
* Increase update frequency of AutoUpdateTooltipLabel
* Prevent crash in TempVariableRef::PostLoad()
* Ensure that correct widget is passed for MacroSelection connections
2024-06-20 21:41:28 +02:00
WarmUpTill
3bc15e585c Switch QPushButton to QToolButton
This will be more consistent with the widget styles used throughout OBS
and solves a few layout issues
2024-06-20 21:41:28 +02:00
WarmUpTill
d77101b6aa Restore fullscreen projector display selection
Instead of discarding the monitor selection now only no longer perform
the action while the monitor setup is changed.
2024-06-18 21:24:28 +02:00
WarmUpTill
c1e23f9c7a Improve slide show condition
* Add temp var support
* Add regex support for path check
* Improve layout stretch handling
2024-06-16 18:42:06 +02:00
WarmUpTill
70415cd8f4 Add support for "slideshow_v2" 2024-06-16 18:42:06 +02:00
WarmUpTill
f50cd76493 Fix i386 build issue 2024-06-16 11:04:05 +02:00
炭酸コーラ
e4af698881 Add Japanese translation 2024-06-15 10:11:45 +02:00
WarmUpTill
33120a359c Fix Twitch "goal type" temp var tooltips being cut off early 2024-06-15 10:11:45 +02:00
WarmUpTill
091a364097 Print line numbers in check-locale.py in case of error 2024-06-15 10:11:45 +02:00
WarmUpTill
e3ff6f34fc Update OpenCV to 4.10.0
This adds support for the MSVC toolset 19.40.x
2024-06-11 19:01:01 +02:00
322 changed files with 34490 additions and 11818 deletions

View File

@@ -6,15 +6,6 @@
"autosort": true
},
"additional_commands": {
"find_qt": {
"flags": [],
"kwargs": {
"COMPONENTS": "+",
"COMPONENTS_WIN": "+",
"COMPONENTS_MACOS": "+",
"COMPONENTS_LINUX": "+"
}
},
"set_target_properties_obs": {
"pargs": 1,
"flags": [],

View File

@@ -1,36 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Logs**
Please provide a log of your issue with verbose logging enabled (See General tab of the plugin).
In case of a crash, please also include the corresponding crash log.
See [here](https://obsproject.com/forum/threads/please-post-a-log-with-your-issue-heres-how.23074/) for a description where to find the log files and how to share them.
Please share the currently used plugin settings by exporting them them to a file (See General tab of the plugin).
If applicable, add screenshots to help explain your problem.
**Version information**
- OS: [e.g. Windows 10]
- OBS Version [e.g. 27.1.3]
- Plugin Version [e.g. 1.17.1]
**Additional context**
Add any other context about the problem here.

112
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View File

@@ -0,0 +1,112 @@
# Based on the OBS issue templates
# https://github.com/obsproject/.github/tree/master/.github/ISSUE_TEMPLATE
name: Bug Report
description: Report a bug or crash
body:
- type: markdown
id: md_welcome
attributes:
value: This form is for reporting bugs for Advanced Scene Switcher!
- type: dropdown
id: os_info
attributes:
label: Operating System Info
description: What Operating System are you running?
options:
- Windows 11
- Windows 10
- macOS 14
- macOS 13
- macOS 12
- macOS 11
- Ubuntu 24.04
- Ubuntu 23.10
- Ubuntu 22.04
- Other
validations:
required: true
- type: input
id: os_info_other
attributes:
label: Other OS
description: "If \"Other\" was selected above, what OS are you using?"
placeholder: "e.g., Arch Linux, FreeBSD"
validations:
required: false
- type: input
id: obs_version
attributes:
label: OBS Studio Version
description: What version of OBS Studio are you using?
placeholder: 30.2.2
validations:
required: true
- type: input
id: advss_version
attributes:
label: Advanced Scene Switcher Version
description: What version of the Advanced Scene Switcher are you using?
placeholder: 1.27.2
validations:
required: true
- type: input
id: settings
attributes:
label: Plugin settings
description: |
Please provide the plugin settings used.
Either only [export the macros](https://github.com/WarmUpTill/SceneSwitcher/wiki/Exporting-and-importing-individual-macros) relevant to this issue or [export all settings](https://github.com/WarmUpTill/SceneSwitcher/wiki/Saving-and-loading-settings#how-to-create-and-import-a-backup-of-your-settings) on the General tab of the plugin.
validations:
required: false
- type: input
id: obs_log_url
attributes:
label: OBS Studio Log URL
description: Please provide the obsproject.com URL (from Help menu > Log Files > Upload Current/Previous Log File) to the OBS log file where this issue occurred.
validations:
required: false
- type: input
id: obs_crash_log_url
attributes:
label: OBS Studio Crash Log URL
description: If this is a crash report, please provide the obsproject.com URL to the OBS crash log file where this issue occurred.
validations:
required: false
- type: textarea
id: expected_behavior
attributes:
label: Expected Behavior
description: "What did you expect to happen?"
validations:
required: true
- type: textarea
id: current_behavior
attributes:
label: Current Behavior
description: "What actually happened?"
validations:
required: true
- type: textarea
id: steps_to_reproduce
attributes:
label: Steps to Reproduce
description: "How do you trigger this bug? Please walk us through it step by step."
placeholder: |
1.
2.
3.
...
value: |
1.
2.
3.
...
validations:
required: true
- type: textarea
id: additional_notes
attributes:
label: Anything else we should know?
validations:
required: false

View File

@@ -1,5 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: Help/Support
- name: 📚 Wiki
url: https://github.com/WarmUpTill/SceneSwitcher/wiki
about: For general questions about how to use and configure the plugin please have a look at the wiki (https://github.com/WarmUpTill/SceneSwitcher/wiki) or ask questions in the OBS forum (https://obsproject.com/forum/threads/advanced-scene-switcher.48264/)
about: For explanations on how the plugin works or to see example guides, check out the wiki.
- name: 💬 OBS Forum Thread
url: https://obsproject.com/forum/threads/advanced-scene-switcher.48264
about: To discuss the plugin or get assistance, feel free to use the OBS forum thread or the GitHub discussions page.

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,37 @@
# Based on the OBS issue templates
# https://github.com/obsproject/.github/tree/master/.github/ISSUE_TEMPLATE
name: Feature request
description: Suggest an idea for this projectReport a bug or crash
body:
- type: markdown
id: md_welcome
attributes:
value: This form is for requesting new features for Advanced Scene Switcher!
- type: textarea
id: background
attributes:
label: Background
description: "Is your feature request related to a problem? Please describe."
validations:
required: false
- type: textarea
id: solution
attributes:
label: Solution
description: "Describe the solution you'd like."
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives
description: "Describe alternatives you've considered."
validations:
required: false
- type: textarea
id: additional_notes
attributes:
label: Anything else we should know?
validations:
required: false

View File

@@ -9,4 +9,5 @@ package 'libxtst-dev'
package 'libxss-dev'
package 'libopencv-dev'
package 'libtesseract-dev'
package 'libprocps-dev'
package 'libproc2-dev'
package 'libusb-1.0-0-dev'

View File

@@ -3,3 +3,5 @@ brew "coreutils"
brew "cmake"
brew "jq"
brew "xcbeautify"
brew "automake"
brew "libtool"

View File

@@ -1,3 +1,4 @@
package 'cmake', path: 'Cmake\bin', bin: 'cmake'
package 'innosetup', path: 'Inno Setup 6', bin: 'iscc'
package 'OpenSSL', path: 'OpenSSL', bin: 'openssl'
package 'OpenSSL', path: 'OpenSSL', bin: 'openssl'
package 'Microsoft.VisualStudio.Locator', path: 'vswhere', bin: 'vswhere'

View File

@@ -306,7 +306,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
log_info "Building OpenCV ..."
cmake --build ${opencv_build_dir} --config Release
log_info "Installing OpenCV..."
log_info "Installing OpenCV ..."
cmake --install ${opencv_build_dir} --prefix "${advss_dep_path}" --config Release || true
popd
@@ -335,17 +335,22 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
log_info "Building Leptonica ..."
cmake --build ${leptonica_build_dir} --config Release
log_info "Installing Leptonica..."
log_info "Installing Leptonica ..."
# Workaround for "unknown file attribute: H" errors when running install
cmake --install ${leptonica_build_dir} --prefix "${advss_dep_path}" --config Release || :
popd
local tesseract_dir="${project_root}/deps/tesseract"
local tesseract_build_dir="${tesseract_dir}/build_${target##*-}"
local tesseract_build_dir_x86_64="${tesseract_dir}/build_x86_64"
pushd ${tesseract_dir}
log_info "Configure Tesseract (x86_64) ..."
local -a tesseract_cmake_args=(
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
-DCMAKE_SYSTEM_NAME="Darwin"
-DCMAKE_OSX_ARCHITECTURES=x86_64
-DCMAKE_SYSTEM_PROCESSOR=x86_64
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
-DSW_BUILD=OFF
-DBUILD_TRAINING_TOOLS=OFF
@@ -353,34 +358,46 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
-DCMAKE_INSTALL_PREFIX="${advss_dep_path}"
)
if [ "${target}" != "macos-x86_64" ]; then
tesseract_cmake_args+=(
-DCMAKE_SYSTEM_PROCESSOR=aarch64
-DHAVE_AVX=FALSE
-DHAVE_AVX2=FALSE
-DHAVE_AVX512F=FALSE
-DHAVE_FMA=FALSE
-DHAVE_SSE4_1=FALSE
-DHAVE_NEON=TRUE
)
sed -i'.original' 's/HAVE_NEON FALSE/HAVE_NEON TRUE/g' "${tesseract_dir}/CMakeLists.txt"
fi
cmake -S . -B ${tesseract_build_dir_x86_64} ${tesseract_cmake_args}
pushd ${tesseract_dir}
log_info "Configure Tesseract ..."
cmake -S . -B ${tesseract_build_dir} ${tesseract_cmake_args}
log_info "Building Tesseract (x86_64) ..."
cmake --build ${tesseract_build_dir_x86_64} --config Release
log_info "Building Tesseract ..."
cmake --build ${tesseract_build_dir} --config Release
log_info "Configure Tesseract (arm64) ..."
git checkout .
local tesseract_build_dir_arm64="${tesseract_dir}/build_arm64"
local -a tesseract_cmake_args=(
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_SYSTEM_NAME="Darwin"
-DCMAKE_OSX_ARCHITECTURES=arm64
-DCMAKE_SYSTEM_PROCESSOR=arm64
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
-DSW_BUILD=OFF
-DBUILD_TRAINING_TOOLS=OFF
-DCMAKE_PREFIX_PATH="${advss_dep_path};${_plugin_deps}"
-DCMAKE_INSTALL_PREFIX="${advss_dep_path}"
)
cmake -S . -B ${tesseract_build_dir_arm64} ${tesseract_cmake_args}
log_info "Building Tesseract (arm64) ..."
cmake --build ${tesseract_build_dir_arm64} --config Release
log_info "Combine arm and x86 libtesseract binaries ..."
mv ${tesseract_build_dir_arm64}/libtesseract.a ${tesseract_build_dir_arm64}/libtesseract_arm.a
lipo -create ${tesseract_build_dir_x86_64}/libtesseract.a ${tesseract_build_dir_arm64}/libtesseract_arm.a -output ${tesseract_build_dir_arm64}/libtesseract.a
log_info "Installing Tesseract..."
cmake --install ${tesseract_build_dir} --prefix "${advss_dep_path}" --config Release
cmake --install ${tesseract_build_dir_arm64} --prefix "${advss_dep_path}" --config Release
popd
pushd ${advss_dep_path}
log_info "Prepare openssl ..."
rm -rf openssl
git clone git://git.openssl.org/openssl.git --branch openssl-3.1.2 --depth 1
git clone https://github.com/openssl/openssl.git --branch openssl-3.1.2 --depth 1
mv openssl openssl_x86
cp -r openssl_x86 openssl_arm
@@ -402,9 +419,65 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
lipo -create openssl_x86/libcrypto.a openssl_arm/libcrypto.a -output openssl-combined/libcrypto.a
lipo -create openssl_x86/libssl.a openssl_arm/libssl.a -output openssl-combined/libssl.a
log_info "Clean up openssl dir..."
log_info "Clean up openssl dir ..."
mv openssl_x86 openssl
rm -rf openssl_arm
popd
pushd ${project_root}/deps/libusb
log_info "Configure libusb x86 ..."
export SDKROOT=$(xcrun --sdk macosx --show-sdk-path)
export CC=$(xcrun --sdk macosx --find clang)
export CXX=$(xcrun --sdk macosx --find clang++)
export CFLAGS="-arch x86_64 -isysroot $SDKROOT"
export CXXFLAGS="-arch x86_64 -isysroot $SDKROOT"
export LDFLAGS="-arch x86_64 -isysroot $SDKROOT"
export MACOSX_DEPLOYMENT_TARGET=10.15
log_info "Building libusb x86 ..."
mkdir ${project_root}/deps/libusb/out_x86
./autogen.sh
./configure --host=x86_64-apple-darwin --prefix=${advss_dep_path}
make && make install
log_info "Configure libusb arm ..."
make clean
rm -r ${project_root}/deps/libusb/out_x86
mkdir ${project_root}/deps/libusb/out_x86
./configure --host=aarch64-apple-darwin --prefix=${project_root}/deps/libusb/out_x86
make && make install
log_info "Building libusb arm ..."
make clean
export SDKROOT=$(xcrun --sdk macosx --show-sdk-path)
export CC=$(xcrun --sdk macosx --find clang)
export CXX=$(xcrun --sdk macosx --find clang++)
export CFLAGS="-arch arm64 -isysroot $SDKROOT"
export CXXFLAGS="-arch arm64 -isysroot $SDKROOT"
export LDFLAGS="-arch arm64 -isysroot $SDKROOT"
export MACOSX_DEPLOYMENT_TARGET=10.15
mkdir ${project_root}/deps/libusb/out_arm
./configure --host=aarch64-apple-darwin --prefix=${project_root}/deps/libusb/out_arm
make && make install
log_info "Combine arm and x86 libusb binaries ..."
lipo -create ${project_root}/deps/libusb/out_x86/lib/libusb-1.0.0.dylib \
${project_root}/deps/libusb/out_arm/lib/libusb-1.0.0.dylib \
-output ${advss_dep_path}/lib/temp-libusb-1.0.0.dylib
mv ${advss_dep_path}/lib/temp-libusb-1.0.0.dylib ${advss_dep_path}/lib/libusb-1.0.0.dylib
install_name_tool -id @rpath/libusb-1.0.0.dylib ${advss_dep_path}/lib/libusb-1.0.0.dylib
log_info "Clean up libusb ..."
unset SDKROOT
unset CC
unset CXX
unset CFLAGS
unset CXXFLAGS
unset LDFLAGS
unset MACOSX_DEPLOYMENT_TARGET
popd
;;
linux)
# Nothing to do for now

View File

@@ -199,6 +199,26 @@ function Build {
Log-Information "Install tesseract..."
Invoke-External cmake --install "${TesseractBuildPath}" --prefix "${ADVSSDepPath}" @TesseractCmakeArgs
Push-Location -Stack BuildLibusbTemp
Ensure-Location $ProjectRoot
$LibusbPath = "${ProjectRoot}/deps/libusb"
Log-Information "Building libusb..."
$msbuildExe = vswhere -latest -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe | select-object -first 1
if ($msbuildExe) {
Invoke-External $msbuildExe "${LibusbPath}/msvc/libusb.sln" /property:Configuration=Release /property:Platform=x64
$libusbBuildResultDirectory = "${LibusbPath}/build/v143/x64/Release"
if (-not (Test-Path -Path $libusbBuildResultDirectory)) {
$libusbBuildResultDirectory = "${LibusbPath}/x64/Release/dll"
}
Copy-Item -Path "${libusbBuildResultDirectory}/*" -Destination ${ADVSSDepPath} -Recurse -Force
} else {
Log-Information "Failed to locate msbuild.exe - skipping libusb build"
}
}
Build

0
.github/test vendored
View File

View File

@@ -43,14 +43,14 @@ jobs:
# devscripts and libobs-dev are needed but they were already installed
# from check_libobs_revision and install_frontend_header sections.
sudo apt update
sudo apt install cmake debhelper libcurl4-openssl-dev libxss-dev libxtst-dev qtbase5-dev libopencv-dev libprocps-dev
sudo apt install build-essential cmake debhelper libcurl4-openssl-dev libxss-dev libxtst-dev qt6-base-dev libopencv-dev libproc2-dev
- name: build
run: |
debuild --no-lintian --no-sign
mv ../*.deb .
- name: Publish
if: success()
uses: actions/upload-artifact@v2.2.1
uses: actions/upload-artifact@v4
with:
name: "obs-scene-switcher.deb"
path: ${{ github.workspace }}/*.deb

View File

@@ -10,7 +10,7 @@ env:
jobs:
check-event:
name: Check GitHub Event Data 🔎
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
defaults:
run:
shell: bash
@@ -75,7 +75,7 @@ jobs:
macos-build:
name: Build for macOS 🍏
runs-on: macos-13
runs-on: macos-14
needs: check-event
defaults:
run:
@@ -107,8 +107,8 @@ jobs:
print "pluginName=${product_name}" >> $GITHUB_OUTPUT
print "pluginVersion=${git_tag}" >> $GITHUB_OUTPUT
print '::group::Select Xcode version'
sudo xcode-select --switch /Applications/Xcode_14.3.1.app
print '::group::Enable Xcode 15.2'
sudo xcode-select --switch /Applications/Xcode_15.2.app/Contents/Developer
print '::endgroup::'
- uses: actions/cache@v4
@@ -177,7 +177,7 @@ jobs:
ubuntu-build:
name: Build for Ubuntu 🐧
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
needs: check-event
defaults:
run:
@@ -210,6 +210,11 @@ jobs:
restore-keys: |
${{ runner.os }}-ccache-x86_64-
- name: Set up CMake 🏗️
uses: jwlawson/actions-setup-cmake@v1.13
with:
cmake-version: '3.24.x'
- name: Set up Homebrew 🍺
uses: Homebrew/actions/setup-homebrew@master
@@ -241,14 +246,14 @@ jobs:
- name: Upload Artifacts 📡
uses: actions/upload-artifact@v4
with:
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-22.04-x86_64-${{ needs.check-event.outputs.commitHash }}
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-24.04-x86_64-${{ needs.check-event.outputs.commitHash }}
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-x86_64*.*
- name: Upload debug symbol artifacts 🪲
uses: actions/upload-artifact@v4
if: ${{ fromJSON(needs.check-event.outputs.package) }}
with:
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-22.04-x86_64-${{ needs.check-event.outputs.commitHash }}-dbgsym
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-24.04-x86_64-${{ needs.check-event.outputs.commitHash }}-dbgsym
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-x86_64*-dbgsym.ddeb
windows-build:

View File

@@ -3,7 +3,7 @@ on:
workflow_call:
jobs:
clang-format:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
@@ -15,7 +15,7 @@ jobs:
failCondition: error
cmake-format:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:

View File

@@ -31,7 +31,7 @@ jobs:
create-release:
name: Create Release 🛫
if: github.ref_type == 'tag'
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
needs: build-project
defaults:
run:
@@ -77,7 +77,7 @@ jobs:
variants=(
'windows-x64;zip|exe'
'macos-universal;tar.xz|pkg'
'ubuntu-22.04-x86_64;tar.xz|deb|ddeb'
'ubuntu-24.04-x86_64;tar.xz|deb|ddeb'
'sources;tar.xz'
)

6
.gitignore vendored
View File

@@ -7,6 +7,7 @@
!/cmake
!/data
!/deps
!/scripting
!/forms
!/lib
!/module
@@ -34,4 +35,7 @@
.DS_Store
# Exclude CMake build number cache
/cmake/.CMakeBuildNumber
/cmake/.CMakeBuildNumber
# Exclude python caches
/**/__pycache__

9
.gitmodules vendored
View File

@@ -25,3 +25,12 @@
[submodule "deps/json"]
path = deps/json
url = https://github.com/nlohmann/json.git
[submodule "deps/libusb"]
path = deps/libusb
url = https://github.com/libusb/libusb.git
[submodule "deps/date"]
path = deps/date
url = https://github.com/HowardHinnant/date.git
[submodule "deps/jsoncons"]
path = deps/jsoncons
url = https://github.com/danielaparker/jsoncons.git

View File

@@ -20,6 +20,12 @@ if(BUILD_OUT_OF_TREE)
include(helpers)
endif()
# OBS 31 no longer defines find_qt so check if we need to include it for in-tree
# builds
if(NOT COMMAND find_qt)
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/helpers_common.cmake")
endif()
set(LIB_NAME "${PROJECT_NAME}-lib")
add_library(${PROJECT_NAME} MODULE)
add_library(${LIB_NAME} SHARED)
@@ -27,6 +33,11 @@ add_library(${LIB_NAME} SHARED)
include(cmake/common/get_git_revision_description.cmake)
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
git_describe(GIT_TAG)
if(${GIT_TAG} STREQUAL "GIT-NOTFOUND")
set(GIT_TAG ${PROJECT_VERSION})
endif()
message(STATUS "${PROJECT_NAME} version: ${GIT_TAG}")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/version.cpp.in"
"${CMAKE_CURRENT_BINARY_DIR}/lib/version.cpp" @ONLY)
@@ -52,8 +63,6 @@ target_sources(
${LIB_NAME}
PRIVATE lib/legacy/scene-group.cpp
lib/legacy/scene-group.hpp
lib/legacy/scene-trigger.cpp
lib/legacy/scene-trigger.hpp
lib/legacy/switch-audio.cpp
lib/legacy/switch-audio.hpp
lib/legacy/switch-executable.cpp
@@ -66,8 +75,6 @@ target_sources(
lib/legacy/switch-idle.hpp
lib/legacy/switch-media.cpp
lib/legacy/switch-media.hpp
lib/legacy/switch-network.cpp
lib/legacy/switch-network.hpp
lib/legacy/switch-pause.cpp
lib/legacy/switch-pause.hpp
lib/legacy/switch-random.cpp
@@ -96,6 +103,8 @@ target_sources(
lib/macro/macro-action-macro.hpp
lib/macro/macro-action-queue.cpp
lib/macro/macro-action-queue.hpp
lib/macro/macro-action-script.cpp
lib/macro/macro-action-script.hpp
lib/macro/macro-action-variable.cpp
lib/macro/macro-action-variable.hpp
lib/macro/macro-action.cpp
@@ -108,6 +117,8 @@ target_sources(
lib/macro/macro-condition-macro.hpp
lib/macro/macro-condition-queue.cpp
lib/macro/macro-condition-queue.hpp
lib/macro/macro-condition-script.cpp
lib/macro/macro-condition-script.hpp
lib/macro/macro-condition-tempvar.cpp
lib/macro/macro-condition-tempvar.hpp
lib/macro/macro-condition-variable.cpp
@@ -128,10 +139,14 @@ target_sources(
lib/macro/macro-ref.hpp
lib/macro/macro-run-button.cpp
lib/macro/macro-run-button.hpp
lib/macro/macro-script-handler.cpp
lib/macro/macro-script-handler.hpp
lib/macro/macro-segment-copy-paste.cpp
lib/macro/macro-segment-copy-paste.hpp
lib/macro/macro-segment-list.cpp
lib/macro/macro-segment-list.hpp
lib/macro/macro-segment-script.cpp
lib/macro/macro-segment-script.hpp
lib/macro/macro-segment-selection.cpp
lib/macro/macro-segment-selection.hpp
lib/macro/macro-segment.cpp
@@ -153,14 +168,22 @@ target_sources(
lib/queue/action-queue.hpp
lib/queue/action-queue-tab.cpp
lib/queue/action-queue-tab.hpp
lib/utils/auto-update-tooltip-label.cpp
lib/utils/auto-update-tooltip-label.hpp
lib/utils/backup.cpp
lib/utils/backup.hpp
lib/utils/condition-logic.cpp
lib/utils/condition-logic.hpp
lib/utils/curl-helper.cpp
lib/utils/curl-helper.hpp
lib/utils/cursor-shape-changer.cpp
lib/utils/cursor-shape-changer.hpp
lib/utils/double-slider.cpp
lib/utils/double-slider.hpp
lib/utils/duration-control.cpp
lib/utils/duration-control.hpp
lib/utils/duration-modifier.cpp
lib/utils/duration-modifier.hpp
lib/utils/duration.cpp
lib/utils/duration.hpp
lib/utils/export-symbol-helper.hpp
@@ -172,8 +195,12 @@ target_sources(
lib/utils/help-icon.cpp
lib/utils/item-selection-helpers.cpp
lib/utils/item-selection-helpers.hpp
lib/utils/json-helpers.cpp
lib/utils/json-helpers.hpp
lib/utils/layout-helpers.cpp
lib/utils/layout-helpers.hpp
lib/utils/list-controls.cpp
lib/utils/list-controls.hpp
lib/utils/list-editor.cpp
lib/utils/list-editor.hpp
lib/utils/log-helper.cpp
@@ -188,7 +215,6 @@ target_sources(
lib/utils/name-dialog.hpp
lib/utils/non-modal-dialog.cpp
lib/utils/non-modal-dialog.hpp
lib/utils/obs-dock.hpp
lib/utils/obs-module-helper.cpp
lib/utils/obs-module-helper.hpp
lib/utils/path-helpers.cpp
@@ -197,6 +223,9 @@ target_sources(
lib/utils/plugin-state-helpers.hpp
lib/utils/priority-helper.cpp
lib/utils/priority-helper.hpp
lib/utils/properties-view.cpp
lib/utils/properties-view.hpp
lib/utils/properties-view.moc.hpp
lib/utils/regex-config.cpp
lib/utils/regex-config.hpp
lib/utils/resizing-text-edit.cpp
@@ -237,12 +266,16 @@ target_sources(
lib/utils/tab-helpers.hpp
lib/utils/temp-variable.cpp
lib/utils/temp-variable.hpp
lib/utils/time-helpers.cpp
lib/utils/time-helpers.hpp
lib/utils/ui-helpers.cpp
lib/utils/ui-helpers.hpp
lib/utils/utility.cpp
lib/utils/utility.hpp
lib/utils/volume-control.cpp
lib/utils/volume-control.hpp
lib/utils/websocket-api.cpp
lib/utils/websocket-api.hpp
lib/variables/variable-line-edit.cpp
lib/variables/variable-line-edit.hpp
lib/variables/variable-number.hpp
@@ -260,13 +293,25 @@ target_sources(
# --- End of section ---
# Subfolder for advanced scene switcher plugins
set(ADVSS_PLUGIN_FOLDER "advanced-scene-switcher-plugins")
target_compile_definitions(
${LIB_NAME} PRIVATE ADVSS_PLUGIN_FOLDER=\"${ADVSS_PLUGIN_FOLDER}\")
include(cmake/common/advss_helpers.cmake)
setup_obs_lib_dependency(${LIB_NAME})
setup_obs_lib_dependency(${PROJECT_NAME})
find_qt(COMPONENTS Widgets Core)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt::Core Qt::Widgets)
target_link_libraries(${LIB_NAME} PRIVATE Qt::Core Qt::Widgets)
find_package(Qt6 REQUIRED COMPONENTS Widgets Core)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Widgets)
target_link_libraries(${LIB_NAME} PRIVATE Qt6::Core Qt6::Widgets)
# Ignore QCheckBox::stateChanged deprecation warning until minimum supported Qt
# version is at least Qt 6.7, which introduces QCheckBox::checkStateChanged
if(Qt6_VERSION VERSION_GREATER "6.0.0")
target_compile_definitions(${LIB_NAME} PRIVATE QT_NO_DEPRECATED_WARNINGS)
endif()
target_compile_options(
${PROJECT_NAME}
PRIVATE
@@ -303,13 +348,9 @@ set_target_properties(${LIB_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden)
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
target_compile_features(${LIB_NAME} PUBLIC cxx_std_17)
add_definitions(-DASIO_STANDALONE)
target_include_directories(
${LIB_NAME}
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/deps/asio/asio/include"
"${CMAKE_CURRENT_SOURCE_DIR}/deps/websocketpp"
"${CMAKE_CURRENT_SOURCE_DIR}/deps/obs-websocket/lib"
"${CMAKE_CURRENT_SOURCE_DIR}/deps/exprtk")
${LIB_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/deps/obs-websocket/lib"
"${CMAKE_CURRENT_SOURCE_DIR}/deps/exprtk")
if(NOT nlohmann_json_DIR
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/deps/json/CMakeLists.txt")
@@ -319,6 +360,21 @@ else()
endif()
target_link_libraries(${LIB_NAME} PUBLIC nlohmann_json::nlohmann_json)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/deps/jsoncons/CMakeLists.txt")
# Don't build jsoncons unit tests as they are causing compilation issues and
# won't be executed either way
if(OS_MACOS)
cmake_policy(SET CMP0077 NEW)
endif()
set(JSONCONS_BUILD_TESTS
OFF
CACHE BOOL "" FORCE)
add_subdirectory(deps/jsoncons EXCLUDE_FROM_ALL)
target_link_libraries(${LIB_NAME} PRIVATE jsoncons)
target_compile_definitions(${LIB_NAME} PRIVATE JSONPATH_SUPPORT=1)
endif()
find_package(CURL QUIET)
find_package(Libcurl QUIET)
if(CURL_FOUND)
@@ -386,7 +442,12 @@ else()
set(PROCESS_CONDITION_SUPPORTED 1)
endif()
if(PROCPS2_INCLUDE_DIR)
message(STATUS "${PROJECT_NAME} using libproc2")
find_package(PkgConfig REQUIRED)
pkg_check_modules(libproc2 IMPORTED_TARGET libproc2<4.0.5 QUIET)
if(libproc2_FOUND)
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS2_USE_INFO)
endif()
pkg_check_modules(libproc2 REQUIRED IMPORTED_TARGET libproc2)
set(PROC_INCLUDE_DIR "${PROCPS2_INCLUDE_DIR}")
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS2_AVAILABLE)
set(PROCESS_CONDITION_SUPPORTED 1)
@@ -399,14 +460,16 @@ else()
endif()
target_include_directories(${LIB_NAME} PRIVATE "${PROC_INCLUDE_DIR}")
target_sources(${LIB_NAME} PRIVATE lib/linux/advanced-scene-switcher-nix.cpp)
# Don't include irrelevant folders into sources archive
list(APPEND CPACK_SOURCE_IGNORE_FILES "\\.deps/.*")
endif()
if(NOT OS_WINDOWS)
target_compile_options(
${LIB_NAME}
PUBLIC -Wno-error=unused-parameter -Wno-error=conversion -Wno-error=shadow
-Wno-error=float-conversion -Wno-error=enum-conversion
-Wno-error=deprecated-declarations)
-Wno-error=float-conversion -Wno-error=enum-conversion)
endif()
# --- End of section ---

View File

@@ -19,8 +19,11 @@ The **Snap** package manager offers an OBS Studio installation which is bundled
sudo snap install obs-studio
```
More information can be found [here](https://github.com/WarmUpTill/SceneSwitcher/wiki/Installation).
## Contributing
- If you wish to contribute code to the project, have a look at this [section](BUILDING.md) describing how to compile the plugin.
- You can add custom conditions and actions at runtime using the API described [here](https://github.com/WarmUpTill/SceneSwitcher/wiki/Scripting).
- You can optionally use [pre-commit](https://pre-commit.com) to automatically handle formatting.
- If you wish to contribute translations, feel free to submit pull requests for the corresponding files under `data/locale`.

View File

@@ -6,17 +6,27 @@ import re
import sys
defaultLocaleFile = "en-US.ini"
commentChars = ";#"
class localeEntry:
locale = ""
widgetPlaceholders = []
qStringArgs = []
lineNum = -1
def __init__(self, locale, widgetPlaceholders, qStringArgs) -> None:
def __init__(self, locale, widgetPlaceholders, qStringArgs, lineNum) -> None:
self.locale = locale
self.widgetPlaceholders = widgetPlaceholders
self.qStringArgs = qStringArgs
self.lineNum = lineNum
def isCommentLine(line):
for char in commentChars:
if line.startswith(char):
return True
return False
def getNonDefaultLocales(dir):
@@ -32,11 +42,11 @@ def getNonDefaultLocales(dir):
def getAllLocaleEntries(file):
localeEntries = []
with open(file, "r", encoding="UTF-8") as f:
for line in f.readlines():
for lineNum, line in enumerate(f):
widgetPlaceholders = []
qStringArgs = []
if line.startswith(";"):
if isCommentLine(line):
continue
for word in line.split("{{"):
@@ -49,7 +59,9 @@ def getAllLocaleEntries(file):
qStringArgs.append(match.group(0))
localeEntries.append(
localeEntry(line.split("=")[0], widgetPlaceholders, qStringArgs)
localeEntry(
line.split("=")[0], widgetPlaceholders, qStringArgs, lineNum + 1
)
)
return localeEntries
@@ -73,8 +85,8 @@ def checkLocaleEntries(file, expectedLocaleEntries):
if expectedLocaleEntry is None:
result = False
print(
'ERROR: Locale entry "{}" from "{}" not found in "{}"'.format(
localeEntry.locale, file, defaultLocaleFile
'ERROR: Locale entry "{}" from "{}:{}" not found in "{}"'.format(
localeEntry.locale, file, localeEntry.lineNum, defaultLocaleFile
)
)
continue
@@ -83,8 +95,13 @@ def checkLocaleEntries(file, expectedLocaleEntries):
if placeholder not in expectedLocaleEntry.widgetPlaceholders:
result = False
print(
'ERROR: Locale entry "{}" from "{}" does contain "{}" widget placeholder while "{}" does not'.format(
localeEntry.locale, file, placeholder, defaultLocaleFile
'ERROR: Locale entry "{}" from "{}:{}" does contain "{}" widget placeholder while "{}:{}" does not'.format(
localeEntry.locale,
file,
localeEntry.lineNum,
placeholder,
defaultLocaleFile,
expectedLocaleEntry.lineNum,
)
)
@@ -92,8 +109,8 @@ def checkLocaleEntries(file, expectedLocaleEntries):
if placeholder not in localeEntry.widgetPlaceholders:
result = False
print(
'ERROR: Locale entry "{}" from "{}" does not contain "{}" widget placeholder'.format(
localeEntry.locale, file, placeholder
'ERROR: Locale entry "{}" from "{}:{}" does not contain "{}" widget placeholder'.format(
localeEntry.locale, file, localeEntry.lineNum, placeholder
)
)
@@ -101,8 +118,13 @@ def checkLocaleEntries(file, expectedLocaleEntries):
if arg not in expectedLocaleEntry.qStringArgs:
result = False
print(
'ERROR: Locale entry "{}" from "{}" does contain "{}" QString arg while "{}" does not'.format(
localeEntry.locale, file, arg, defaultLocaleFile
'ERROR: Locale entry "{}" from "{}:{}" does contain "{}" QString arg while "{}:{}" does not'.format(
localeEntry.locale,
file,
localeEntry.lineNum,
arg,
defaultLocaleFile,
expectedLocaleEntry.lineNum,
)
)
@@ -110,8 +132,8 @@ def checkLocaleEntries(file, expectedLocaleEntries):
if arg not in localeEntry.qStringArgs:
result = False
print(
'ERROR: Locale entry "{}" from "{}" does not contain "{}" QString arg'.format(
localeEntry.locale, file, arg
'ERROR: Locale entry "{}" from "{}:{}" does not contain "{}" QString arg'.format(
localeEntry.locale, file, localeEntry.lineNum, arg
)
)

View File

@@ -7,7 +7,7 @@ Build-Depends: cmake,
libcurl4-openssl-dev,
libobs-dev,
libxtst-dev,
qtbase5-dev,
qt6-base-dev,
libxss-dev,
libopencv-dev
Standards-Version: 4.6.0

View File

@@ -3,7 +3,7 @@
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
export QT_SELECT = qt5
export QT_SELECT = qt6
%:
dh $@

View File

@@ -1,33 +1,33 @@
{
"dependencies": {
"obs-studio": {
"version": "29.1.2",
"version": "30.1.2",
"baseUrl": "https://github.com/obsproject/obs-studio/archive/refs/tags",
"label": "OBS sources",
"hashes": {
"macos": "215f1fa5772c5dd9f3d6e35b0cb573912b00320149666a77864f9d305525504b",
"windows-x64": "46d451f3f42b9d2c59339ec268165849c7b7904cdf1cc2a8d44c015815a9e37d"
"macos": "490bae1c392b3b344b0270afd8cb887da4bc50bd92c0c426e96713c1ccb9701a",
"windows-x64": "c2dd03fa7fd01fad5beafce8f7156da11f9ed9a588373fd40b44a06f4c03b867"
}
},
"prebuilt": {
"version": "2023-04-12",
"version": "2024-03-19",
"baseUrl": "https://github.com/obsproject/obs-deps/releases/download",
"label": "Pre-Built obs-deps",
"hashes": {
"macos": "9535c6e1ad96f7d49960251e85a245774088d48da1d602bb82f734b10219125a",
"windows-x64": "c13a14a1acc4224b21304d97b63da4121de1ed6981297e50496fbc474abc0503"
"macos": "2e9bfb55a5e0e4c1086fa1fda4cf268debfead473089df2aaea80e1c7a3ca7ff",
"windows-x64": "6e86068371526a967e805f6f9903f9407adb683c21820db5f07da8f30d11e998"
}
},
"qt6": {
"version": "2023-04-12",
"version": "2024-03-19",
"baseUrl": "https://github.com/obsproject/obs-deps/releases/download",
"label": "Pre-Built Qt6",
"hashes": {
"macos": "eb7614544ab4f3d2c6052c797635602280ca5b028a6b987523d8484222ce45d1",
"windows-x64": "4d39364b8a8dee5aa24fcebd8440d5c22bb4551c6b440ffeacce7d61f2ed1add"
"macos": "694f1e639c017e3b1f456f735330dc5afae287cbea85757101af1368de3142c8",
"windows-x64": "72d1df34a0ef7413a681d5fcc88cae81da60adc03dcd23ef17862ab170bcc0dd"
},
"debugSymbols": {
"windows-x64": "f34ee5067be19ed370268b15c53684b7b8aaa867dc800b68931df905d679e31f"
"windows-x64": "fbddd1f659c360f2291911ac5709b67b6f8182e6bca519d24712e4f6fd3cc865"
}
}
},

View File

@@ -8,14 +8,11 @@ if(BUILD_OUT_OF_TREE)
endif()
endif()
# Subfolder for advanced scene switcher plugins
set(_PLUGIN_FOLDER "adv-ss-plugins")
# --- MACOS section ---
if(OS_MACOS)
set(ADVSS_BUNDLE_DIR "advanced-scene-switcher.plugin")
set(ADVSS_BUNDLE_MODULE_DIR "${ADVSS_BUNDLE_DIR}/Contents/MacOS")
set(ADVSS_BUNDLE_PLUGIN_DIR ${ADVSS_BUNDLE_MODULE_DIR}/${_PLUGIN_FOLDER})
set(ADVSS_BUNDLE_PLUGIN_DIR ${ADVSS_BUNDLE_MODULE_DIR}/${ADVSS_PLUGIN_FOLDER})
function(install_advss_lib_helper target where)
install(
@@ -60,10 +57,36 @@ if(OS_MACOS)
${dep}_Development)
endfunction()
function(install_advss_plugin_dependency_file ${target} dep)
target_sources(advanced-scene-switcher PRIVATE ${dep})
set_source_files_properties(${dep} PROPERTIES MACOSX_PACKAGE_LOCATION
${ADVSS_BUNDLE_PLUGIN_DIR})
function(install_advss_plugin_dependency_file target dep)
get_filename_component(_FILENAME ${dep} NAME)
string(REGEX REPLACE "\\.[^.]*$" "" _FILENAMENOEXT ${_FILENAME})
set(_DEP_NAME "${target}-${_FILENAMENOEXT}")
install(
FILES "${dep}"
DESTINATION "${ADVSS_BUNDLE_PLUGIN_DIR}"
COMPONENT ${_DEP_NAME}_Runtime
DESTINATION "${ADVSS_BUNDLE_PLUGIN_DIR}"
COMPONENT ${_DEP_NAME}_Runtime
NAMELINK_COMPONENT ${_DEP_NAME}_Development)
install(
FILES "${dep}"
DESTINATION "${ADVSS_BUNDLE_PLUGIN_DIR}"
COMPONENT obs_${_DEP_NAME}
EXCLUDE_FROM_ALL
DESTINATION "${ADVSS_BUNDLE_PLUGIN_DIR}"
COMPONENT obs_${_DEP_NAME}
EXCLUDE_FROM_ALL)
add_custom_command(
TARGET ${target}
POST_BUILD
COMMAND
"${CMAKE_COMMAND}" --install ${CMAKE_CURRENT_BINARY_DIR} --config
$<CONFIG> --prefix ${CMAKE_INSTALL_PREFIX} --component obs_${_DEP_NAME}
COMMENT "Installing ${_DEP_NAME} to OBS rundir\n"
VERBATIM)
endfunction()
# --- End of section ---
@@ -128,8 +151,8 @@ else()
function(install_advss_plugin target)
plugin_install_helper(
"${target}" "${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
"${_PLUGIN_FOLDER}")
"${target}" "${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
"${ADVSS_PLUGIN_FOLDER}")
if(NOT OS_WINDOWS)
set_target_properties(${target} PROPERTIES INSTALL_RPATH
"$ORIGIN:$ORIGIN/..")
@@ -143,12 +166,12 @@ else()
${dep}
RUNTIME
DESTINATION
"${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
"${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT
${dep}_Runtime
LIBRARY
DESTINATION
"${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
"${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT
${dep}_Runtime
NAMELINK_COMPONENT
@@ -159,13 +182,13 @@ else()
${dep}
RUNTIME
DESTINATION
"${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
"${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT
obs_${dep}
EXCLUDE_FROM_ALL
LIBRARY
DESTINATION
"${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
"${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT
obs_${dep}
EXCLUDE_FROM_ALL)
@@ -188,18 +211,18 @@ else()
install(
FILES "${dep}"
DESTINATION "${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
DESTINATION "${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT ${_DEP_NAME}_Runtime
DESTINATION "${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
DESTINATION "${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT ${_DEP_NAME}_Runtime
NAMELINK_COMPONENT ${_DEP_NAME}_Development)
install(
FILES "${dep}"
DESTINATION "${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
DESTINATION "${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT obs_${_DEP_NAME}
EXCLUDE_FROM_ALL
DESTINATION "${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
DESTINATION "${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT obs_${_DEP_NAME}
EXCLUDE_FROM_ALL)
@@ -260,8 +283,14 @@ endfunction()
function(setup_advss_plugin target)
setup_obs_lib_dependency(${target})
find_qt(COMPONENTS Widgets Core)
target_link_libraries(${target} PRIVATE Qt::Core Qt::Widgets)
find_package(Qt6 REQUIRED COMPONENTS Widgets Core)
target_link_libraries(${target} PRIVATE Qt6::Core Qt6::Widgets)
# Ignore QCheckBox::stateChanged deprecation warning until minimum supported
# Qt version is at least Qt 6.7, which introduces QCheckBox::checkStateChanged
if(Qt6_VERSION VERSION_GREATER "6.7.0")
target_compile_definitions(${target} PRIVATE QT_NO_DEPRECATED_WARNINGS)
endif()
set_target_properties(
${target}
@@ -309,7 +338,6 @@ function(install_advss_plugin_dependency)
if(NOT PARSED_ARGS_TARGET)
message(FATAL_ERROR "You must provide a target")
endif()
set(_PLUGIN_FOLDER "adv-ss-plugins")
foreach(_DEPENDENCY ${PARSED_ARGS_DEPENDENCIES})
if(EXISTS ${_DEPENDENCY})
install_advss_plugin_dependency_file(${PARSED_ARGS_TARGET} ${_DEPENDENCY})

View File

@@ -17,85 +17,6 @@ if(NOT QT_VERSION)
set_property(CACHE QT_VERSION PROPERTY STRINGS AUTO 5 6)
endif()
# find_qt: Macro to find best possible Qt version for use with the project:
macro(find_qt)
set(multiValueArgs COMPONENTS COMPONENTS_WIN COMPONENTS_MAC COMPONENTS_LINUX)
cmake_parse_arguments(find_qt "" "${oneValueArgs}" "${multiValueArgs}"
${ARGN})
# Do not use versionless targets in the first step to avoid Qt::Core being
# clobbered by later opportunistic find_package runs
set(QT_NO_CREATE_VERSIONLESS_TARGETS TRUE)
message(DEBUG "Start Qt version discovery...")
# Loop until _QT_VERSION is set or FATAL_ERROR aborts script execution early
while(NOT _QT_VERSION)
message(DEBUG "QT_VERSION set to ${QT_VERSION}")
if(QT_VERSION STREQUAL AUTO AND NOT qt_test_version)
set(qt_test_version 6)
elseif(NOT QT_VERSION STREQUAL AUTO)
set(qt_test_version ${QT_VERSION})
endif()
message(DEBUG "Attempting to find Qt${qt_test_version}")
find_package(
Qt${qt_test_version}
COMPONENTS Core
QUIET)
if(TARGET Qt${qt_test_version}::Core)
set(_QT_VERSION
${qt_test_version}
CACHE INTERNAL "")
message(STATUS "Qt version found: ${_QT_VERSION}")
unset(qt_test_version)
break()
elseif(QT_VERSION STREQUAL AUTO)
if(qt_test_version EQUAL 6)
message(WARNING "Qt6 was not found, falling back to Qt5")
set(qt_test_version 5)
continue()
endif()
endif()
message(FATAL_ERROR "Neither Qt6 nor Qt5 found.")
endwhile()
# Enable versionless targets for the remaining Qt components
set(QT_NO_CREATE_VERSIONLESS_TARGETS FALSE)
set(qt_components ${find_qt_COMPONENTS})
if(OS_WINDOWS)
list(APPEND qt_components ${find_qt_COMPONENTS_WIN})
elseif(OS_MACOS)
list(APPEND qt_components ${find_qt_COMPONENTS_MAC})
else()
list(APPEND qt_components ${find_qt_COMPONENTS_LINUX})
endif()
message(DEBUG "Trying to find Qt components ${qt_components}...")
find_package(Qt${_QT_VERSION} REQUIRED ${qt_components})
list(APPEND qt_components Core)
if("Gui" IN_LIST find_qt_COMPONENTS_LINUX)
list(APPEND qt_components "GuiPrivate")
endif()
# Check for versionless targets of each requested component and create if
# necessary
foreach(component IN LISTS qt_components)
message(DEBUG "Checking for target Qt::${component}")
if(NOT TARGET Qt::${component} AND TARGET Qt${_QT_VERSION}::${component})
add_library(Qt::${component} INTERFACE IMPORTED)
set_target_properties(
Qt::${component} PROPERTIES INTERFACE_LINK_LIBRARIES
Qt${_QT_VERSION}::${component})
endif()
set_property(TARGET Qt::${component} PROPERTY INTERFACE_COMPILE_FEATURES "")
endforeach()
endmacro()
# check_uuid: Helper function to check for valid UUID
function(check_uuid uuid_string return_value)
set(valid_uuid TRUE)

View File

@@ -19,14 +19,13 @@ AdvSceneSwitcher.generalTab.status.autoStart.streaming="Streamen"
AdvSceneSwitcher.generalTab.status.autoStart.recordingAndStreaming="Aufnehmen oder Streamen"
AdvSceneSwitcher.generalTab.status.checkInterval="Teste Bedingungen alle"
AdvSceneSwitcher.generalTab.generalBehavior="Allgemeines Verhalten"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet="Wenn keine Bedingung erfüllt ist für "
AdvSceneSwitcher.generalTab.generalBehavior.onNoMetDelayTooltip="Kann nur so genau sein wie das eingestellte Interval zum Testen der Bedingungen."
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch="Nicht wechseln"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom="Wechsle zu einer Szene auf dem Zufall-Tab"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="Wechsle zu:"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch="Wenn keine Bedingung erfüllt ist für "
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatchDelay.tooltip="Kann nur so genau sein wie das eingestellte Interval zum Testen der Bedingungen."
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.dontSwitch="Nicht wechseln"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchToRandom="Wechsle zu einer Szene auf dem Zufall-Tab"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchTo="Wechsle zu:"
AdvSceneSwitcher.generalTab.generalBehavior.cooldown="Nach Ausführen von Aktionen überspringe das Ausführen von Aktionen für"
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="In diesem Zeitraum werden potentielle erfüllte Bedingungen ignoriert!"
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Ausführliches Logging"
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Fensterposition und -größe speichern"
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Benachrichtigungen im System-Tray anzeigen"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Deaktiviere UI Tipps"
@@ -94,7 +93,7 @@ AdvSceneSwitcher.macroTab.highlightExecutedMacros="Kürzlich ausgeführte Makros
AdvSceneSwitcher.macroTab.highlightTrueConditions="Bedingungen des aktuell ausgewählten Makros hervorheben, die kürzlich als wahr bewertet wurden"
AdvSceneSwitcher.macroTab.highlightPerformedActions="Zuletzt ausgeführte Aktionen des aktuell ausgewählten Makros hervorheben"
AdvSceneSwitcher.macroTab.newMacroRegisterHotkey="Hotkeys zur Steuerung des Pausen-Status neuer Makros registrieren"
AdvSceneSwitcher.macroTab.currentDisableHotkeys="Hotkeys zur Steuerung des Pausen-Status ausgewählter Makros registrieren"
AdvSceneSwitcher.macroTab.currentRegisterHotkeys="Hotkeys zur Steuerung des Pausen-Status ausgewählter Makros registrieren"
; Macro List
AdvSceneSwitcher.macroList.deleted="gelöscht"
@@ -142,8 +141,6 @@ AdvSceneSwitcher.condition.scene.type.currentPattern="Aktuelle Szene entspricht"
AdvSceneSwitcher.condition.scene.type.previousPattern="Vorherige Szene entspricht"
AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour="Während des Übergangs die Ziel-Szene überprüfen"
AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="Während des Übergangs die Quell-Szene überprüfen"
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}"
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
AdvSceneSwitcher.condition.window="Fenster"
AdvSceneSwitcher.condition.file="Datei"
AdvSceneSwitcher.condition.file.type.match="entspricht"
@@ -159,7 +156,7 @@ AdvSceneSwitcher.condition.media.source="Quelle"
AdvSceneSwitcher.condition.media.anyOnScene="Beliebige Medienquelle in"
AdvSceneSwitcher.condition.media.allOnScene="Alle Medienquellen in"
AdvSceneSwitcher.condition.media.inconsistencyInfo="Leider verhalten sich nicht alle Medien-Quell-Typen gleich (z.B. Medien-Quelle vs. VLC-Video-Quelle \"Gestoppt\"-Status).\nBitte experimentieren, was in eurem Setup funktioniert!"
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}Status ist{{states}}und{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.media.layout.legacy="{{sourceTypes}}{{mediaSources}}{{scenes}}{{checkTypes}}Status ist{{states}}und{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Video"
AdvSceneSwitcher.condition.video.condition.match="entspricht genau"
AdvSceneSwitcher.condition.video.condition.differ="stimmt nicht überein"
@@ -214,7 +211,6 @@ AdvSceneSwitcher.condition.stream.state.start="Stream läuft"
AdvSceneSwitcher.condition.stream.state.stop="Stream gestoppt"
AdvSceneSwitcher.condition.stream.state.starting="Stream wird gestartet"
AdvSceneSwitcher.condition.stream.state.stopping="Stream wird gestoppt"
AdvSceneSwitcher.condition.stream.entry="{{streamState}}{{keyFrameInterval}}"
AdvSceneSwitcher.condition.record="Aufnahme"
AdvSceneSwitcher.condition.record.state.start="Aufnahme läuft"
AdvSceneSwitcher.condition.record.state.pause="Aufnahme pausiert"
@@ -321,11 +317,8 @@ AdvSceneSwitcher.condition.date.entry.nextMatchDate="Nächster Treffer bei: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} Bei Wiederholung ausgewähltes Datum auf Wiederholungsdatum aktualisieren"
AdvSceneSwitcher.condition.sceneTransform="Szenenelement transformieren"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Transformation erhalten"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="Auf{{scenes}}{{sources}}{{types}}"
AdvSceneSwitcher.condition.sceneTransform.entry.type.matches="entspricht Transformation"
AdvSceneSwitcher.condition.sceneTransform.entry.type.changed="hat sich geändert"
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}"
AdvSceneSwitcher.condition.sceneTransform.condition.match="entspricht Transformation"
AdvSceneSwitcher.condition.sceneTransform.condition.changed="hat sich geändert"
AdvSceneSwitcher.condition.transition="Übergang"
AdvSceneSwitcher.condition.transition.type.current="Aktueller Übergangstyp ist"
AdvSceneSwitcher.condition.transition.type.duration="Aktuelle Übergangsdauer beträgt"
@@ -427,7 +420,6 @@ AdvSceneSwitcher.action.replay.saveWarn="Warnung: Ein zu häufiges Speichern kan
AdvSceneSwitcher.action.replay.type.stop="Replay Buffer stoppen"
AdvSceneSwitcher.action.replay.type.start="Replay Buffer starten"
AdvSceneSwitcher.action.replay.type.save="Replay Buffer speichern"
AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.streaming="Stream"
AdvSceneSwitcher.action.streaming.type.stop="Stream stoppen"
AdvSceneSwitcher.action.streaming.type.start="Stream starten"
@@ -496,10 +488,10 @@ AdvSceneSwitcher.action.sceneOrder.type.moveDown="Nach unten verschieben"
AdvSceneSwitcher.action.sceneOrder.type.moveTop="An erste Stelle verschieben"
AdvSceneSwitcher.action.sceneOrder.type.moveBottom="An letzte Stelle verschieben"
AdvSceneSwitcher.action.sceneOrder.type.movePosition="An Position verschieben"
AdvSceneSwitcher.action.sceneOrder.entry="Auf{{scenes}}{{actions}}{{sources}}{{position}}"
AdvSceneSwitcher.action.sceneOrder.entry="Auf{{scenes}}{{actions}}{{sources}}{{sources2}}{{position}}"
AdvSceneSwitcher.action.sceneTransform="Szenenelement transformieren"
AdvSceneSwitcher.action.sceneTransform.getTransform="Transformation erhalten"
AdvSceneSwitcher.action.sceneTransform.entry="Auf{{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.sceneTransform.entry="Auf{{scenes}}{{action}}{{rotation}}{{sources}}{{settingSelection}}{{singleSettingValue}}"
AdvSceneSwitcher.action.file="Datei"
AdvSceneSwitcher.action.file.type.write="Schreiben"
AdvSceneSwitcher.action.file.type.append="Anhängen"
@@ -533,9 +525,8 @@ AdvSceneSwitcher.action.screenshot.save.default="Standard"
AdvSceneSwitcher.action.screenshot.save.custom="Benutzerdefiniert"
AdvSceneSwitcher.action.screenshot.type.source="Quelle"
AdvSceneSwitcher.action.screenshot.type.scene="Szene"
AdvSceneSwitcher.action.screenshot.mainOutput="OBS's Haupt-Ausgabe"
AdvSceneSwitcher.action.screenshot.blackscreenNote="Quellen oder Szenen, die nicht immer gerendert werden, können dazu führen, dass einige Teile der Screenshots leer bleiben."
AdvSceneSwitcher.action.screenshot.entry="Screenshot{{targetType}}{{sources}}{{scenes}}und speichere in{{saveType}}Pfad"
AdvSceneSwitcher.action.screenshot.entry="Screenshot{{targetType}}{{sources}}{{scenes}}und speichere in{{saveType}}{{variables}}Pfad"
AdvSceneSwitcher.action.profile="Profil"
AdvSceneSwitcher.action.profile.entry="Aktives Profil umschalten auf {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="Szenensammlung"
@@ -734,31 +725,6 @@ AdvSceneSwitcher.videoTab.ignoreInactiveSource="außer Video Quelle ist inaktiv"
AdvSceneSwitcher.videoTab.entry="Wenn {{videoSources}} {{condition}} {{filePath}} {{browseButton}} für {{duration}} wechsle zu {{scenes}} mit {{transitions}} {{ignoreInactiveSource}}"
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>Dieser Tab ermöglicht es basierend auf der Videoausgabe von Quellen scenen zu wechseln.<br/>Für eine noch bessere Implementierung dieser Funktionalität siehe <a href=\"https://obsproject.com/forum/resources/pixel-match-switcher.1202/\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a>.<br/><br/>Klicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen..</p></body></html>"
; Network Tab
AdvSceneSwitcher.networkTab.title="Netzwerk"
AdvSceneSwitcher.networkTab.description="Über diesen Tab kann die aktive Szene einer anderen OBS-Instanz ferngesteuert werden.\nBitte beachten, dass die Szenennamen auf allen OBS-Instanzen exakt übereinstimmen müssen."
AdvSceneSwitcher.networkTab.warning="Die Verwendung des Servers außerhalb eines lokalen Netzwerks kann dazu führen, dass die aktive Szene von dritten Personen ausgelesen werden kann."
AdvSceneSwitcher.networkTab.server="Server starten (Sendet Szenenwechselnachrichten zu allen verbundenen Clients)"
AdvSceneSwitcher.networkTab.server.port="Port"
AdvSceneSwitcher.networkTab.server.lockToIPv4="Nur IPv4 verwenden (deaktiviert IPv6)"
AdvSceneSwitcher.networkTab.server.sendSceneChange="Sende Nachrichten für Szenenwechsel"
AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Sende nur Szenenwechselnachrichten für automatisierte Szenenwechsel"
AdvSceneSwitcher.networkTab.server.sendPreview="Sende Nachrichten für Vorschau Szenenwechsel im Studio-Modus"
AdvSceneSwitcher.networkTab.startFailed.message="Der WebSocket-Server konnte nicht gestartet werden, mögliche Gründe:\n - Der TCP-Port %1 wird möglicherweise gerade von einem anderen Programm verwendet. Versuchen Sie einen anderen Port in den Websocket-Servereinstellungen zu setzen oder alle Programme zu beenden, die den Port möglicherweise verwenden.\n - Fehler: %2"
AdvSceneSwitcher.networkTab.server.status.currentStatus="Aktueller Status"
AdvSceneSwitcher.networkTab.server.status.notRunning="Nicht gestartet"
AdvSceneSwitcher.networkTab.server.status.starting="Startet"
AdvSceneSwitcher.networkTab.server.status.running="Server läuft"
AdvSceneSwitcher.networkTab.server.restart="Server neu starten"
AdvSceneSwitcher.networkTab.client="Client starten (Empfängt Szenenwechselnachrichten )"
AdvSceneSwitcher.networkTab.client.address="Hostname oder IP-Adresse"
AdvSceneSwitcher.networkTab.client.port="Port"
AdvSceneSwitcher.networkTab.client.status.currentStatus="Aktueller status"
AdvSceneSwitcher.networkTab.client.status.disconnected="Verbindung getrennt"
AdvSceneSwitcher.networkTab.client.status.connecting="Verbinde"
AdvSceneSwitcher.networkTab.client.status.connected="Verbunden"
AdvSceneSwitcher.networkTab.client.reconnect="Erneutes Verbinden erzwingen"
; Scene Group Tab
AdvSceneSwitcher.sceneGroupTab.title="Szenengruppe"
AdvSceneSwitcher.sceneGroupTab.list="Szenengruppen"
@@ -779,30 +745,6 @@ AdvSceneSwitcher.sceneGroupTab.exists="Szenengruppen- oder Szenenname existiert
AdvSceneSwitcher.sceneGroupTab.help="Szenengruppen können, genau wie reguläre Szenen, als Ziel eines Szenenwechsler Eintrags ausgewählt werden.\n\nSzenengruppen bestehen aus einer Liste von Szenen.\nDie aktive Szene der Szenengruppe schreitet abhängig von den konfigurierten Einstellungen durch die Liste der zugewiesenen Szenen fort.\n\nMögliche Einstellungen zum Fortschreiten der aktiven Szene sind:\nAnzahl der Szenenwechsel.\nZeit seit dem letzten Szenenwechsel in der Szenengruppe.\nOder zufällig.\n\nSo kann zum Beispiel eine Szenengruppe welche die folgenden Szenen enthält ...\nSzene 1\nSzene 2\nSzene 3 \n... beim erstem Mal zu \"Szene 1\" wechseln.\nBeim zweiten Mal zu \"Szene 2\".\nDie restlichem Male wechselt die Szenengruppe zu \"Szene 3\".\n\nKlicke auf das markierte Plus Symbol, um eine neuen Szenengruppe hinzuzufügen."
AdvSceneSwitcher.sceneGroupTab.scenes.help="Wähle die Szenengruppe, die du bearbeiten möchtest, auf der linken Seite aus.\n\nWähle oben eine Szene aus, die du zur Szenengruppe hinzufügen möchtest, und klicke das Plus Symbol.\n\nDie gleiche Szene kann mehrfach in derselben Szenengruppe auftauchen."
; Scene Trigger Tab
AdvSceneSwitcher.sceneTriggerTab.title="Szenen Trigger"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none="--Trigger auswählen--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive="aktiv ist"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneInactive="nicht aktiv ist"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneLeave="verlassen wird"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none="--Aktion auswählen--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording="Aufnahme starten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.pauseRecording="Aufnahme pausieren"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unpauseRecording="Aufnahme fortsetzen"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopRecording="Aufnahme stoppen"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming="Streamen stoppen"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="Streamen starten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="Replay Buffer starten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="Replay Buffer stoppen"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="Audio Quelle stumm schalten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="Audio Quelle aktiv schalten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="Szenenwechsler starten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="Szenenwechsler stoppen"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startVirtualCamera="Virtuelle Kamera starten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopVirtualCamera="Virtuelle Kamera stoppen"
AdvSceneSwitcher.sceneTriggerTab.entry="Wenn {{scenes}} {{triggers}} {{actions}} {{audioSources}} nach {{duration}} "
AdvSceneSwitcher.sceneTriggerTab.help="Dieser Tab ermöglicht es automatisiert Handlungen, wie etwa das Stoppen von einer Aufnahme oder des Streamens, bei einem Szenenwechsel auszuführen."
; Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Starte den Erweiterten Szenenwechsler"
AdvSceneSwitcher.hotkey.stopSwitcherHotkey="Stoppe den Erweiterten Szenenwechsler"
@@ -899,7 +841,7 @@ AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Dies scheint das erste
AdvSceneSwitcher.deprecatedTabWarning="Die Entwicklung für dieses Tab wurde gestoppt!\nBitte stattdessen zur Verwendung des Makros-Tab übergehen.\nDieser Hinweis kann im Allgemein-Tab deaktiviert werden."
AdvSceneSwitcher.unit.milliseconds="Millisekunden"
AdvSceneSwitcher.unit.secends="Sekunden"
AdvSceneSwitcher.unit.seconds="Sekunden"
AdvSceneSwitcher.unit.minutes="Minuten"
AdvSceneSwitcher.unit.hours="Stunden"
AdvSceneSwitcher.duration.condition.none="Kein Dauer-Modifikator"

File diff suppressed because it is too large Load Diff

View File

@@ -19,13 +19,12 @@ AdvSceneSwitcher.generalTab.status.autoStart.streaming="Emitiendo"
AdvSceneSwitcher.generalTab.status.autoStart.recordingAndStreaming="Grabando o emitiendo"
AdvSceneSwitcher.generalTab.status.checkInterval="Comprobar las condiciones de cambio cada"
AdvSceneSwitcher.generalTab.generalBehavior="Comportamiento general"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet="Si no se cumple ninguna condición por"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMetDelayTooltip="Solo será tan preciso como el intervalo de comprobación configurado."
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch="No cambiar"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom="Cambiar a una escena aleatoria"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="Cambiar a:"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch="Si no se cumple ninguna condición por"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatchDelay.tooltip="Solo será tan preciso como el intervalo de comprobación configurado."
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.dontSwitch="No cambiar"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchToRandom="Cambiar a una escena aleatoria"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchTo="Cambiar a:"
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="¡Durante este tiempo, se ignorarán las posibles coincidencias!"
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Habilitar el registro detallado"
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Guardar la posición y el tamaño de la ventana"
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Mostrar notificaciones del sistema"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Deshabilitar sugerencias de interfaz de usuario"
@@ -117,8 +116,6 @@ AdvSceneSwitcher.condition.scene.type.changed="Escena cambiada"
AdvSceneSwitcher.condition.scene.type.notChanged="La escena no ha cambiado"
AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour="Durante la transición, verifique la escena de destino de la transición"
AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="Durante la transición, verifique la escena de origen de la transición"
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}"
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
AdvSceneSwitcher.condition.window="Ventana"
AdvSceneSwitcher.condition.file="Archivo"
AdvSceneSwitcher.condition.file.entry.line1="Contenido de{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
@@ -128,7 +125,7 @@ AdvSceneSwitcher.condition.media="Medios"
AdvSceneSwitcher.condition.media.anyOnScene="Cualquier fuente multimedia activada"
AdvSceneSwitcher.condition.media.allOnScene="Todas las fuentes de medios activadas"
AdvSceneSwitcher.condition.media.inconsistencyInfo="Desafortunadamente, no todos los tipos de fuentes de medios se comportan de la misma manera (p. ej., fuente de medios frente a estado \"Detenido\" de fuente de video VLC).\n¡Así que experimente lo que funciona para su configuración!"
AdvSceneSwitcher.condition.media.entry="El estado de{{sourceTypes}}{{mediaSources}}{{scenes}}es{{states}}y{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.media.layout.legacy="El estado de{{sourceTypes}}{{mediaSources}}{{scenes}}{{checkTypes}}es{{states}}y{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Video"
AdvSceneSwitcher.condition.video.condition.match="coincide exactamente"
AdvSceneSwitcher.condition.video.condition.differ="no coincide"
@@ -174,7 +171,6 @@ AdvSceneSwitcher.condition.stream.state.start="Transmisión en ejecución"
AdvSceneSwitcher.condition.stream.state.stop="Transmisión detenida"
AdvSceneSwitcher.condition.stream.state.starting="Inicio de transmisión"
AdvSceneSwitcher.condition.stream.state.stopping="Detener transmisión"
AdvSceneSwitcher.condition.stream.entry="{{streamState}}{{keyFrameInterval}}"
AdvSceneSwitcher.condition.record="Grabación"
AdvSceneSwitcher.condition.record.state.start="Grabación en ejecución"
AdvSceneSwitcher.condition.record.state.pause="Grabación en pausa"
@@ -269,10 +265,7 @@ AdvSceneSwitcher.condition.date.entry.nextMatchDate="Próxima coincidencia en: %
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} Al repetir actualizar la fecha seleccionada para repetir la fecha"
AdvSceneSwitcher.condition.sceneTransform="Transformar elemento de escena"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Obtener transformación"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="En{{scenes}}{{sources}}{{types}}"
AdvSceneSwitcher.condition.sceneTransform.entry.type.matches="coincide con la transformación"
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}"
AdvSceneSwitcher.condition.sceneTransform.condition.match="coincide con la transformación"
AdvSceneSwitcher.condition.transition="Transición"
AdvSceneSwitcher.condition.transition.type.current="El tipo de transición actual es"
AdvSceneSwitcher.condition.transition.type.duration="La duración de la transición actual es"
@@ -348,7 +341,6 @@ AdvSceneSwitcher.action.replay.saveWarn="Advertencia: ¡Guardar con demasiada fr
AdvSceneSwitcher.action.replay.type.stop="Detener el búfer de reproducción"
AdvSceneSwitcher.action.replay.type.start="Iniciar búfer de reproducción"
AdvSceneSwitcher.action.replay.type.save="Guardar búfer de reproducción"
AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.streaming="Transmisión"
AdvSceneSwitcher.action.streaming.type.stop="Detener transmisión"
AdvSceneSwitcher.action.streaming.type.start="Iniciar transmisión"
@@ -413,10 +405,10 @@ AdvSceneSwitcher.action.sceneOrder.type.moveDown="Mover hacia abajo"
AdvSceneSwitcher.action.sceneOrder.type.moveTop="Mover al principio"
AdvSceneSwitcher.action.sceneOrder.type.moveBottom="Mover al final"
AdvSceneSwitcher.action.sceneOrder.type.movePosition="Mover a la posición"
AdvSceneSwitcher.action.sceneOrder.entry="En{{scenes}}{{actions}}{{sources}}{{position}}"
AdvSceneSwitcher.action.sceneOrder.entry="En{{scenes}}{{actions}}{{sources}}{{sources2}}{{position}}"
AdvSceneSwitcher.action.sceneTransform="Transformar elemento de escena"
AdvSceneSwitcher.action.sceneTransform.getTransform="Obtener transformación"
AdvSceneSwitcher.action.sceneTransform.entry="En{{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.sceneTransform.entry="En{{scenes}}{{action}}{{rotation}}{{sources}}{{settingSelection}}{{singleSettingValue}}"
AdvSceneSwitcher.action.file="Archivo"
AdvSceneSwitcher.action.file.type.write="Escribir"
AdvSceneSwitcher.action.file.type.append="Agregar"
@@ -445,7 +437,6 @@ AdvSceneSwitcher.action.random="Aleatorio"
AdvSceneSwitcher.action.random.entry="Ejecute aleatoriamente cualquiera de las siguientes macros (las macros en pausa se ignoran)"
AdvSceneSwitcher.action.systray="Notificación de la bandeja del sistema"
AdvSceneSwitcher.action.screenshot="Captura de pantalla"
AdvSceneSwitcher.action.screenshot.mainOutput="Salida principal de OBS"
AdvSceneSwitcher.action.profile="Perfil"
AdvSceneSwitcher.action.profile.entry="Cambiar perfil activo a {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="Colección de escenas"
@@ -622,31 +613,6 @@ AdvSceneSwitcher.videoTab.ignoreInactiveSource="a menos que la fuente esté inac
AdvSceneSwitcher.videoTab.entry="Cuando {{videoSources}} {{condition}} {{filePath}} {{browseButton}} durante {{duration}} cambiar a {{scenes}} usando {{transitions}} {{ignoreInactiveSource}}"
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>Esta pestaña te permitirá cambiar escenas según la salida de vídeo actual de las fuentes seleccionadas.<br/>Asegúrate de revisar <a href=\"https://obsproject.com/forum/resources/pixel-match-switcher.1202\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a> para una implementación aún mejor de esta funcionalidad.<br/><br/> Haz clic en el símbolo más resaltado para continuar.</p></body></html>"
; Network Tab
AdvSceneSwitcher.networkTab.title="Red"
AdvSceneSwitcher.networkTab.description="Esta pestaña le permitirá controlar de forma remota la escena activa de otra instancia de OBS.\nTenga en cuenta que los nombres de las escenas deben coincidir exactamente en todas las instancias de OBS".
AdvSceneSwitcher.networkTab.warning="Ejecutar el servidor fuera de una red local permitirá que terceros lean la escena activa".
AdvSceneSwitcher.networkTab.server="Iniciar servidor (envía mensajes de cambio de escena a todos los clientes conectados)"
AdvSceneSwitcher.networkTab.server.port="Puerto"
AdvSceneSwitcher.networkTab.server.lockToIPv4="Bloquear servidor para usar solo IPv4"
AdvSceneSwitcher.networkTab.server.sendSceneChange="Enviar mensajes para cambios de escena"
AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Solo enviar mensajes para cambios de escena automatizados"
AdvSceneSwitcher.networkTab.server.sendPreview="Enviar mensajes para obtener una vista previa del cambio de escena cuando se ejecuta en Modo Estudio"
AdvSceneSwitcher.networkTab.startFailed.message="El servidor WebSockets no pudo iniciarse, tal vez porque:\n - El puerto TCP %1 puede estar actualmente en uso en otro lugar de este sistema, posiblemente por otra aplicación. Intente configurar un puerto TCP diferente en el WebSocket configuración del servidor o detenga cualquier aplicación que pueda estar usando este puerto.\n - Mensaje de error: %2"
AdvSceneSwitcher.networkTab.server.status.currentStatus="Estado actual"
AdvSceneSwitcher.networkTab.server.status.notRunning="Desconectado"
AdvSceneSwitcher.networkTab.server.status.starting="Iniciando"
AdvSceneSwitcher.networkTab.server.status.running="En ejecución"
AdvSceneSwitcher.networkTab.server.restart="Reiniciar servidor"
AdvSceneSwitcher.networkTab.client="Iniciar cliente (Recibe mensajes de cambios de escena)"
AdvSceneSwitcher.networkTab.client.address="Nombre de host o dirección IP"
AdvSceneSwitcher.networkTab.client.port="Puerto"
AdvSceneSwitcher.networkTab.client.status.currentStatus="Estado actual"
AdvSceneSwitcher.networkTab.client.status.disconnected="Desconectado"
AdvSceneSwitcher.networkTab.client.status.connecting="Conectando"
AdvSceneSwitcher.networkTab.client.status.connected="Conectado"
AdvSceneSwitcher.networkTab.client.reconnect="Forzar reconexión"
; Scene Group Tab
AdvSceneSwitcher.sceneGroupTab.title="Grupo de escenas"
AdvSceneSwitcher.sceneGroupTab.list="Grupos de escenas"
@@ -667,30 +633,6 @@ AdvSceneSwitcher.sceneGroupTab.exists="El grupo de escenas o el nombre de la esc
AdvSceneSwitcher.sceneGroupTab.help="Los grupos de escenas se pueden seleccionar como un objetivo al igual que una escena normal.\n\nComo sugiere el nombre, un grupo de escenas es una colección de varias escenas.\nEl grupo de escenas avanzará a través de la lista de sus escenas asignadas según los ajustes configurados, que se puede encontrar en el lado derecho.\n\nPuedes configurar el grupo de escenas para avanzar a la siguiente escena en la lista:\nDespués de varias veces, el grupo de escenas se selecciona como objetivo.\nDespués de que un cierto período de tiempo haya pasado.\nO al azar.\n\nPor ejemplo, un grupo de escenas que contiene las escenas ... \nEscena 1 \nEscena 2 \nEscena 3 \n ... activará la \"Escena 1 \" la primera vez que se seleccione como objetivo. \nLa segunda vez se activará \"Escena 2 \". \nLas veces restantes \"Escena 3 \" se activarán. \n\nHaz clic en el símbolo más resaltado a continuación para agregar un nuevo grupo de escenas."
AdvSceneSwitcher.sceneGroupTab.scenes.help="Selecciona el grupo de escenas que deseas modificar a la izquierda. \n\nSelecciona una escena para agregar a este grupo de escenas seleccionando la escena de arriba y haciendo clic en el símbolo más a continuación. \n\nSe puede agregar una escena varias veces al mismo grupo de escenas."
; Scene Trigger Tab
AdvSceneSwitcher.sceneTriggerTab.title="Activadores de escena"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none="--selecciona el activador--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive="esté activo"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneInactive="no esté activo"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneLeave="cambie a otra escena"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none="--Selecciona la acción--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording="empezar a grabar"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.pauseRecording="pausar la grabación"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unpauseRecording="reanudar grabación"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopRecording="detener grabación"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming="detener transmisión"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="iniciar transmisión"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="iniciar búfer de repetición"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="detener búfer de repetición"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="silenciar fuente"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="dejar de silenciar fuente"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="iniciar el selector de escenas"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="detener el selector de escenas"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startVirtualCamera="Iniciar Camara Virtual"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopVirtualCamera="Detener Camara Virtual"
AdvSceneSwitcher.sceneTriggerTab.entry="Cuando {{scenes}} {{triggers}} {{actions}} {{audioSources}} después de {{duration}}"
AdvSceneSwitcher.sceneTriggerTab.help="Esta pestaña te permite activar acciones sobre cambios de escena, como detener la grabación o la transmisión."
; Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Iniciar Advanced Scene Switcher"
AdvSceneSwitcher.hotkey.stopSwitcherHotkey="Detener Advanced Scene Switcher"
@@ -744,7 +686,7 @@ AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Esta parece ser la prim
AdvSceneSwitcher.deprecatedTabWarning="¡Se detuvo el desarrollo de esta pestaña!\nConsidere cambiar a macros en su lugar.\nEsta sugerencia se puede desactivar en la pestaña General".
AdvSceneSwitcher.unit.milliseconds="milisegundos"
AdvSceneSwitcher.unit.secends="segundos"
AdvSceneSwitcher.unit.seconds="segundos"
AdvSceneSwitcher.unit.minutes="minutos"
AdvSceneSwitcher.unit.hours="horas"
AdvSceneSwitcher.duration.condition.none="Sin límite de tiempo"

View File

@@ -19,14 +19,13 @@ AdvSceneSwitcher.generalTab.status.autoStart.streaming="Diffusion en continu"
AdvSceneSwitcher.generalTab.status.autoStart.recordingAndStreaming="Enregistrement ou Diffusion en continu"
AdvSceneSwitcher.generalTab.status.checkInterval="Vérifier les conditions toutes les"
AdvSceneSwitcher.generalTab.generalBehavior="Comportement général"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet="Si aucune action n'est effectuée pendant"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMetDelayTooltip="Sera aussi précis que l'intervalle de vérification configuré."
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch="Ne pas basculer"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom="Basculer vers n'importe quelle scène dans l'onglet Aléatoire"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="Basculer vers :"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch="Si aucune action n'est effectuée pendant"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatchDelay.tooltip="Sera aussi précis que l'intervalle de vérification configuré."
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.dontSwitch="Ne pas basculer"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchToRandom="Basculer vers n'importe quelle scène dans l'onglet Aléatoire"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchTo="Basculer vers :"
AdvSceneSwitcher.generalTab.generalBehavior.cooldown="Après avoir effectué des actions, sauter l'exécution d'actions pendant"
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="Pendant cette période, les correspondances potentielles seront ignorées !"
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Activer les journaux détaillés"
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Enregistrer la position et la taille de la fenêtre"
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Afficher les notifications dans la zone de notification système"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Désactiver les indications de l'interface utilisateur"
@@ -109,7 +108,7 @@ AdvSceneSwitcher.macroTab.highlightExecutedMacros="Mettre en surbrillance les ma
AdvSceneSwitcher.macroTab.highlightTrueConditions="Mettre en surbrillance les conditions de la macro actuellement sélectionnée qui ont évalué à vrai récemment"
AdvSceneSwitcher.macroTab.highlightPerformedActions="Mettre en surbrillance les actions récemment effectuées de la macro actuellement sélectionnée"
AdvSceneSwitcher.macroTab.newMacroRegisterHotkey="Enregistrer des raccourcis clavier pour contrôler l'état de pause des nouvelles macros"
AdvSceneSwitcher.macroTab.currentDisableHotkeys="Enregistrer des raccourcis clavier pour contrôler l'état de pause de la macro sélectionnée"
AdvSceneSwitcher.macroTab.currentRegisterHotkeys="Enregistrer des raccourcis clavier pour contrôler l'état de pause de la macro sélectionnée"
AdvSceneSwitcher.macroTab.currentSkipExecutionOnStartup="Ignorer l'exécution des actions de la macro actuelle au démarrage"
AdvSceneSwitcher.macroTab.currentRegisterDock="Enregistrer le widget du dock pour contrôler l'état de pause de la macro sélectionnée ou l'exécuter manuellement"
AdvSceneSwitcher.macroTab.currentDockAddRunButton="Ajouter un bouton pour exécuter la macro"
@@ -196,7 +195,7 @@ AdvSceneSwitcher.condition.media.source="Source"
AdvSceneSwitcher.condition.media.anyOnScene="Toute source média sur la scène"
AdvSceneSwitcher.condition.media.allOnScene="Toutes les sources média sur la scène"
AdvSceneSwitcher.condition.media.inconsistencyInfo="Malheureusement, toutes les sources média ne se comportent pas de la même manière (par exemple, l'état \"Arrêté\" de la source Media par rapport à la source Vidéo VLC).\nPar conséquent, veuillez expérimenter ce qui fonctionne le mieux pour votre configuration !"
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}l'état est{{states}}et{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.media.layout.legacy="{{sourceTypes}}{{mediaSources}}{{scenes}}{{checkTypes}}l'état est{{states}}et{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Vidéo"
AdvSceneSwitcher.condition.video.condition.match="correspond exactement à"
AdvSceneSwitcher.condition.video.condition.differ="ne correspond pas à"
@@ -382,8 +381,7 @@ AdvSceneSwitcher.condition.date.entry.nextMatchDate="Prochaine correspondance à
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}}À la répétition, mettre à jour la date sélectionnée pour la date de répétition"
AdvSceneSwitcher.condition.sceneTransform="Transformation de l'élément de la scène"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Obtenir la transformation"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="Sur{{scenes}}{{sources}}{{types}}"
AdvSceneSwitcher.condition.sceneTransform.entry.type.matches="correspond à la transformation"
AdvSceneSwitcher.condition.sceneTransform.condition.match="correspond à la transformation"
AdvSceneSwitcher.condition.transition="Transition"
AdvSceneSwitcher.condition.transition.type.current="Le type de transition actuel est"
AdvSceneSwitcher.condition.transition.type.duration="La durée de la transition actuelle est"
@@ -455,7 +453,7 @@ AdvSceneSwitcher.condition.slideshow="Diaporama"
AdvSceneSwitcher.condition.slideshow.condition.slideChanged="Diapositive changée"
AdvSceneSwitcher.condition.slideshow.condition.slideIndex="Le numéro de la diapositive actuelle est"
AdvSceneSwitcher.condition.slideshow.condition.slidePath="Le chemin de la diapositive actuelle est"
AdvSceneSwitcher.condition.slideshow.updateIntervalTooltip="Les informations sur l'état du diaporama ne seront mises à jour qu'en fonction du temps configuré entre les diapositives"
AdvSceneSwitcher.condition.slideshow.updateInterval.tooltip="Les informations sur l'état du diaporama ne seront mises à jour qu'en fonction du temps configuré entre les diapositives"
; Macro Actions
AdvSceneSwitcher.action.scene="Changer de scène"
@@ -598,7 +596,7 @@ AdvSceneSwitcher.action.sceneOrder.type.moveDown="Déplacer vers le bas"
AdvSceneSwitcher.action.sceneOrder.type.moveTop="Déplacer en haut"
AdvSceneSwitcher.action.sceneOrder.type.moveBottom="Déplacer en bas"
AdvSceneSwitcher.action.sceneOrder.type.movePosition="Déplacer à la position"
AdvSceneSwitcher.action.sceneOrder.entry="Sur{{scenes}}{{actions}}{{sources}}{{position}}"
AdvSceneSwitcher.action.sceneOrder.entry="Sur{{scenes}}{{actions}}{{sources}}{{sources2}}{{position}}"
AdvSceneSwitcher.action.sceneTransform="Transformation de l'élément de la scène"
AdvSceneSwitcher.action.sceneTransform.type.manual="Transform"
AdvSceneSwitcher.action.sceneTransform.type.reset="Réinitialiser la transformation"
@@ -611,7 +609,7 @@ AdvSceneSwitcher.action.sceneTransform.type.centerToScreen="Centrer à l'écran"
AdvSceneSwitcher.action.sceneTransform.type.centerVertically="Centrer verticalement"
AdvSceneSwitcher.action.sceneTransform.type.centerHorizontally="Centrer horizontalement"
AdvSceneSwitcher.action.sceneTransform.getTransform="Obtenir la transformation"
AdvSceneSwitcher.action.sceneTransform.entry="Sur{{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.sceneTransform.entry="Sur{{scenes}}{{action}}{{rotation}}{{sources}}{{settingSelection}}{{singleSettingValue}}"
AdvSceneSwitcher.action.file="Fichier"
AdvSceneSwitcher.action.file.type.write="Écrire"
AdvSceneSwitcher.action.file.type.append="Ajouter"
@@ -648,9 +646,8 @@ AdvSceneSwitcher.action.screenshot.save.default="Par défaut"
AdvSceneSwitcher.action.screenshot.save.custom="Personnalisé"
AdvSceneSwitcher.action.screenshot.type.source="Source"
AdvSceneSwitcher.action.screenshot.type.scene="Scène"
AdvSceneSwitcher.action.screenshot.mainOutput="Sortie principale d'OBS"
AdvSceneSwitcher.action.screenshot.blackscreenNote="Les sources ou les scènes qui ne sont pas toujours rendues peuvent entraîner des parties de captures d'écran vides."
AdvSceneSwitcher.action.screenshot.entry="Capturer{{targetType}}{{sources}}{{scenes}}et enregistrer à l'emplacement{{saveType}}"
AdvSceneSwitcher.action.screenshot.entry="Capturer{{targetType}}{{sources}}{{scenes}}et enregistrer à l'emplacement{{saveType}}{{variables}}"
AdvSceneSwitcher.action.profile="Profil"
AdvSceneSwitcher.action.profile.entry="Changer le profil actif vers{{profiles}}"
AdvSceneSwitcher.action.sceneCollection="Collection de scènes"
@@ -706,10 +703,10 @@ AdvSceneSwitcher.action.variable.invalidSelection="Sélection invalide !"
AdvSceneSwitcher.action.variable.actionNoVariableSupport="La récupération de valeurs de variables à partir d'actions %1 n'est pas prise en charge !"
AdvSceneSwitcher.action.variable.conditionNoVariableSupport="La récupération de valeurs de variables à partir de conditions %1 n'est pas prise en charge !"
AdvSceneSwitcher.action.variable.currentSegmentValue="Valeur actuelle :"
AdvSceneSwitcher.action.variable.entry.substringIndex="Début de la sous-chaîne :{{subStringStart}}Taille de la sous-chaîne :{{subStringSize}}"
AdvSceneSwitcher.action.variable.entry.substringRegex="Attribuer la valeur du match{{regexMatchIdx}}en utilisant une expression régulière :"
AdvSceneSwitcher.action.variable.entry.userInput.customPrompt="{{useCustomPrompt}}Utiliser un message personnalisé{{inputPrompt}}"
AdvSceneSwitcher.action.variable.entry.userInput.placeholder="{{useInputPlaceholder}}Remplir avec un indicateur de position{{inputPlaceholder}}"
AdvSceneSwitcher.action.variable.layout.substringIndex="Début de la sous-chaîne :{{subStringStart}}Taille de la sous-chaîne :{{subStringSize}}"
AdvSceneSwitcher.action.variable.layout.substringRegex="Attribuer la valeur du match{{regexMatchIdx}}en utilisant une expression régulière :"
AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}Utiliser un message personnalisé{{inputPrompt}}"
AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}Remplir avec un indicateur de position{{inputPlaceholder}}"
AdvSceneSwitcher.action.projector="Projecteur"
AdvSceneSwitcher.action.projector.type.source="Source"
AdvSceneSwitcher.action.projector.type.scene="Scène"
@@ -719,8 +716,6 @@ AdvSceneSwitcher.action.projector.type.multiview="Multivue"
AdvSceneSwitcher.action.projector.display="Affichage"
AdvSceneSwitcher.action.projector.windowed="Fenêtré"
AdvSceneSwitcher.action.projector.fullscreen="Plein écran"
AdvSceneSwitcher.action.projector.entry="Ouvrir le projecteur{{windowTypes}}de{{types}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.projector.entry.monitor="sur{{monitors}}"
AdvSceneSwitcher.action.midi="MIDI"
AdvSceneSwitcher.action.midi.entry="Envoyer un message à{{device}}:"
AdvSceneSwitcher.action.midi.entry.listen="Définir la sélection de messages MIDI sur les messages entrants de{{listenDevices}}:{{listenButton}}"
@@ -898,31 +893,6 @@ AdvSceneSwitcher.videoTab.ignoreInactiveSource="sauf si la source est inactive"
AdvSceneSwitcher.videoTab.entry="Lorsque{{videoSources}}{{condition}}{{filePath}}{{browseButton}}pendant{{duration}}, passez à{{scenes}}en utilisant{{transitions}}{{ignoreInactiveSource}}"
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>Cet onglet vous permettra de basculer entre les scènes en fonction de la sortie vidéo actuelle des sources sélectionnées.<br/>Assurez-vous de consulter <a href=\"https://obsproject.com/forum/resources/pixel-match-switcher.1202/\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a> pour une implémentation encore meilleure de cette fonctionnalité.<br/><br/> Cliquez sur le symbole plus en surbrillance pour continuer.</p></body></html>"
; Network Tab
AdvSceneSwitcher.networkTab.title="Réseau"
AdvSceneSwitcher.networkTab.description="Cet onglet vous permettra de contrôler à distance la scène active d'une autre instance OBS.\nVeuillez noter que les noms de scènes doivent correspondre exactement sur toutes les instances OBS."
AdvSceneSwitcher.networkTab.warning="Exécuter le serveur en dehors d'un réseau local permettra à des tiers de lire la scène active."
AdvSceneSwitcher.networkTab.server="Démarrer le serveur (Envoie des messages de changement de scène à tous les clients connectés)"
AdvSceneSwitcher.networkTab.server.port="Port"
AdvSceneSwitcher.networkTab.server.lockToIPv4="Verrouiller le serveur pour n'utiliser que IPv4"
AdvSceneSwitcher.networkTab.server.sendSceneChange="Envoyer des messages pour les changements de scène"
AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Envoyer uniquement des messages pour les changements de scène automatisés"
AdvSceneSwitcher.networkTab.server.sendPreview="Envoyer des messages pour le changement de prévisualisation de scène lors de l'exécution en mode Studio"
AdvSceneSwitcher.networkTab.startFailed.message="Le serveur WebSocket n'a pas pu démarrer, peut-être parce que :\n - Le port TCP %1 est peut-être déjà utilisé ailleurs sur ce système, éventuellement par une autre application. Essayez de définir un port TCP différent dans les paramètres du serveur WebSocket, ou arrêtez toute application qui pourrait utiliser ce port.\n - Message d'erreur : %2"
AdvSceneSwitcher.networkTab.server.status.currentStatus="État actuel"
AdvSceneSwitcher.networkTab.server.status.notRunning="Non démarré"
AdvSceneSwitcher.networkTab.server.status.starting="Démarrage"
AdvSceneSwitcher.networkTab.server.status.running="En cours d'exécution"
AdvSceneSwitcher.networkTab.server.restart="Redémarrer le serveur"
AdvSceneSwitcher.networkTab.client="Démarrer le client (Reçoit des messages de changement de scène)"
AdvSceneSwitcher.networkTab.client.address="Nom d'hôte ou adresse IP"
AdvSceneSwitcher.networkTab.client.port="Port"
AdvSceneSwitcher.networkTab.client.status.currentStatus="État actuel"
AdvSceneSwitcher.networkTab.client.status.disconnected="Déconnecté"
AdvSceneSwitcher.networkTab.client.status.connecting="Connexion"
AdvSceneSwitcher.networkTab.client.status.connected="Connecté"
AdvSceneSwitcher.networkTab.client.reconnect="Forcer la reconnexion"
; Scene Group Tab
AdvSceneSwitcher.sceneGroupTab.title="Groupe de Scènes"
AdvSceneSwitcher.sceneGroupTab.list="Groupes de Scènes"
@@ -943,30 +913,6 @@ AdvSceneSwitcher.sceneGroupTab.exists="Le nom du Groupe de Scènes ou de la Scè
AdvSceneSwitcher.sceneGroupTab.help="Les Groupes de Scènes peuvent être sélectionnés comme une cible, tout comme une scène régulière.\n\nComme son nom l'indique, un Groupe de Scènes est une collection de plusieurs scènes.\nLe Groupe de Scènes avancera dans la liste de ses scènes attribuées en fonction des paramètres configurés, que vous pouvez trouver à droite.\n\nVous pouvez configurer le Groupe de Scènes pour passer à la scène suivante dans la liste :\nAprès un certain nombre de fois où le Groupe de Scènes est sélectionné comme cible.\nAprès qu'un certain laps de temps se soit écoulé.\nOu au hasard.\n\nPar exemple, un Groupe de Scènes contenant les scènes...\nScène 1\nScène 2\nScène 3\n... activera "Scène 1" la première fois qu'il est sélectionné comme cible.\nLa deuxième fois, il activera "Scène 2".\nLes fois suivantes, "Scène 3" sera activée.\n\nCliquez sur le symbole plus en surbrillance ci-dessous pour ajouter un nouveau Groupe de Scènes."
AdvSceneSwitcher.sceneGroupTab.scenes.help="Sélectionnez le groupe de scènes que vous souhaitez modifier à gauche.\n\nSélectionnez une scène à ajouter à ce groupe de scènes en sélectionnant la scène ci-dessus et en cliquant sur le symbole plus ci-dessous.\n\nUne scène peut être ajoutée plusieurs fois au même groupe de scènes."
; Scene Trigger Tab
AdvSceneSwitcher.sceneTriggerTab.title="Déclencheurs de Scène"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none="--sélectionnez un déclencheur--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive="est active"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneInactive="n'est pas active"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneLeave="passé à autre chose"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none="--sélectionnez une action--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording="démarrer l'enregistrement"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.pauseRecording="mettre en pause l'enregistrement"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unpauseRecording="reprendre l'enregistrement"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopRecording="arrêter l'enregistrement"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming="arrêter la diffusion"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="démarrer la diffusion"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="démarrer le tampon de répétition"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="arrêter le tampon de répétition"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="mettre la source en sourdine"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="annuler la mise en sourdine de la source"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="démarrer le commutateur de scènes avancé"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="arrêter le commutateur de scènes avancé"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startVirtualCamera="démarrer la caméra virtuelle"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopVirtualCamera="arrêter la caméra virtuelle"
AdvSceneSwitcher.sceneTriggerTab.entry="Lorsque{{scenes}}{{triggers}}{{actions}}{{audioSources}}après{{duration}}"
AdvSceneSwitcher.sceneTriggerTab.help="Cet onglet vous permet de déclencher des actions lors des changements de scènes, comme l'arrêt de l'enregistrement ou de la diffusion."
; Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Démarrer le commutateur de scènes avancé"
AdvSceneSwitcher.hotkey.stopSwitcherHotkey="Arrêter le commutateur de scènes avancé"
@@ -1162,8 +1108,8 @@ AdvSceneSwitcher.sceneItemSelection.type.sourceName="Nom de la source"
AdvSceneSwitcher.sceneItemSelection.type.sourceVariable="Nom de la variable"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern="Le nom de la source correspond au motif"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern.entry="{{nameConflictIndex}}correspondant à{{pattern}}{{regex}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceGroup="Sources de type"
AdvSceneSwitcher.sceneItemSelection.type.sourceGroup.entry="{{nameConflictIndex}}Type de source{{sourceGroups}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceType="Sources de type"
AdvSceneSwitcher.sceneItemSelection.type.sourceType.entry="{{nameConflictIndex}}Type de source{{sourceTypes}}"
AdvSceneSwitcher.sceneItemSelection.type.index="Source avec index"
AdvSceneSwitcher.sceneItemSelection.type.index.entry="{{index}}source"
AdvSceneSwitcher.sceneItemSelection.type.indexRange="Sources dans la plage d'index"
@@ -1183,7 +1129,7 @@ AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Il semble que ce soit l
AdvSceneSwitcher.deprecatedTabWarning="Le développement de cet onglet est terminé !\nVeuillez envisager de passer à l'utilisation des Macros à la place.\nCette astuce peut être désactivée dans l'onglet Général."
AdvSceneSwitcher.unit.milliseconds="millisecondes"
AdvSceneSwitcher.unit.secends="secondes"
AdvSceneSwitcher.unit.seconds="secondes"
AdvSceneSwitcher.unit.minutes="minutes"
AdvSceneSwitcher.unit.hours="heures"
AdvSceneSwitcher.duration.condition.none="Aucun modificateur de durée"

2153
data/locale/ja-JP.ini Normal file

File diff suppressed because it is too large Load Diff

2082
data/locale/pt-BR.ini Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -19,13 +19,12 @@ AdvSceneSwitcher.generalTab.status.autoStart.streaming="Вещание"
AdvSceneSwitcher.generalTab.status.autoStart.recordingAndStreaming="Запись или потоковое вещание"
AdvSceneSwitcher.generalTab.status.checkInterval="Проверять условия переключения каждый раз"
AdvSceneSwitcher.generalTab.generalBehavior="Общее поведение"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet="Если не выполняется условие переключения для"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMetDelayTooltip="Будет только настолько точным, насколько настроен интервал проверки."
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch="Не переключаться"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom="Переключиться на любую сцену на вкладке Random"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="Переключиться на:"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch="Если не выполняется условие переключения для"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatchDelay.tooltip="Будет только настолько точным, насколько настроен интервал проверки."
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.dontSwitch="Не переключаться"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchToRandom="Переключиться на любую сцену на вкладке Random"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchTo="Переключиться на:"
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="В течение этого времени потенциальные совпадения будут игнорироваться!"
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Включить ведение подробного журнала"
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Сохранять положение и размер окна"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Отключить подсказки пользовательского интерфейса"
AdvSceneSwitcher.generalTab.priority="Приоритет"
@@ -137,7 +136,6 @@ AdvSceneSwitcher.action.replay="Буфер воспроизведения"
AdvSceneSwitcher.action.replay.type.stop="Остановить буфер воспроизведения"
AdvSceneSwitcher.action.replay.type.start="Начать воспроизведение буфера"
AdvSceneSwitcher.action.replay.type.save="Сохранить буфер воспроизведения"
AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.streaming="Потоковое вещание"
AdvSceneSwitcher.action.streaming.type.stop="Остановить потоковое вещание"
AdvSceneSwitcher.action.streaming.type.start="Начать потоковое вещание"
@@ -308,28 +306,6 @@ AdvSceneSwitcher.videoTab.ignoreInactiveSource="если источник неа
AdvSceneSwitcher.videoTab.entry="Когда {{videoSources}} {{condition}} {{filePath}} {{browseButton}} для {{duration}} переключиться на {{scenes}} используя {{transitions}} {{ignoreInactiveSource}}"
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>Эта вкладка позволит вам переключать сцены на основе текущего видеовыхода выбранных источников.<br/>Обязательно проверьте <a href=\"https://obsproject.com/forum/resources/pixel-match-switcher.1202/\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a> для еще лучшей реализации этой функциональности.<br/><br/> Нажмите на выделенный символ плюса, чтобы продолжить.</p></body></html>"
; Network Tab
AdvSceneSwitcher.networkTab.title="Сеть"
AdvSceneSwitcher.networkTab.warning="Запуск сервера вне локальной сети позволит третьим лицам читать активную сцену."
AdvSceneSwitcher.networkTab.server="Запустить сервер (отправляет сообщения о переключении сцены всем подключенным клиентам)"
AdvSceneSwitcher.networkTab.server.port="Порт"
AdvSceneSwitcher.networkTab.server.lockToIPv4="Заблокировать сервер на использование только IPv4"
AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Отправлять сообщения только для автоматических переключателей сцен"
AdvSceneSwitcher.networkTab.startFailed.message="Сервер WebSockets не удалось запустить, возможно потому, что:\n - TCP порт %1 может в настоящее время использоваться в другом месте в этой системе, возможно, другим приложением. Попробуйте установить другой TCP порт в настройках сервера WebSocket, или остановите любое приложение, которое может использовать этот порт.\n - Сообщение об ошибке: %2"
AdvSceneSwitcher.networkTab.server.status.currentStatus="Текущий статус"
AdvSceneSwitcher.networkTab.server.status.notRunning="Не запущен"
AdvSceneSwitcher.networkTab.server.status.starting="Запуск"
AdvSceneSwitcher.networkTab.server.status.running="Работает"
AdvSceneSwitcher.networkTab.server.restart="Перезапустить сервер"
AdvSceneSwitcher.networkTab.client="Запустить клиент (Получает сообщения о переключении сцен)"
AdvSceneSwitcher.networkTab.client.address="Имя хоста или IP-адрес"
AdvSceneSwitcher.networkTab.client.port="Порт"
AdvSceneSwitcher.networkTab.client.status.currentStatus="Текущий статус"
AdvSceneSwitcher.networkTab.client.status.disconnected="Отключено"
AdvSceneSwitcher.networkTab.client.status.connecting="Подключение"
AdvSceneSwitcher.networkTab.client.status.connected="Подключено"
AdvSceneSwitcher.networkTab.client.reconnect="Принудительное переподключение"
; Scene Group Tab
AdvSceneSwitcher.sceneGroupTab.title="Группа сцен"
AdvSceneSwitcher.sceneGroupTab.list="Группы сцен"
@@ -350,28 +326,6 @@ AdvSceneSwitcher.sceneGroupTab.exists="Группа сцен или назван
AdvSceneSwitcher.sceneGroupTab.help="Группы сцен могут быть выбраны в качестве цели так же, как и обычные сцены.\n\nКак следует из названия, группа сцен представляет собой набор из нескольких сцен.\nГруппа сцен будет продвигаться по списку назначенных ей сцен в зависимости от настроенных параметров, которые можно найти справа.\n\nВы можете настроить группу сцен на переход к следующей сцене в списке:\nПосле определенного количества раз, когда группа сцен выбрана в качестве цели.\nПо истечении определенного времени.\nИли случайным образом.\n\nНапример, группа сцен, содержащая сцены ...\nScene 1\nScene 2\nScene 3 \n... активирует \"Scene 1\" в первый раз, когда он выбран в качестве цели.\nВо второй раз он активируется \"Scene 2\".\nОставшееся время \"Scene 3\" будет активирован.\n\nНажмите на выделенный символ плюса ниже, чтобы добавить новую группу сцен."
AdvSceneSwitcher.sceneGroupTab.scenes.help="Выберите группу сцен, которую вы хотите изменить слева.\n\nВыберите сцену для добавления в эту группу сцен, выбрав сцену выше и нажав на символ плюса ниже.\n\nСцена может быть добавлена несколько раз в одну и ту же группу сцен."
; Scene Trigger Tab
AdvSceneSwitcher.sceneTriggerTab.title="Триггеры сцены"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none="--выбрать триггер--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive="активен"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneInactive="не активен"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneLeave="переключился с"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none="--выбрать действие--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording="начать запись"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.pauseRecording="приостановить запись"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unpauseRecording="отменить паузу записи"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopRecording="остановить запись"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming="остановить потоковое вещание"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="начать потоковое вещание"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="запустить буфер воспроизведения"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="остановить буфер воспроизведения"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="отключить источник"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="включить источник"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="запустить переключатель сцены"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="остановить переключатель сцены"
AdvSceneSwitcher.sceneTriggerTab.entry="Когда {{scenes}} {{triggers}} {{actions}} {{audioSources}} после {{duration}}"
AdvSceneSwitcher.sceneTriggerTab.help="Эта вкладка позволяет запускать действия при изменении сцены, например, остановку записи или потоковой передачи."
; Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Запустить Advanced Scene Switcher"
AdvSceneSwitcher.hotkey.stopSwitcherHotkey="становить Advanced Scene Switcher"
@@ -400,6 +354,6 @@ AdvSceneSwitcher.status.active="Активный"
AdvSceneSwitcher.status.inactive="Неактивен"
AdvSceneSwitcher.unit.milliseconds="миллисекунды"
AdvSceneSwitcher.unit.secends="секунды"
AdvSceneSwitcher.unit.seconds="секунды"
AdvSceneSwitcher.unit.minutes="минуты"
AdvSceneSwitcher.unit.hours="часы"

View File

@@ -19,14 +19,13 @@ AdvSceneSwitcher.generalTab.status.autoStart.streaming="Yayın"
AdvSceneSwitcher.generalTab.status.autoStart.recordingAndStreaming="Kayıt veya Yayın"
AdvSceneSwitcher.generalTab.status.checkInterval="Anahtar koşullarını her seferinde kontrol edin"
AdvSceneSwitcher.generalTab.generalBehavior="Genel davranış"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet="...için herhangi bir işlem yapılmazsa"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMetDelayTooltip="Yalnızca yapılandırılmış kontrol aralığı kadar doğru olacaktır."
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch="Geçiş Yapma"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom="Rastgele sekmesinde herhangi bir sahneye geçin"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="Değiştirmek:"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch="...için herhangi bir işlem yapılmazsa"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatchDelay.tooltip="Yalnızca yapılandırılmış kontrol aralığı kadar doğru olacaktır."
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.dontSwitch="Geçiş Yapma"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchToRandom="Rastgele sekmesinde herhangi bir sahneye geçin"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchTo="Değiştirmek:"
AdvSceneSwitcher.generalTab.generalBehavior.cooldown="Bir eşleşmeden sonra aşağıdakiler için işlem yapmayın:"
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="Bu süre zarfında olası eşleşmeler göz ardı edilecektir!"
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Ayrıntılı günlük kaydını etkinleştir"
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Pencere konumunu ve boyutunu kaydet"
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Sistem tepsisi bildirimlerini göster"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="UI ipuçlarını devre dışı bırak"
@@ -107,8 +106,6 @@ AdvSceneSwitcher.condition.scene.type.previous="Önceki Sahne"
AdvSceneSwitcher.condition.scene.type.changed="Sahne değişti"
AdvSceneSwitcher.condition.scene.type.notChanged="Sahne değişmedi"
AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour="Geçiş hedefi sahnesi için geçiş kontrolü sırasında"
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}"
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
AdvSceneSwitcher.condition.window="Pencere"
AdvSceneSwitcher.condition.file="Dosya"
AdvSceneSwitcher.condition.file.entry.line1="İçerik{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
@@ -117,7 +114,7 @@ AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFil
AdvSceneSwitcher.condition.media="Medya"
AdvSceneSwitcher.condition.media.anyOnScene="Herhangi bir medya kaynağı"
AdvSceneSwitcher.condition.media.allOnScene="Tüm medya kaynakları "
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}durumu{{states}}ve{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.media.layout.legacy="{{sourceTypes}}{{mediaSources}}{{scenes}}{{checkTypes}}durumu{{states}}ve{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Video"
AdvSceneSwitcher.condition.video.condition.match="Tam olarak eşleşir"
AdvSceneSwitcher.condition.video.condition.differ="Eşleştirme"
@@ -151,7 +148,6 @@ AdvSceneSwitcher.condition.stream.state.start="Yayın çalışıyor"
AdvSceneSwitcher.condition.stream.state.stop="Yayın durdu"
AdvSceneSwitcher.condition.stream.state.starting="Yayın başlıyor"
AdvSceneSwitcher.condition.stream.state.stopping="Yayın duruyor"
AdvSceneSwitcher.condition.stream.entry="{{streamState}}{{keyFrameInterval}}"
AdvSceneSwitcher.condition.record="Kayıt"
AdvSceneSwitcher.condition.record.state.start="Kayıt Çalışıyor"
AdvSceneSwitcher.condition.record.state.pause="Kayıt durakladı"
@@ -226,10 +222,7 @@ AdvSceneSwitcher.condition.date.state.before="Önce"
AdvSceneSwitcher.condition.date.state.between="Arasında"
AdvSceneSwitcher.condition.sceneTransform="Sahne öğesi dönüşümü"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Dönüşümü al"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="Açık{{scenes}}{{sources}}{{types}}"
AdvSceneSwitcher.condition.sceneTransform.entry.type.matches="dönüşümle eşleşir"
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}"
AdvSceneSwitcher.condition.sceneTransform.condition.match="dönüşümle eşleşir"
AdvSceneSwitcher.condition.transition="Geçiş"
AdvSceneSwitcher.condition.transition.type.current="Geçerli geçiş türü"
AdvSceneSwitcher.condition.transition.type.duration="Mevcut geçiş süresi"
@@ -279,7 +272,6 @@ AdvSceneSwitcher.action.replay="Tekrar arabelleği"
AdvSceneSwitcher.action.replay.type.stop="Tekrar arabelleğini durdur"
AdvSceneSwitcher.action.replay.type.start="Tekrar arabelleğini başlat"
AdvSceneSwitcher.action.replay.type.save="Tekrar arabelleğini kaydet"
AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.streaming="Yayın"
AdvSceneSwitcher.action.streaming.type.stop="Yayın durdur"
AdvSceneSwitcher.action.streaming.type.start="Yayın başlat"
@@ -342,10 +334,10 @@ AdvSceneSwitcher.action.sceneOrder.type.moveDown="Aşağı indir"
AdvSceneSwitcher.action.sceneOrder.type.moveTop="En üste taşı"
AdvSceneSwitcher.action.sceneOrder.type.moveBottom="Aşağıya taşı"
AdvSceneSwitcher.action.sceneOrder.type.movePosition="Konuma taşı"
AdvSceneSwitcher.action.sceneOrder.entry="Açık{{scenes}}{{actions}}{{sources}}{{position}}"
AdvSceneSwitcher.action.sceneOrder.entry="Açık{{scenes}}{{actions}}{{sources}}{{sources2}}{{position}}"
AdvSceneSwitcher.action.sceneTransform="Sahne öğesi dönüşümü"
AdvSceneSwitcher.action.sceneTransform.getTransform="Dönüşümü al"
AdvSceneSwitcher.action.sceneTransform.entry="Açık{{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.sceneTransform.entry="Açık{{scenes}}{{action}}{{rotation}}{{sources}}{{settingSelection}}{{singleSettingValue}}"
AdvSceneSwitcher.action.file="Dosya"
AdvSceneSwitcher.action.file.type.write="Yaz"
AdvSceneSwitcher.action.file.type.append="Ekle"
@@ -363,7 +355,6 @@ AdvSceneSwitcher.action.random="Rastgele"
AdvSceneSwitcher.action.random.entry="Aşağıdaki makrolardan herhangi birini rastgele çalıştırın (duraklatılmış makrolar yoksayılır)"
AdvSceneSwitcher.action.systray="Sistem tepsisi bildirimi"
AdvSceneSwitcher.action.screenshot="Ekran görüntüsü"
AdvSceneSwitcher.action.screenshot.mainOutput="OBS'nin ana çıkışı"
AdvSceneSwitcher.action.profile="Profil"
AdvSceneSwitcher.action.profile.entry="Aktif profili şununla değiştir: {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="Sahne koleksiyonu"
@@ -533,31 +524,6 @@ AdvSceneSwitcher.videoTab.ignoreInactiveSource="Kaynak etkin olmadığı sürece
AdvSceneSwitcher.videoTab.entry="Zaman {{videoSources}} {{condition}} {{filePath}} {{browseButton}} için {{duration}} şuna dönüştür {{scenes}} kullan {{transitions}} {{ignoreInactiveSource}}"
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>Bu sekme, seçilen kaynakların mevcut video çıkışına göre sahneler arasında geçiş yapmanızı sağlar.<br/><a href=\"https:// adresini kontrol ettiğinizden emin olun. obsproject.com/forum/resources/pixel-match-switcher.1202/\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a> bu işlevin daha da iyi bir şekilde uygulanması için.<br/><br/> Devam etmek için vurgulanan artı simgesini tıklayın.</p></body></html>"
; Network Tab
AdvSceneSwitcher.networkTab.title="Ağ"
AdvSceneSwitcher.networkTab.description="Bu sekme, başka bir OBS örneğinin etkin sahnesini uzaktan kontrol etmenizi sağlar.\nSahne adlarının tüm OBS örneklerinde tam olarak eşleşmesi gerektiğini lütfen unutmayın."
AdvSceneSwitcher.networkTab.warning="Sunucuyu yerel bir ağın dışında çalıştırmak, üçüncü tarafların aktif sahneyi okumasına izin verecektir."
AdvSceneSwitcher.networkTab.server="Sunucuyu başlat (Bağlı tüm istemcilere sahne değiştirme mesajları gönderir)"
AdvSceneSwitcher.networkTab.server.port="Port"
AdvSceneSwitcher.networkTab.server.lockToIPv4="Sunucuyu yalnızca IPv4 kullanacak şekilde kilitleyin"
AdvSceneSwitcher.networkTab.server.sendSceneChange="Sahne değişiklikleri için mesaj gönder"
AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Yalnızca otomatik sahne anahtarları için mesaj gönder"
AdvSceneSwitcher.networkTab.server.sendPreview="Stüdyo modunda çalışırken önizleme sahnesi değişikliği için mesajlar gönderin"
AdvSceneSwitcher.networkTab.startFailed.message="WebSockets sunucusu başlatılamadı, bunun nedeni şunlar olabilir:\n - %1 TCP bağlantı noktası şu anda bu sistemde başka bir yerde, muhtemelen başka bir uygulama tarafından kullanılıyor olabilir. WebSocket sunucu ayarlarında farklı bir TCP bağlantı noktası ayarlamayı deneyin veya bu bağlantı noktasını kullanabilecek herhangi bir uygulamayı durdurun.\n - Hata mesajı: %2"
AdvSceneSwitcher.networkTab.server.status.currentStatus="Şu anki durum"
AdvSceneSwitcher.networkTab.server.status.notRunning="Çalışmıyor"
AdvSceneSwitcher.networkTab.server.status.starting="Başlangıç"
AdvSceneSwitcher.networkTab.server.status.running="Çalışıyor"
AdvSceneSwitcher.networkTab.server.restart="Sunucuyu yeniden başlat"
AdvSceneSwitcher.networkTab.client="İstemciyi başlat (Sahne değiştirme mesajlarını alır)"
AdvSceneSwitcher.networkTab.client.address="Ana makine adı veya IP adresi"
AdvSceneSwitcher.networkTab.client.port="Port"
AdvSceneSwitcher.networkTab.client.status.currentStatus="Şu anki durum"
AdvSceneSwitcher.networkTab.client.status.disconnected="Bağlantı kesildi"
AdvSceneSwitcher.networkTab.client.status.connecting="Bağlanıyor"
AdvSceneSwitcher.networkTab.client.status.connected="Bağlandı"
AdvSceneSwitcher.networkTab.client.reconnect="Yeniden bağlanmaya zorla"
; Scene Group Tab
AdvSceneSwitcher.sceneGroupTab.title="Sahne Grubu"
AdvSceneSwitcher.sceneGroupTab.list="Sahne Grupları"
@@ -578,30 +544,6 @@ AdvSceneSwitcher.sceneGroupTab.exists="Sahne Grubu veya Sahne adı zaten var"
AdvSceneSwitcher.sceneGroupTab.help="Sahne Grupları, normal bir sahne gibi bir hedef olarak seçilebilir.\n\nAdından da anlaşılacağı gibi, bir sahne grubu birden fazla sahneden oluşan bir koleksiyondur.\nSahne grubu, yapılandırılmış ayarlara bağlı olarak kendisine atanan sahneler listesinde ilerleyecektir. sağ tarafta bulunabilir.\n\nSahne grubunu listedeki bir sonraki sahneye geçecek şekilde yapılandırabilirsiniz:\nSahne grubu birkaç kez hedef olarak seçildikten sonra.\nBelirli bir süre sonra geçti.\nVeya rastgele.\n\nÖrneğin, sahneleri içeren bir sahne grubu ...\nSahne 1\nSahne 2\nSahne 3 \n... ilk seçildiğinde \"Sahne 1\"i etkinleştirecek bir hedef olarak.\nİkinci kez \"Sahne 2\"yi etkinleştirecek.\nKalan zamanlar \"Scene 3\" etkinleştirilecek.\n\nYeni bir sahne grubu eklemek için aşağıdaki vurgulanan artı sembolüne tıklayın."
AdvSceneSwitcher.sceneGroupTab.scenes.help="Soldan değiştirmek istediğiniz sahne grubunu seçin.\n\nYukarıdaki sahneyi seçip aşağıdaki artı simgesini tıklayarak bu sahne grubuna eklemek için bir sahne seçin.\n\nAynı sahneye birden fazla sahne eklenebilir grup."
; Scene Trigger Tab
AdvSceneSwitcher.sceneTriggerTab.title="Sahne Tetikleyicileri"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none="--tetikleyiciyi seçin--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive="aktif"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneInactive="aktif değil"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneLeave="uzaklaştı"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none="--eylemi seç--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording="Kayda başla"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.pauseRecording="Kaydı duraklat"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unpauseRecording="kaydı duraklatma"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopRecording="Kaydetmeyi bırak"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming="Akışı durdur"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="Akışı başlat"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="Yeniden oynatma arabelleğini başlat"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="Tekrar arabelleğini durdur"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="Kaynağı sessize al"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="Kaynağı sesi aç"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="Sahne değiştiriciyi başlat"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="Sahne değiştiriciyi durdur"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startVirtualCamera="Sanal kamerayı başlat"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopVirtualCamera="Sanal kamerayı durdur"
AdvSceneSwitcher.sceneTriggerTab.entry="Şundan {{scenes}} {{triggers}} {{actions}} {{audioSources}} sonra {{duration}}"
AdvSceneSwitcher.sceneTriggerTab.help="Bu sekme, kaydı veya akışı durdurma gibi sahne değişikliklerinde eylemleri tetiklemenize olanak tanır."
; Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Advanced Scene Switcher'ı başlatın"
AdvSceneSwitcher.hotkey.stopSwitcherHotkey="Gelişmiş Sahne Değiştiriciyi Durdurun"
@@ -649,7 +591,7 @@ AdvSceneSwitcher.stopped="Eklenti durdu"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Gelişmiş Sahne Değiştirici ilk kez başlatılıyor gibi görünüyor.<br>Lütfen <a href=\"https://github.com/ adresine bir göz atın. WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> için kılavuzlar ve örnekler listesi.<br>Yapmayın. eklentinin <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:# sayfasında soru sormaktan çekinmeyin OBS forumlarında 268bd2;\">konu</span></a>!</p></body></html>"
AdvSceneSwitcher.unit.milliseconds="millisaniye"
AdvSceneSwitcher.unit.secends="saniye"
AdvSceneSwitcher.unit.seconds="saniye"
AdvSceneSwitcher.unit.minutes="dakika"
AdvSceneSwitcher.unit.hours="saat"
AdvSceneSwitcher.duration.condition.none="Zaman kısıtlaması yok"

File diff suppressed because it is too large Load Diff

1
deps/date vendored Submodule

Submodule deps/date added at 5bdb7e6f31

13156
deps/exprtk/exprtk.hpp vendored

File diff suppressed because it is too large Load Diff

1
deps/jsoncons vendored Submodule

Submodule deps/jsoncons added at 64b9da1e9f

2
deps/libremidi vendored

1
deps/libusb vendored Submodule

Submodule deps/libusb added at d52e355daa

View File

@@ -29,7 +29,8 @@ extern "C" {
#endif
typedef void *obs_websocket_vendor;
typedef void (*obs_websocket_request_callback_function)(obs_data_t *, obs_data_t *, void *);
typedef void (*obs_websocket_request_callback_function)(obs_data_t *,
obs_data_t *, void *);
struct obs_websocket_request_response {
unsigned int status_code;
@@ -44,7 +45,7 @@ struct obs_websocket_request_callback {
void *priv_data;
};
inline proc_handler_t *_ph;
static proc_handler_t *_ph;
/* ==================== INTERNAL API FUNCTIONS ==================== */
@@ -53,9 +54,10 @@ static inline proc_handler_t *obs_websocket_get_ph(void)
proc_handler_t *global_ph = obs_get_proc_handler();
assert(global_ph != NULL);
calldata_t cd = {0};
calldata_t cd = {0, 0, 0, 0};
if (!proc_handler_call(global_ph, "obs_websocket_api_get_ph", &cd))
blog(LOG_DEBUG, "Unable to fetch obs-websocket proc handler object. obs-websocket not installed?");
blog(LOG_DEBUG,
"Unable to fetch obs-websocket proc handler object. obs-websocket not installed?");
proc_handler_t *ret = (proc_handler_t *)calldata_ptr(&cd, "ph");
calldata_free(&cd);
@@ -69,7 +71,9 @@ static inline bool obs_websocket_ensure_ph(void)
return _ph != NULL;
}
static inline bool obs_websocket_vendor_run_simple_proc(obs_websocket_vendor vendor, const char *proc_name, calldata_t *cd)
static inline bool
obs_websocket_vendor_run_simple_proc(obs_websocket_vendor vendor,
const char *proc_name, calldata_t *cd)
{
if (!obs_websocket_ensure_ph())
return false;
@@ -91,12 +95,12 @@ static inline unsigned int obs_websocket_get_api_version(void)
if (!obs_websocket_ensure_ph())
return 0;
calldata_t cd = {0};
calldata_t cd = {0, 0, 0, 0};
if (!proc_handler_call(_ph, "get_api_version", &cd))
return 1; // API v1 does not include get_api_version
unsigned int ret = calldata_int(&cd, "version");
unsigned int ret = (unsigned int)calldata_int(&cd, "version");
calldata_free(&cd);
@@ -104,7 +108,12 @@ static inline unsigned int obs_websocket_get_api_version(void)
}
// Calls an obs-websocket request. Free response with `obs_websocket_request_response_free()`
static inline obs_websocket_request_response *obs_websocket_call_request(const char *request_type, obs_data_t *request_data = NULL)
static inline struct obs_websocket_request_response *
obs_websocket_call_request(const char *request_type, obs_data_t *request_data
#ifdef __cplusplus
= NULL
#endif
)
{
if (!obs_websocket_ensure_ph())
return NULL;
@@ -113,14 +122,16 @@ static inline obs_websocket_request_response *obs_websocket_call_request(const c
if (request_data)
request_data_string = obs_data_get_json(request_data);
calldata_t cd = {0};
calldata_t cd = {0, 0, 0, 0};
calldata_set_string(&cd, "request_type", request_type);
calldata_set_string(&cd, "request_data", request_data_string);
proc_handler_call(_ph, "call_request", &cd);
auto ret = (struct obs_websocket_request_response *)calldata_ptr(&cd, "response");
struct obs_websocket_request_response *ret =
(struct obs_websocket_request_response *)calldata_ptr(
&cd, "response");
calldata_free(&cd);
@@ -128,7 +139,8 @@ static inline obs_websocket_request_response *obs_websocket_call_request(const c
}
// Free a request response object returned by `obs_websocket_call_request()`
static inline void obs_websocket_request_response_free(struct obs_websocket_request_response *response)
static inline void obs_websocket_request_response_free(
struct obs_websocket_request_response *response)
{
if (!response)
return;
@@ -144,12 +156,13 @@ static inline void obs_websocket_request_response_free(struct obs_websocket_requ
// ALWAYS CALL ONLY VIA `obs_module_post_load()` CALLBACK!
// Registers a new "vendor" (Example: obs-ndi)
static inline obs_websocket_vendor obs_websocket_register_vendor(const char *vendor_name)
static inline obs_websocket_vendor
obs_websocket_register_vendor(const char *vendor_name)
{
if (!obs_websocket_ensure_ph())
return NULL;
calldata_t cd = {0};
calldata_t cd = {0, 0, 0, 0};
calldata_set_string(&cd, "name", vendor_name);
@@ -161,32 +174,37 @@ static inline obs_websocket_vendor obs_websocket_register_vendor(const char *ven
}
// Registers a new request for a vendor
static inline bool obs_websocket_vendor_register_request(obs_websocket_vendor vendor, const char *request_type,
obs_websocket_request_callback_function request_callback, void *priv_data)
static inline bool obs_websocket_vendor_register_request(
obs_websocket_vendor vendor, const char *request_type,
obs_websocket_request_callback_function request_callback,
void *priv_data)
{
calldata_t cd = {0};
calldata_t cd = {0, 0, 0, 0};
struct obs_websocket_request_callback cb = {};
cb.callback = request_callback;
cb.priv_data = priv_data;
struct obs_websocket_request_callback cb = {request_callback,
priv_data};
calldata_set_string(&cd, "type", request_type);
calldata_set_ptr(&cd, "callback", &cb);
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_register", &cd);
bool success = obs_websocket_vendor_run_simple_proc(
vendor, "vendor_request_register", &cd);
calldata_free(&cd);
return success;
}
// Unregisters an existing vendor request
static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor vendor, const char *request_type)
static inline bool
obs_websocket_vendor_unregister_request(obs_websocket_vendor vendor,
const char *request_type)
{
calldata_t cd = {0};
calldata_t cd = {0, 0, 0, 0};
calldata_set_string(&cd, "type", request_type);
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_unregister", &cd);
bool success = obs_websocket_vendor_run_simple_proc(
vendor, "vendor_request_unregister", &cd);
calldata_free(&cd);
return success;
@@ -194,14 +212,17 @@ static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor
// Does not affect event_data refcount.
// Emits an event under the vendor's name
static inline bool obs_websocket_vendor_emit_event(obs_websocket_vendor vendor, const char *event_name, obs_data_t *event_data)
static inline bool obs_websocket_vendor_emit_event(obs_websocket_vendor vendor,
const char *event_name,
obs_data_t *event_data)
{
calldata_t cd = {0};
calldata_t cd = {0, 0, 0, 0};
calldata_set_string(&cd, "type", event_name);
calldata_set_ptr(&cd, "data", (void *)event_data);
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_event_emit", &cd);
bool success = obs_websocket_vendor_run_simple_proc(
vendor, "vendor_event_emit", &cd);
calldata_free(&cd);
return success;

2
deps/opencv vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
#include "advanced-scene-switcher.hpp"
#include "backup.hpp"
#include "curl-helper.hpp"
#include "log-helper.hpp"
#include "macro-helpers.hpp"
#include "obs-module-helper.hpp"
@@ -14,15 +13,20 @@
#include "tab-helpers.hpp"
#include "utility.hpp"
#include "version.h"
#include "websocket-api.hpp"
#include <filesystem>
#include <obs-frontend-api.h>
#include <QAction>
#include <QDirIterator>
#include <QLibrary>
#include <QMainWindow>
#include <QTextStream>
#include <regex>
#ifdef _WIN32
#include <Windows.h>
#endif
namespace advss {
AdvSceneSwitcher *AdvSceneSwitcher::window = nullptr;
@@ -70,6 +74,9 @@ static void DisplayMissingDependencyWarning()
QString warning(obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage"));
DisplayMessage(warning.arg(failedLibsString));
// Only display the warning once per plugin load
switcher->loadFailureLibs.clear();
}
static void DisplayMissingDataDirWarning()
@@ -82,18 +89,32 @@ static void DisplayMissingDataDirWarning()
"Please check installation instructions!\n\n"
"Data most likely expected at:\n\n";
#ifdef _WIN32
msg += QString::fromStdString(
(std::filesystem::current_path().string()));
msg += QDir::currentPath();
msg += "/";
#endif
msg += obs_get_module_data_path(obs_current_module());
DisplayMessage(msg);
}
bool CanCreateDefaultAction();
bool CanCreateDefaultCondition();
static void DisplayCorruptedInstallWarning()
{
if (CanCreateDefaultAction() && CanCreateDefaultCondition()) {
return;
}
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.warnCorruptedInstallMessage"));
}
void AdvSceneSwitcher::LoadUI()
{
DisplayMissingDataDirWarning();
DisplayMissingDependencyWarning();
DisplayCorruptedInstallWarning();
SetupGeneralTab();
SetupTitleTab();
@@ -109,9 +130,7 @@ void AdvSceneSwitcher::LoadUI()
SetupTimeTab();
SetupAudioTab();
SetupVideoTab();
SetupNetworkTab();
SetupSceneGroupTab();
SetupTriggerTab();
SetupMacroTab();
SetupOtherTabs(ui->tabWidget);
@@ -238,8 +257,8 @@ void SwitcherData::Thread()
duration = std::chrono::milliseconds(interval) +
std::chrono::milliseconds(linger) - runTime;
if (duration.count() < 1) {
blog(LOG_INFO,
"detected busy loop - refusing to sleep less than 1ms");
vblog(LOG_INFO,
"detected busy loop - refusing to sleep less than 1ms");
duration = std::chrono::milliseconds(10);
}
}
@@ -292,7 +311,7 @@ void SwitcherData::Thread()
}
}
ResetForNextInterval();
RunIntervalResetSteps();
if (match) {
if (macroMatch) {
@@ -342,14 +361,6 @@ void SwitcherData::SetPreconditions()
InvalidateMacroTempVarValues();
}
void SwitcherData::ResetForNextInterval()
{
// Plugin reset functions
for (const auto &func : resetIntervalSteps) {
func();
}
}
bool SwitcherData::CheckForMatch(OBSWeakSource &scene,
OBSWeakSource &transition, int &linger,
bool &setPrevSceneAfterLinger,
@@ -430,7 +441,7 @@ void AutoStartActionQueues();
void SwitcherData::Start()
{
if (!(th && th->isRunning())) {
ResetForNextInterval();
RunIntervalResetSteps();
ResetMacros();
AutoStartActionQueues();
@@ -438,17 +449,7 @@ void SwitcherData::Start()
th = new SwitcherThread();
th->start((QThread::Priority)threadPriority);
// Will be overwritten quickly but might be useful
writeToStatusFile("Advanced Scene Switcher running");
}
if (networkConfig.ServerEnabled) {
server.start(networkConfig.ServerPort,
networkConfig.LockToIPv4);
}
if (networkConfig.ClientEnabled) {
client.connect(networkConfig.GetClientUri());
RunStartSteps();
}
if (showSystemTrayNotifications) {
@@ -475,12 +476,9 @@ void SwitcherData::Stop()
th->wait();
delete th;
th = nullptr;
writeToStatusFile("Advanced Scene Switcher stopped");
RunStopSteps();
}
server.stop();
client.disconnect();
if (showSystemTrayNotifications) {
DisplayTrayMessage(
obs_module_text("AdvSceneSwitcher.pluginName"),
@@ -548,12 +546,7 @@ static void handleSceneChange()
GetWeakSourceName(switcher->previousScene).c_str());
}
switcher->checkTriggers();
switcher->checkDefaultSceneTransitions();
if (switcher->networkConfig.ShouldSendFrontendSceneChange()) {
switcher->server.sendMessage({ws.Get(), nullptr, 0});
}
}
static void setLiveTime()
@@ -582,17 +575,6 @@ static void checkAutoStartStreaming()
switcher->Start();
}
static void handlePeviewSceneChange()
{
if (switcher->networkConfig.ShouldSendPrviewSceneChange()) {
OBSSourceAutoRelease source =
obs_frontend_get_current_preview_scene();
OBSWeakSourceAutoRelease weak =
obs_source_get_weak_source(source);
switcher->server.sendMessage({weak.Get(), nullptr, 0}, true);
}
}
static void handleTransitionEnd()
{
GetMacroTransitionCV().notify_all();
@@ -670,9 +652,6 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
handleSceneChange();
break;
case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED:
handlePeviewSceneChange();
break;
case OBS_FRONTEND_EVENT_RECORDING_STARTED:
setLiveTime();
checkAutoStartRecording();
@@ -705,7 +684,7 @@ static void LoadPlugins()
{
QFileInfo libPath(
QString(obs_get_module_binary_path(obs_current_module())));
QString pluginDir(libPath.absolutePath() + "/adv-ss-plugins");
QString pluginDir(libPath.absolutePath() + "/" ADVSS_PLUGIN_FOLDER);
#ifdef _WIN32
QString libPattern = "*.dll";
SetDllDirectory(pluginDir.toStdWString().c_str());
@@ -745,12 +724,7 @@ void OpenSettingsWindow()
}
}
QWidget *GetSettingsWindow()
{
return SettingsWindowIsOpened() ? AdvSceneSwitcher::window : nullptr;
}
void AdvSceneSwitcher::HighligthMacroSettingsButton(bool enable)
void AdvSceneSwitcher::HighlightMacroSettingsButton(bool enable)
{
static QObject *highlight = nullptr;
if ((highlight && enable) || (!highlight && !enable)) {
@@ -770,13 +744,13 @@ void AdvSceneSwitcher::HighligthMacroSettingsButton(bool enable)
highlight = HighlightWidget(ui->macroSettings, Qt::green);
}
void HighligthMacroSettingsButton(bool enable)
void HighlightMacroSettingsButton(bool enable)
{
auto window = GetSettingsWindow();
if (!window) {
return;
}
static_cast<AdvSceneSwitcher *>(window)->HighligthMacroSettingsButton(
static_cast<AdvSceneSwitcher *>(window)->HighlightMacroSettingsButton(
enable);
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include "macro-segment-list.hpp"
#include "condition-logic.hpp"
#include "log-helper.hpp"
#include <ui_advanced-scene-switcher.h>
@@ -12,7 +13,6 @@ class MacroActionEdit;
class MacroConditionEdit;
class Duration;
class SequenceWidget;
enum class LogicType;
struct SceneGroup;
/*******************************************************************************
@@ -53,6 +53,7 @@ public slots:
void on_noMatchRandomSwitch_clicked();
void NoMatchDelayDurationChanged(const Duration &);
void CooldownDurationChanged(const Duration &);
void on_enableCooldown_stateChanged(int state);
void on_startupBehavior_currentIndexChanged(int index);
void on_logLevel_currentIndexChanged(int index);
void on_autoStartEvent_currentIndexChanged(int index);
@@ -81,31 +82,33 @@ public:
void RemoveMacro(std::shared_ptr<Macro> &);
void RemoveSelectedMacros();
void RenameMacro(std::shared_ptr<Macro> &, const QString &name);
std::shared_ptr<Macro> GetSelectedMacro();
std::vector<std::shared_ptr<Macro>> GetSelectedMacros();
std::shared_ptr<Macro> GetSelectedMacro() const;
std::vector<std::shared_ptr<Macro>> GetSelectedMacros() const;
void SetEditMacro(Macro &m);
void SetMacroEditAreaDisabled(bool);
void HighlightAction(int idx, QColor color = QColor(Qt::green));
void HighlightElseAction(int idx, QColor color = QColor(Qt::green));
void HighlightCondition(int idx, QColor color = QColor(Qt::green));
void SetMacroEditAreaDisabled(bool) const;
void HighlightAction(int idx, QColor color = QColor(Qt::green)) const;
void HighlightElseAction(int idx,
QColor color = QColor(Qt::green)) const;
void HighlightCondition(int idx,
QColor color = QColor(Qt::green)) const;
void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0);
void PopulateMacroElseActions(Macro &m, uint32_t afterIdx = 0);
void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0);
void SetActionData(Macro &m);
void SetElseActionData(Macro &m);
void SetConditionData(Macro &m);
void SetActionData(Macro &m) const;
void SetElseActionData(Macro &m) const;
void SetConditionData(Macro &m) const;
void SwapActions(Macro *m, int pos1, int pos2);
void SwapConditions(Macro *m, int pos1, int pos2);
void HighligthMacroSettingsButton(bool enable = true);
void HighlightMacroSettingsButton(bool enable = true);
public slots:
void on_macroAdd_clicked();
void on_macroRemove_clicked();
void on_macroUp_clicked();
void on_macroDown_clicked();
void on_macroUp_clicked() const;
void on_macroDown_clicked() const;
void on_macroName_editingFinished();
void on_runMacroInParallel_stateChanged(int value);
void on_runMacroOnChange_stateChanged(int value);
void on_runMacroInParallel_stateChanged(int value) const;
void on_runMacroOnChange_stateChanged(int value) const;
void on_conditionAdd_clicked();
void on_conditionRemove_clicked();
void on_conditionTop_clicked();
@@ -118,14 +121,14 @@ public slots:
void on_actionUp_clicked();
void on_actionDown_clicked();
void on_actionBottom_clicked();
void on_toggleElseActions_clicked();
void on_toggleElseActions_clicked() const;
void on_elseActionAdd_clicked();
void on_elseActionRemove_clicked();
void on_elseActionTop_clicked();
void on_elseActionUp_clicked();
void on_elseActionDown_clicked();
void on_elseActionBottom_clicked();
void MacroSelectionAboutToChange();
void MacroSelectionAboutToChange() const;
void MacroSelectionChanged();
void UpMacroSegementHotkey();
void DownMacroSegementHotkey();
@@ -136,22 +139,22 @@ public slots:
void ShowMacroConditionsContextMenu(const QPoint &);
void CopyMacro();
void RenameSelectedMacro();
void ExportMacros();
void ExportMacros() const;
void ImportMacros();
void ExpandAllActions();
void ExpandAllElseActions();
void ExpandAllConditions();
void CollapseAllActions();
void CollapseAllElseActions();
void CollapseAllConditions();
void MinimizeActions();
void MaximizeActions();
void MinimizeElseActions();
void MaximizeElseActions();
void MinimizeConditions();
void MaximizeConditions();
void SetElseActionsStateToHidden();
void SetElseActionsStateToVisible();
void ExpandAllActions() const;
void ExpandAllElseActions() const;
void ExpandAllConditions() const;
void CollapseAllActions() const;
void CollapseAllElseActions() const;
void CollapseAllConditions() const;
void MinimizeActions() const;
void MaximizeActions() const;
void MinimizeElseActions() const;
void MaximizeElseActions() const;
void MinimizeConditions() const;
void MaximizeConditions() const;
void SetElseActionsStateToHidden() const;
void SetElseActionsStateToVisible() const;
void MacroActionSelectionChanged(int idx);
void MacroActionReorder(int to, int target);
void AddMacroAction(Macro *macro, int idx, const std::string &id,
@@ -173,12 +176,12 @@ public slots:
void MacroConditionReorder(int to, int target);
void AddMacroCondition(int idx);
void AddMacroCondition(Macro *macro, int idx, const std::string &id,
obs_data_t *data, LogicType logic);
obs_data_t *data, Logic::Type logic);
void RemoveMacroCondition(int idx);
void MoveMacroConditionUp(int idx);
void MoveMacroConditionDown(int idx);
void HighlightControls();
void HighlightOnChange();
void HighlightControls() const;
void HighlightOnChange() const;
void on_macroSettings_clicked();
void CopyMacroSegment();
void PasteMacroSegment();
@@ -358,24 +361,6 @@ public slots:
void on_videoDown_clicked();
void on_getScreenshot_clicked();
// Scene group tab
public:
void SetupNetworkTab();
public slots:
void on_serverSettings_toggled(bool on);
void on_serverPort_valueChanged(int value);
void on_lockToIPv4_stateChanged(int state);
void on_serverRestart_clicked();
void UpdateServerStatus();
void on_clientSettings_toggled(bool on);
void on_clientHostname_textChanged(const QString &text);
void on_clientPort_valueChanged(int value);
void on_sendSceneChange_stateChanged(int state);
void on_restrictSend_stateChanged(int state);
void on_sendPreview_stateChanged(int state);
void on_clientReconnect_clicked();
void UpdateClientStatus();
// Scene group tab
public:
void SetupSceneGroupTab();
@@ -397,20 +382,12 @@ signals:
void SceneGroupRemoved(const QString &name);
void SceneGroupRenamed(const QString &oldName, const QString newName);
// Trigger tab
public:
void SetupTriggerTab();
public slots:
void on_triggerAdd_clicked();
void on_triggerRemove_clicked();
void on_triggerUp_clicked();
void on_triggerDown_clicked();
/* --- End of legacy tab section --- */
private:
void SetCheckIntervalTooLowVisibility() const;
};
void OpenSettingsWindow();
QWidget *GetSettingsWindow();
void HighligthMacroSettingsButton(bool enable);
void HighlightMacroSettingsButton(bool enable);
} // namespace advss

View File

@@ -91,6 +91,17 @@ void AdvSceneSwitcher::CooldownDurationChanged(const Duration &dur)
switcher->cooldown = dur;
}
void AdvSceneSwitcher::on_enableCooldown_stateChanged(int state)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->enableCooldown = state;
ui->cooldownTime->setEnabled(state);
}
void AdvSceneSwitcher::on_startupBehavior_currentIndexChanged(int index)
{
if (loading) {
@@ -102,12 +113,12 @@ void AdvSceneSwitcher::on_startupBehavior_currentIndexChanged(int index)
static_cast<SwitcherData::StartupBehavior>(index);
}
void AdvSceneSwitcher::on_logLevel_currentIndexChanged(int value)
void AdvSceneSwitcher::on_logLevel_currentIndexChanged(int idx)
{
if (loading) {
return;
}
switcher->logLevel = static_cast<SwitcherData::LogLevel>(value);
SetLogLevel(static_cast<LogLevel>(ui->logLevel->itemData(idx).toInt()));
}
void AdvSceneSwitcher::on_autoStartEvent_currentIndexChanged(int index)
@@ -139,6 +150,8 @@ void AdvSceneSwitcher::on_checkInterval_valueChanged(int value)
std::lock_guard<std::mutex> lock(switcher->m);
switcher->interval = value;
SetCheckIntervalTooLowVisibility();
}
void AdvSceneSwitcher::closeEvent(QCloseEvent *)
@@ -205,7 +218,6 @@ static bool isLegacyTab(const QString &name)
{
return name == obs_module_text(
"AdvSceneSwitcher.sceneGroupTab.title") ||
name == obs_module_text("AdvSceneSwitcher.networkTab.title") ||
name == obs_module_text(
"AdvSceneSwitcher.transitionTab.title") ||
name == obs_module_text(
@@ -223,9 +235,7 @@ static bool isLegacyTab(const QString &name)
"AdvSceneSwitcher.sceneSequenceTab.title") ||
name == obs_module_text("AdvSceneSwitcher.audioTab.title") ||
name == obs_module_text("AdvSceneSwitcher.videoTab.title") ||
name == obs_module_text("AdvSceneSwitcher.pauseTab.title") ||
name == obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.title");
name == obs_module_text("AdvSceneSwitcher.pauseTab.title");
}
void AdvSceneSwitcher::on_hideLegacyTabs_stateChanged(int state)
@@ -237,6 +247,10 @@ void AdvSceneSwitcher::on_hideLegacyTabs_stateChanged(int state)
ui->tabWidget->setTabVisible(idx, !state);
}
}
// Changing priority of legacy tabs will very likely not be necessary if
// the legacy tabs are hidden
ui->priorityBox->setVisible(!switcher->hideLegacyTabs);
}
void AdvSceneSwitcher::SetDeprecationWarnings()
@@ -330,6 +344,10 @@ void AdvSceneSwitcher::on_importSettings_clicked()
return;
}
// We have to make sure to that no macro is currently being edited while
// the new settings are loaded
ui->macros->clearSelection();
std::lock_guard<std::mutex> lock(switcher->m);
switcher->LoadSettings(obj);
switcher->lastImportPath = path.toStdString();
@@ -365,6 +383,7 @@ void AdvSceneSwitcher::CheckFirstTimeSetup()
switcher->firstBoot = false;
DisplayMessage(
obs_module_text("AdvSceneSwitcher.firstBootMessage"));
switcher->Start();
}
}
@@ -445,8 +464,6 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
loadTimeSwitches(obj);
loadAudioSwitches(obj);
loadVideoSwitches(obj);
loadNetworkSettings(obj);
loadSceneTriggers(obj);
LoadGeneralSettings(obj);
LoadHotkeys(obj);
LoadUISettings(obj);
@@ -481,8 +498,6 @@ void SwitcherData::SaveSettings(obs_data_t *obj)
saveTimeSwitches(obj);
saveAudioSwitches(obj);
saveVideoSwitches(obj);
saveNetworkSwitches(obj);
saveSceneTriggers(obj);
SaveGeneralSettings(obj);
SaveHotkeys(obj);
SaveUISettings(obj);
@@ -505,6 +520,7 @@ void SwitcherData::SaveGeneralSettings(obs_data_t *obj)
noMatchDelay.Save(obj, "noMatchDelay");
cooldown.Save(obj, "cooldown");
obs_data_set_bool(obj, "enableCooldown", enableCooldown);
obs_data_set_bool(obj, "active", sceneColletionStop ? true : !stop);
sceneColletionStop = false;
@@ -514,7 +530,8 @@ void SwitcherData::SaveGeneralSettings(obs_data_t *obj)
obs_data_set_int(obj, "autoStartEvent",
static_cast<int>(autoStartEvent));
obs_data_set_int(obj, "logLevel", static_cast<int>(logLevel));
SaveLogLevel(obj);
obs_data_set_bool(obj, "showSystemTrayNotifications",
showSystemTrayNotifications);
obs_data_set_bool(obj, "disableHints", disableHints);
@@ -550,6 +567,11 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
noMatchDelay.Load(obj, "noMatchDelay");
cooldown.Load(obj, "cooldown");
if (!obs_data_has_user_value(obj, "enableCooldown")) {
enableCooldown = cooldown.Seconds() != 0;
} else {
enableCooldown = obs_data_get_bool(obj, "enableCooldown");
}
obs_data_set_default_bool(obj, "active", true);
stop = !obs_data_get_bool(obj, "active");
@@ -565,7 +587,8 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
autoStartEvent =
static_cast<AutoStart>(obs_data_get_int(obj, "autoStartEvent"));
logLevel = static_cast<LogLevel>(obs_data_get_int(obj, "logLevel"));
LoadLogLevel(obj);
showSystemTrayNotifications =
obs_data_get_bool(obj, "showSystemTrayNotifications");
disableHints = obs_data_get_bool(obj, "disableHints");
@@ -653,7 +676,7 @@ void SwitcherData::CheckNoMatchSwitch(bool &match, OBSWeakSource &scene,
void SwitcherData::checkSwitchCooldown(bool &match)
{
if (!match) {
if (!match || !enableCooldown) {
return;
}
@@ -793,6 +816,28 @@ static void setupGeneralTabInactiveWarning(QTabWidget *tabs)
inactiveTimer->start();
}
void advss::AdvSceneSwitcher::SetCheckIntervalTooLowVisibility() const
{
auto macro = GetMacroWithInvalidConditionInterval();
if (!macro) {
ui->checkIntervalTooLowWarning->hide();
return;
}
const QString labelTextFormat(obs_module_text(
"AdvSceneSwitcher.generalTab.status.checkIntervalTooLow"));
const QString labelTooltipFormat(obs_module_text(
"AdvSceneSwitcher.generalTab.status.checkIntervalTooLow.tooltip"));
const QString name = QString::fromStdString(macro->Name());
const QString duration = QString::fromStdString(
macro->GetCustomConditionCheckInterval().ToString());
ui->checkIntervalTooLowWarning->setText(labelTextFormat.arg(name));
ui->checkIntervalTooLowWarning->setToolTip(
labelTooltipFormat.arg(name).arg(duration).arg(name));
ui->checkIntervalTooLowWarning->show();
}
void AdvSceneSwitcher::SetupGeneralTab()
{
PopulateSceneSelection(ui->noMatchSwitchScene, false);
@@ -814,14 +859,17 @@ void AdvSceneSwitcher::SetupGeneralTab()
DurationSelection *noMatchDelay = new DurationSelection();
noMatchDelay->SetDuration(switcher->noMatchDelay);
noMatchDelay->setToolTip(obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMetDelayTooltip"));
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMatchDelay.tooltip"));
ui->noMatchLayout->addWidget(noMatchDelay);
QWidget::connect(noMatchDelay,
SIGNAL(DurationChanged(const Duration &)), this,
SLOT(NoMatchDelayDurationChanged(const Duration &)));
ui->checkInterval->setValue(switcher->interval);
SetCheckIntervalTooLowVisibility();
ui->enableCooldown->setChecked(switcher->enableCooldown);
ui->cooldownTime->setEnabled(switcher->enableCooldown);
ui->cooldownTime->SetDuration(switcher->cooldown);
ui->cooldownTime->setToolTip(obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint"));
@@ -829,7 +877,9 @@ void AdvSceneSwitcher::SetupGeneralTab()
SIGNAL(DurationChanged(const Duration &)), this,
SLOT(CooldownDurationChanged(const Duration &)));
ui->logLevel->setCurrentIndex(static_cast<int>(switcher->logLevel));
PopulateLogLevelSelection(ui->logLevel);
ui->logLevel->setCurrentIndex(
ui->logLevel->findData(static_cast<int>(GetLogLevel())));
ui->saveWindowGeo->setChecked(switcher->saveWindowGeo);
ui->showTrayNotifications->setChecked(

View File

@@ -170,8 +170,10 @@ void AdvSceneSwitcher::on_sceneGroupAdd_clicked()
item->setData(Qt::UserRole, text);
ui->sceneGroups->setCurrentItem(item);
addPulse->deleteLater();
addPulse = nullptr;
if (addPulse) {
addPulse->deleteLater();
addPulse = nullptr;
}
ui->sceneGroupHelp->setVisible(false);
emit SceneGroupAdded(QString::fromStdString(name));

View File

@@ -1,581 +0,0 @@
#include "advanced-scene-switcher.hpp"
#include "layout-helpers.hpp"
#include "selection-helpers.hpp"
#include "source-helpers.hpp"
#include "switcher-data.hpp"
#include "ui-helpers.hpp"
#include "utility.hpp"
#include <obs-frontend-api.h>
#include <thread>
namespace advss {
bool SceneTrigger::pause = false;
static QObject *addPulse = nullptr;
void AdvSceneSwitcher::on_triggerAdd_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->sceneTriggers.emplace_back();
listAddClicked(ui->sceneTriggers,
new SceneTriggerWidget(this,
&switcher->sceneTriggers.back()),
ui->triggerAdd, addPulse);
ui->triggerHelp->setVisible(false);
}
void AdvSceneSwitcher::on_triggerRemove_clicked()
{
QListWidgetItem *item = ui->sceneTriggers->currentItem();
if (!item) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
int idx = ui->sceneTriggers->currentRow();
auto &switches = switcher->sceneTriggers;
switches.erase(switches.begin() + idx);
}
delete item;
}
void AdvSceneSwitcher::on_triggerUp_clicked()
{
int index = ui->sceneTriggers->currentRow();
if (!listMoveUp(ui->sceneTriggers)) {
return;
}
SceneTriggerWidget *s1 =
(SceneTriggerWidget *)ui->sceneTriggers->itemWidget(
ui->sceneTriggers->item(index));
SceneTriggerWidget *s2 =
(SceneTriggerWidget *)ui->sceneTriggers->itemWidget(
ui->sceneTriggers->item(index - 1));
SceneTriggerWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->sceneTriggers[index],
switcher->sceneTriggers[index - 1]);
}
void AdvSceneSwitcher::on_triggerDown_clicked()
{
int index = ui->sceneTriggers->currentRow();
if (!listMoveDown(ui->sceneTriggers)) {
return;
}
SceneTriggerWidget *s1 =
(SceneTriggerWidget *)ui->sceneTriggers->itemWidget(
ui->sceneTriggers->item(index));
SceneTriggerWidget *s2 =
(SceneTriggerWidget *)ui->sceneTriggers->itemWidget(
ui->sceneTriggers->item(index + 1));
SceneTriggerWidget::swapSwitchData(s1, s2);
std::lock_guard<std::mutex> lock(switcher->m);
std::swap(switcher->sceneTriggers[index],
switcher->sceneTriggers[index + 1]);
}
void SceneTrigger::logMatch()
{
std::string sceneName = "";
std::string statusName = "";
std::string actionName = "";
switch (triggerType) {
case sceneTriggerType::NONE:
statusName = "NONE";
break;
case sceneTriggerType::SCENE_ACTIVE:
statusName = "SCENE ACTIVE";
break;
case sceneTriggerType::SCENE_INACTIVE:
statusName = "SCENE INACTIVE";
break;
case sceneTriggerType::SCENE_LEAVE:
statusName = "SCENE LEAVE";
break;
default:
break;
}
switch (triggerAction) {
case sceneTriggerAction::NONE:
actionName = "NONE";
break;
case sceneTriggerAction::START_RECORDING:
actionName = "START RECORDING";
break;
case sceneTriggerAction::PAUSE_RECORDING:
actionName = "PAUSE RECORDING";
break;
case sceneTriggerAction::UNPAUSE_RECORDING:
actionName = "UNPAUSE RECORDING";
break;
case sceneTriggerAction::STOP_RECORDING:
actionName = "STOP RECORDING";
break;
case sceneTriggerAction::START_STREAMING:
actionName = "START STREAMING";
break;
case sceneTriggerAction::STOP_STREAMING:
actionName = "STOP STREAMING";
break;
case sceneTriggerAction::START_REPLAY_BUFFER:
actionName = "START REPLAY BUFFER";
break;
case sceneTriggerAction::STOP_REPLAY_BUFFER:
actionName = "STOP REPLAY BUFFER";
break;
case sceneTriggerAction::MUTE_SOURCE:
actionName = "MUTE (" + GetWeakSourceName(audioSource) + ")";
break;
case sceneTriggerAction::UNMUTE_SOURCE:
actionName = "UNMUTE (" + GetWeakSourceName(audioSource) + ")";
break;
case sceneTriggerAction::START_SWITCHER:
actionName = "START SCENE SWITCHER";
break;
case sceneTriggerAction::STOP_SWITCHER:
actionName = "STOP SCENE SWITCHER";
break;
case sceneTriggerAction::START_VCAM:
actionName = "START VIRTUAL CAMERA";
break;
case sceneTriggerAction::STOP_VCAM:
actionName = "STOP VIRTUAL CAMERA";
break;
default:
actionName = "UNKNOWN";
break;
}
blog(LOG_INFO,
"scene '%s' in status '%s' triggering action '%s' after %f seconds",
GetWeakSourceName(scene).c_str(), statusName.c_str(),
actionName.c_str(), duration.Seconds());
}
void frontEndActionThread(sceneTriggerAction action, double delay)
{
long long mil = delay * 1000;
std::this_thread::sleep_for(std::chrono::milliseconds(mil));
switch (action) {
case sceneTriggerAction::NONE:
break;
case sceneTriggerAction::START_RECORDING:
obs_frontend_recording_start();
break;
case sceneTriggerAction::PAUSE_RECORDING:
obs_frontend_recording_pause(true);
break;
case sceneTriggerAction::UNPAUSE_RECORDING:
obs_frontend_recording_pause(false);
break;
case sceneTriggerAction::STOP_RECORDING:
obs_frontend_recording_stop();
break;
case sceneTriggerAction::START_STREAMING:
obs_frontend_streaming_start();
break;
case sceneTriggerAction::STOP_STREAMING:
obs_frontend_streaming_stop();
break;
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(26, 0, 0)
case sceneTriggerAction::START_REPLAY_BUFFER:
obs_frontend_replay_buffer_start();
break;
case sceneTriggerAction::STOP_REPLAY_BUFFER:
obs_frontend_replay_buffer_stop();
break;
#endif
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
case sceneTriggerAction::START_VCAM:
obs_frontend_start_virtualcam();
break;
case sceneTriggerAction::STOP_VCAM:
obs_frontend_stop_virtualcam();
break;
#endif
default:
blog(LOG_WARNING, "ignoring unexpected frontend action '%d'",
static_cast<int>(action));
break;
}
}
void muteThread(OBSWeakSource source, double delay, bool mute)
{
long long mil = delay * 1000;
std::this_thread::sleep_for(std::chrono::milliseconds(mil));
auto s = obs_weak_source_get_source(source);
obs_source_set_muted(s, mute);
obs_source_release(s);
}
void statusThread(double delay, bool stop)
{
long long mil = delay * 1000;
std::this_thread::sleep_for(std::chrono::milliseconds(mil));
if (stop) {
switcher->Stop();
} else {
switcher->Start();
}
}
bool isFrontendAction(sceneTriggerAction triggerAction)
{
return triggerAction == sceneTriggerAction::START_RECORDING ||
triggerAction == sceneTriggerAction::PAUSE_RECORDING ||
triggerAction == sceneTriggerAction::UNPAUSE_RECORDING ||
triggerAction == sceneTriggerAction::STOP_RECORDING ||
triggerAction == sceneTriggerAction::START_STREAMING ||
triggerAction == sceneTriggerAction::STOP_STREAMING ||
triggerAction == sceneTriggerAction::START_REPLAY_BUFFER ||
triggerAction == sceneTriggerAction::STOP_REPLAY_BUFFER ||
triggerAction == sceneTriggerAction::START_VCAM ||
triggerAction == sceneTriggerAction::STOP_VCAM;
}
bool isAudioAction(sceneTriggerAction t)
{
return t == sceneTriggerAction::MUTE_SOURCE ||
t == sceneTriggerAction::UNMUTE_SOURCE;
}
bool isSwitcherStatusAction(sceneTriggerAction t)
{
return t == sceneTriggerAction::START_SWITCHER ||
t == sceneTriggerAction::STOP_SWITCHER;
}
void SceneTrigger::performAction()
{
if (triggerAction == sceneTriggerAction::NONE) {
return;
}
std::thread t;
if (isFrontendAction(triggerAction)) {
t = std::thread(frontEndActionThread, triggerAction,
duration.Seconds());
} else if (isAudioAction(triggerAction)) {
bool mute = triggerAction == sceneTriggerAction::MUTE_SOURCE;
t = std::thread(muteThread, audioSource, duration.Seconds(),
mute);
} else if (isSwitcherStatusAction(triggerAction)) {
bool stop = triggerAction == sceneTriggerAction::STOP_SWITCHER;
t = std::thread(statusThread, duration.Seconds(), stop);
} else {
blog(LOG_WARNING, "ignoring unknown action '%d'",
static_cast<int>(triggerAction));
}
t.detach();
}
bool SceneTrigger::checkMatch(OBSWeakSource currentScene,
OBSWeakSource previousScene)
{
switch (triggerType) {
case sceneTriggerType::NONE:
return false;
case sceneTriggerType::SCENE_ACTIVE:
return currentScene == scene;
case sceneTriggerType::SCENE_INACTIVE:
return currentScene != scene;
case sceneTriggerType::SCENE_LEAVE:
return previousScene == scene;
}
return false;
}
void SwitcherData::checkTriggers()
{
if (SceneTrigger::pause) {
return;
}
for (auto &t : sceneTriggers) {
if (stop && !isSwitcherStatusAction(t.triggerAction)) {
continue;
}
if (t.checkMatch(currentScene, previousScene)) {
t.logMatch();
t.performAction();
}
}
}
void SwitcherData::saveSceneTriggers(obs_data_t *obj)
{
obs_data_array_t *triggerArray = obs_data_array_create();
for (auto &s : sceneTriggers) {
obs_data_t *array_obj = obs_data_create();
s.save(array_obj);
obs_data_array_push_back(triggerArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "triggers", triggerArray);
obs_data_array_release(triggerArray);
}
void SwitcherData::loadSceneTriggers(obs_data_t *obj)
{
sceneTriggers.clear();
obs_data_array_t *triggerArray = obs_data_get_array(obj, "triggers");
size_t count = obs_data_array_count(triggerArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(triggerArray, i);
sceneTriggers.emplace_back();
sceneTriggers.back().load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(triggerArray);
}
void AdvSceneSwitcher::SetupTriggerTab()
{
for (auto &s : switcher->sceneTriggers) {
QListWidgetItem *item;
item = new QListWidgetItem(ui->sceneTriggers);
ui->sceneTriggers->addItem(item);
SceneTriggerWidget *sw = new SceneTriggerWidget(this, &s);
item->setSizeHint(sw->minimumSizeHint());
ui->sceneTriggers->setItemWidget(item, sw);
}
if (switcher->sceneTriggers.size() == 0) {
if (!switcher->disableHints) {
addPulse = HighlightWidget(ui->triggerAdd,
QColor(Qt::green));
}
ui->triggerHelp->setVisible(true);
} else {
ui->triggerHelp->setVisible(false);
}
}
void SceneTrigger::save(obs_data_t *obj)
{
obs_data_set_string(obj, "scene", GetWeakSourceName(scene).c_str());
obs_data_set_int(obj, "triggerType", static_cast<int>(triggerType));
obs_data_set_int(obj, "triggerAction", static_cast<int>(triggerAction));
duration.Save(obj, "duration");
obs_data_set_string(obj, "audioSource",
GetWeakSourceName(audioSource).c_str());
}
void SceneTrigger::load(obs_data_t *obj)
{
const char *sceneName = obs_data_get_string(obj, "scene");
scene = GetWeakSourceByName(sceneName);
triggerType = static_cast<sceneTriggerType>(
obs_data_get_int(obj, "triggerType"));
triggerAction = static_cast<sceneTriggerAction>(
obs_data_get_int(obj, "triggerAction"));
duration.Load(obj, "duration");
const char *audioSourceName = obs_data_get_string(obj, "audioSource");
audioSource = GetWeakSourceByName(audioSourceName);
}
static inline void populateTriggers(QComboBox *list)
{
AddSelectionEntry(
list,
obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneInactive"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneLeave"));
}
inline void populateActions(QComboBox *list)
{
AddSelectionEntry(
list,
obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.pauseRecording"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unpauseRecording"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopRecording"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startVirtualCamera"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopVirtualCamera"));
}
SceneTriggerWidget::SceneTriggerWidget(QWidget *parent, SceneTrigger *s)
: SwitchWidget(parent, s, false, false)
{
triggers = new QComboBox();
actions = new QComboBox();
duration = new DurationSelection();
audioSources = new QComboBox();
QWidget::connect(triggers, SIGNAL(currentIndexChanged(int)), this,
SLOT(TriggerTypeChanged(int)));
QWidget::connect(actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(TriggerActionChanged(int)));
QWidget::connect(duration, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(DurationChanged(const Duration &)));
QWidget::connect(audioSources,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(AudioSourceChanged(const QString &)));
populateTriggers(triggers);
populateActions(actions);
PopulateAudioSelection(audioSources);
if (s) {
triggers->setCurrentIndex(static_cast<int>(s->triggerType));
actions->setCurrentIndex(static_cast<int>(s->triggerAction));
duration->SetDuration(s->duration);
audioSources->setCurrentText(
GetWeakSourceName(s->audioSource).c_str());
if (isAudioAction(s->triggerAction)) {
audioSources->show();
} else {
audioSources->hide();
}
}
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{triggers}}", triggers},
{"{{actions}}", actions},
{"{{audioSources}}", audioSources},
{"{{duration}}", duration},
{"{{scenes}}", scenes}};
PlaceWidgets(obs_module_text("AdvSceneSwitcher.sceneTriggerTab.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
switchData = s;
loading = false;
}
SceneTrigger *SceneTriggerWidget::getSwitchData()
{
return switchData;
}
void SceneTriggerWidget::setSwitchData(SceneTrigger *s)
{
switchData = s;
}
void SceneTriggerWidget::swapSwitchData(SceneTriggerWidget *s1,
SceneTriggerWidget *s2)
{
SwitchWidget::swapSwitchData(s1, s2);
SceneTrigger *t = s1->getSwitchData();
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
void SceneTriggerWidget::TriggerTypeChanged(int index)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->triggerType = static_cast<sceneTriggerType>(index);
}
void SceneTriggerWidget::TriggerActionChanged(int index)
{
if (loading || !switchData) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
switchData->triggerAction =
static_cast<sceneTriggerAction>(index);
}
if (isAudioAction(switchData->triggerAction)) {
audioSources->show();
} else {
audioSources->hide();
}
}
void SceneTriggerWidget::DurationChanged(const Duration &duration)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->duration = duration;
}
void SceneTriggerWidget::AudioSourceChanged(const QString &text)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->audioSource = GetWeakSourceByQString(text);
}
} // namespace advss

View File

@@ -1,88 +0,0 @@
/******************************************************************************
Note: Long-term goal is to remove this tab / file.
Most functionality shall be moved to the Macro tab instead.
So if you plan to make changes here, please consider applying them to the
corresponding macro tab functionality instead.
******************************************************************************/
#pragma once
#include "switch-generic.hpp"
#include "duration-control.hpp"
namespace advss {
enum class sceneTriggerType {
NONE = 0,
SCENE_ACTIVE = 1,
SCENE_INACTIVE = 2,
SCENE_LEAVE = 3,
};
enum class sceneTriggerAction {
NONE = 0,
START_RECORDING,
PAUSE_RECORDING,
UNPAUSE_RECORDING,
STOP_RECORDING,
START_STREAMING,
STOP_STREAMING,
START_REPLAY_BUFFER,
STOP_REPLAY_BUFFER,
MUTE_SOURCE,
UNMUTE_SOURCE,
START_SWITCHER,
STOP_SWITCHER,
START_VCAM,
STOP_VCAM,
};
struct SceneTrigger : SceneSwitcherEntry {
static bool pause;
sceneTriggerType triggerType = sceneTriggerType::NONE;
sceneTriggerAction triggerAction = sceneTriggerAction::NONE;
Duration duration;
OBSWeakSource audioSource = nullptr;
const char *getType() { return "trigger"; }
void save(obs_data_t *obj);
void load(obs_data_t *obj);
bool checkMatch(OBSWeakSource currentScene,
OBSWeakSource previousScene);
void performAction();
void logMatch();
};
class SceneTriggerWidget : public SwitchWidget {
Q_OBJECT
public:
SceneTriggerWidget(QWidget *parent, SceneTrigger *s);
SceneTrigger *getSwitchData();
void setSwitchData(SceneTrigger *s);
static void swapSwitchData(SceneTriggerWidget *s1,
SceneTriggerWidget *s2);
private slots:
void TriggerTypeChanged(int index);
void TriggerActionChanged(int index);
void DurationChanged(const Duration &);
void AudioSourceChanged(const QString &text);
private:
QComboBox *triggers;
QComboBox *actions;
DurationSelection *duration;
QComboBox *audioSources;
SceneTrigger *switchData;
};
} // namespace advss

View File

@@ -22,7 +22,7 @@ void AdvSceneSwitcher::on_audioAdd_clicked()
AudioSwitchWidget *sw =
new AudioSwitchWidget(this, &switcher->audioSwitches.back());
listAddClicked(ui->audioSwitches, sw, ui->audioAdd, addPulse);
listAddClicked(ui->audioSwitches, sw, &addPulse);
ui->audioHelp->setVisible(false);
}

View File

@@ -19,7 +19,7 @@ void AdvSceneSwitcher::on_executableAdd_clicked()
listAddClicked(ui->executables,
new ExecutableSwitchWidget(
this, &switcher->executableSwitches.back()),
ui->executableAdd, addPulse);
&addPulse);
ui->exeHelp->setVisible(false);
}

View File

@@ -21,6 +21,29 @@ bool FileSwitch::pause = false;
static QObject *addPulse = nullptr;
static std::hash<std::string> strHash;
static void writeToStatusFile(const QString &msg)
{
if (!GetSwitcher() || !GetSwitcher()->fileIO.writeEnabled ||
GetSwitcher()->fileIO.writePath.empty()) {
return;
}
QFile file(QString::fromStdString(GetSwitcher()->fileIO.writePath));
if (file.open(QIODevice::ReadWrite)) {
QTextStream stream(&file);
stream << msg << Qt::endl;
}
file.close();
}
static bool _ = []() {
AddStartStep(
[]() { writeToStatusFile("Advanced Scene Switcher running"); });
AddStopStep(
[]() { writeToStatusFile("Advanced Scene Switcher stopped"); });
return true;
}();
void AdvSceneSwitcher::on_browseButton_clicked()
{
QString path = QFileDialog::getOpenFileName(
@@ -113,24 +136,6 @@ void SwitcherData::writeSceneInfoToFile()
}
}
void SwitcherData::writeToStatusFile(const QString &msg)
{
if (!fileIO.writeEnabled || fileIO.writePath.empty()) {
return;
}
QFile file(QString::fromStdString(fileIO.writePath));
if (file.open(QIODevice::ReadWrite)) {
QTextStream stream(&file);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
stream << msg << "\n";
#else
stream << msg << Qt::endl;
#endif
}
file.close();
}
bool SwitcherData::checkSwitchInfoFromFile(OBSWeakSource &scene,
OBSWeakSource &transition)
{
@@ -291,7 +296,7 @@ void AdvSceneSwitcher::on_fileAdd_clicked()
listAddClicked(ui->fileSwitches,
new FileSwitchWidget(this,
&switcher->fileSwitches.back()),
ui->fileAdd, addPulse);
&addPulse);
ui->fileHelp->setVisible(false);
}

View File

@@ -22,7 +22,7 @@ void AdvSceneSwitcher::on_mediaAdd_clicked()
listAddClicked(ui->mediaSwitches,
new MediaSwitchWidget(this,
&switcher->mediaSwitches.back()),
ui->mediaAdd, addPulse);
&addPulse);
ui->mediaHelp->setVisible(false);
}

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ void AdvSceneSwitcher::on_pauseAdd_clicked()
listAddClicked(ui->pauseEntries,
new PauseEntryWidget(this,
&switcher->pauseEntries.back()),
ui->pauseAdd, addPulse);
&addPulse);
ui->pauseHelp->setVisible(false);
}

View File

@@ -19,7 +19,7 @@ void AdvSceneSwitcher::on_randomAdd_clicked()
listAddClicked(ui->randomSwitches,
new RandomSwitchWidget(this,
&switcher->randomSwitches.back()),
ui->randomAdd, addPulse);
&addPulse);
ui->randomHelp->setVisible(false);
}
@@ -143,7 +143,7 @@ void AdvSceneSwitcher::SetupRandomTab()
border-style: outset; \
border-width: 2px; \
border-radius: 7px; \
border-color: rgb(0,0,0,0) \
border-color: rgb(0,0,0) \
}");
if (switcher->switchIfNotMatching != NoMatchBehavior::RANDOM_SWITCH) {
if (!switcher->disableHints) {

View File

@@ -79,7 +79,7 @@ void AdvSceneSwitcher::on_screenRegionAdd_clicked()
listAddClicked(ui->screenRegionSwitches,
new ScreenRegionWidget(
this, &switcher->screenRegionSwitches.back()),
ui->screenRegionAdd, addPulse);
&addPulse);
ui->regionHelp->setVisible(false);
}

View File

@@ -26,7 +26,7 @@ void AdvSceneSwitcher::on_sceneSequenceAdd_clicked()
listAddClicked(ui->sceneSequenceSwitches,
new SequenceWidget(
this, &switcher->sceneSequenceSwitches.back()),
ui->sceneSequenceAdd, addPulse);
&addPulse);
ui->sequenceHelp->setVisible(false);
}
@@ -585,6 +585,8 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
QVariant(QStringLiteral("addIconSmall")));
reduce->setProperty("themeID",
QVariant(QStringLiteral("removeIconSmall")));
extend->setProperty("class", QVariant(QStringLiteral("icon-plus")));
reduce->setProperty("class", QVariant(QStringLiteral("icon-trash")));
extend->setMaximumSize(22, 22);
reduce->setMaximumSize(22, 22);

View File

@@ -17,7 +17,7 @@ void AdvSceneSwitcher::on_timeAdd_clicked()
listAddClicked(ui->timeSwitches,
new TimeSwitchWidget(this,
&switcher->timeSwitches.back()),
ui->timeAdd, addPulse);
&addPulse);
ui->timeHelp->setVisible(false);
}

View File

@@ -24,7 +24,7 @@ void AdvSceneSwitcher::on_videoAdd_clicked()
VideoSwitchWidget *sw =
new VideoSwitchWidget(this, &switcher->videoSwitches.back());
listAddClicked(ui->videoSwitches, sw, ui->videoAdd, addPulse);
listAddClicked(ui->videoSwitches, sw, &addPulse);
ui->videoHelp->setVisible(false);
}
@@ -105,7 +105,7 @@ void AdvSceneSwitcher::on_getScreenshot_clicked()
}
auto source = obs_weak_source_get_source(s->videoSource);
auto screenshotData = std::make_unique<ScreenshotHelper>(source);
auto screenshotData = std::make_unique<Screenshot>(source);
obs_source_release(source);
QString filePath = QFileDialog::getSaveFileName(this);
@@ -121,16 +121,16 @@ void AdvSceneSwitcher::on_getScreenshot_clicked()
// During selection of the save path enough time should usually have
// passed already
// Add this just in case ...
if (!screenshotData->done) {
if (!screenshotData->IsDone()) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (!screenshotData->done) {
if (!screenshotData->IsDone()) {
DisplayMessage("Failed to get screenshot of source!");
return;
}
screenshotData->image.save(file.fileName());
screenshotData->GetImage().save(file.fileName());
sw->SetFilePath(file.fileName());
}
@@ -262,7 +262,7 @@ void VideoSwitch::load(obs_data_t *obj)
void VideoSwitch::getScreenshot()
{
auto source = obs_weak_source_get_source(videoSource);
screenshotData = std::make_unique<ScreenshotHelper>(source);
screenshotData = std::make_unique<Screenshot>(source);
obs_source_release(source);
}
@@ -293,56 +293,55 @@ bool VideoSwitch::checkMatch()
bool match = false;
if (screenshotData) {
if (screenshotData->done) {
bool conditionMatch = false;
switch (condition) {
case videoSwitchType::MATCH:
conditionMatch = screenshotData->image ==
matchImage;
break;
case videoSwitchType::DIFFER:
conditionMatch = screenshotData->image !=
matchImage;
break;
case videoSwitchType::HAS_NOT_CHANGED:
conditionMatch = screenshotData->image ==
matchImage;
break;
case videoSwitchType::HAS_CHANGED:
conditionMatch = screenshotData->image !=
matchImage;
break;
default:
break;
}
if (conditionMatch) {
currentMatchDuration +=
std::chrono::duration_cast<
std::chrono::milliseconds>(
screenshotData->time -
previousTime);
} else {
currentMatchDuration = {};
}
bool durationMatch = currentMatchDuration.count() >=
duration * 1000;
if (conditionMatch && durationMatch) {
match = true;
}
if (!requiresFileInput(condition)) {
matchImage = std::move(screenshotData->image);
}
previousTime = std::move(screenshotData->time);
screenshotData.reset(nullptr);
}
if (!screenshotData) {
getScreenshot();
return match;
}
if (!screenshotData->IsDone()) {
getScreenshot();
return match;
}
bool conditionMatch = false;
switch (condition) {
case videoSwitchType::MATCH:
conditionMatch = screenshotData->GetImage() == matchImage;
break;
case videoSwitchType::DIFFER:
conditionMatch = screenshotData->GetImage() != matchImage;
break;
case videoSwitchType::HAS_NOT_CHANGED:
conditionMatch = screenshotData->GetImage() == matchImage;
break;
case videoSwitchType::HAS_CHANGED:
conditionMatch = screenshotData->GetImage() != matchImage;
break;
default:
break;
}
if (conditionMatch) {
currentMatchDuration +=
std::chrono::duration_cast<std::chrono::milliseconds>(
screenshotData->GetScreenshotTime() -
previousTime);
} else {
currentMatchDuration = {};
}
bool durationMatch = currentMatchDuration.count() >= duration * 1000;
if (conditionMatch && durationMatch) {
match = true;
}
if (!requiresFileInput(condition)) {
matchImage = screenshotData->GetImage();
}
previousTime = screenshotData->GetScreenshotTime();
screenshotData.reset(nullptr);
getScreenshot();
return match;

View File

@@ -32,7 +32,7 @@ struct VideoSwitch : virtual SceneSwitcherEntry {
double duration = 0;
bool ignoreInactiveSource = false;
std::unique_ptr<ScreenshotHelper> screenshotData = nullptr;
std::unique_ptr<Screenshot> screenshotData = nullptr;
std::chrono::high_resolution_clock::time_point previousTime{};
QImage matchImage;

View File

@@ -21,7 +21,7 @@ void AdvSceneSwitcher::on_windowAdd_clicked()
listAddClicked(ui->windowSwitches,
new WindowSwitchWidget(this,
&switcher->windowSwitches.back()),
ui->windowAdd, addPulse);
&addPulse);
ui->windowHelp->setVisible(false);
}

View File

@@ -384,7 +384,11 @@ static void getProcessListProcps2(QStringList &processes)
return;
}
while ((stack = procps_pids_get_(info, PIDS_FETCH_TASKS_ONLY))) {
#ifdef PROCPS2_USE_INFO
auto cmd = PIDS_VAL(0, str, stack, info);
#else
auto cmd = PIDS_VAL(0, str, stack);
#endif
QString procName(cmd);
if (!procName.isEmpty() && !processes.contains(procName)) {
processes << procName;
@@ -546,6 +550,11 @@ static void initProc2()
#endif
}
int ignoreXerror(Display *d, XErrorEvent *e)
{
return 0;
}
void PlatformInit()
{
auto display = disp();
@@ -556,6 +565,7 @@ void PlatformInit()
initXss();
initProcps();
initProc2();
XSetErrorHandler(ignoreXerror);
}
static void cleanupHelper(QLibrary *lib)
@@ -572,6 +582,7 @@ void PlatformCleanup()
cleanupHelper(libprocps);
cleanupHelper(libproc2);
cleanupDisplay();
XSetErrorHandler(NULL);
}
} // namespace advss

View File

@@ -7,8 +7,6 @@
#include "section.hpp"
#include "switch-button.hpp"
#include <QGraphicsOpacityEffect>
namespace advss {
static inline void populateActionSelection(QComboBox *list)
@@ -17,6 +15,9 @@ static inline void populateActionSelection(QComboBox *list)
QString entry(obs_module_text(action._name.c_str()));
if (list->findText(entry) == -1) {
list->addItem(entry);
qobject_cast<QListView *>(list->view())
->setRowHidden(list->count() - 1,
action._hidden);
} else {
blog(LOG_WARNING,
"did not insert duplicate action entry with name \"%s\"",
@@ -60,12 +61,40 @@ MacroActionEdit::MacroActionEdit(QWidget *parent,
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData(id);
SetupWidgets(true);
_actionStateTimer.start(300);
_loading = false;
}
void MacroActionEdit::SetupWidgets(bool basicSetup)
{
if (_allWidgetsAreSetup) {
return;
}
const auto id = (*_entryData)->GetId();
_actionSelection->setCurrentText(
obs_module_text(MacroActionFactory::GetActionName(id).c_str()));
const bool enabled = (*_entryData)->Enabled();
_enable->setChecked(enabled);
SetDisableEffect(!enabled);
HeaderInfoChanged(
QString::fromStdString((*_entryData)->GetShortDesc()));
if (basicSetup) {
return;
}
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &)));
_section->SetContent(widget, (*_entryData)->GetCollapsed());
SetFocusPolicyOfWidgets();
_allWidgetsAreSetup = true;
}
void MacroActionEdit::ActionSelectionChanged(const QString &text)
{
if (_loading || !_entryData) {
@@ -95,38 +124,11 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
SetFocusPolicyOfWidgets();
}
void MacroActionEdit::UpdateEntryData(const std::string &id)
{
_actionSelection->setCurrentText(
obs_module_text(MacroActionFactory::GetActionName(id).c_str()));
const bool enabled = (*_entryData)->Enabled();
_enable->setChecked(enabled);
SetDisableEffect(!enabled);
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &)));
HeaderInfoChanged(
QString::fromStdString((*_entryData)->GetShortDesc()));
_section->SetContent(widget, (*_entryData)->GetCollapsed());
SetFocusPolicyOfWidgets();
}
void MacroActionEdit::SetEntryData(std::shared_ptr<MacroAction> *data)
{
_entryData = data;
}
void MacroActionEdit::SetDisableEffect(bool value)
{
if (value) {
auto effect = new QGraphicsOpacityEffect(this);
effect->setOpacity(0.5);
_section->setGraphicsEffect(effect);
} else {
_section->setGraphicsEffect(nullptr);
}
}
void MacroActionEdit::ActionEnableChanged(bool value)
{
if (_loading || !_entryData) {
@@ -144,13 +146,9 @@ void MacroActionEdit::UpdateActionState()
return;
}
SetEnableAppearance((*_entryData)->Enabled());
}
void MacroActionEdit::SetEnableAppearance(bool value)
{
_enable->setChecked(value);
SetDisableEffect(!value);
const bool enabled = (*_entryData)->Enabled();
SetEnableAppearance(enabled);
_enable->setChecked(enabled);
}
std::shared_ptr<MacroSegment> MacroActionEdit::Data() const

View File

@@ -17,7 +17,7 @@ public:
QWidget *parent = nullptr,
std::shared_ptr<MacroAction> * = nullptr,
const std::string &id = MacroAction::GetDefaultID().data());
void UpdateEntryData(const std::string &id);
void SetupWidgets(bool basicSetup = false);
void SetEntryData(std::shared_ptr<MacroAction> *);
private slots:
@@ -27,8 +27,6 @@ private slots:
private:
std::shared_ptr<MacroSegment> Data() const;
void SetDisableEffect(bool);
void SetEnableAppearance(bool);
FilterComboBox *_actionSelection;
SwitchButton *_enable;

View File

@@ -1,7 +1,28 @@
#include "macro-action-factory.hpp"
#include <mutex>
namespace advss {
namespace {
class MacroActionUnknown : public MacroAction {
public:
MacroActionUnknown(Macro *m) : MacroAction(m) {}
std::shared_ptr<MacroAction> Copy() const
{
return std::make_shared<MacroActionUnknown>(GetMacro());
}
bool PerformAction() { return true; };
bool Save(obs_data_t *obj) const { return MacroAction::Save(obj); };
bool Load(obs_data_t *obj) { return MacroAction::Load(obj); };
std::string GetId() const { return "unknown"; }
};
} // namespace
static std::recursive_mutex mutex;
std::map<std::string, MacroActionInfo> &MacroActionFactory::GetMap()
{
static std::map<std::string, MacroActionInfo> _methods;
@@ -10,6 +31,7 @@ std::map<std::string, MacroActionInfo> &MacroActionFactory::GetMap()
bool MacroActionFactory::Register(const std::string &id, MacroActionInfo info)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it == GetMap().end()) {
GetMap()[id] = info;
return true;
@@ -17,35 +39,61 @@ bool MacroActionFactory::Register(const std::string &id, MacroActionInfo info)
return false;
}
bool MacroActionFactory::Deregister(const std::string &id)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (GetMap().count(id) == 0) {
return false;
}
GetMap().erase(id);
return true;
}
static std::shared_ptr<MacroAction> createUnknownAction(Macro *m)
{
return std::make_shared<MacroActionUnknown>(m);
}
std::shared_ptr<MacroAction> MacroActionFactory::Create(const std::string &id,
Macro *m)
{
if (auto it = GetMap().find(id); it != GetMap().end())
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._create(m);
}
return nullptr;
return createUnknownAction(m);
}
static QWidget *createUnknownActionWidget()
{
return new QLabel(obs_module_text("AdvSceneSwitcher.action.unknown"));
}
QWidget *MacroActionFactory::CreateWidget(const std::string &id,
QWidget *parent,
std::shared_ptr<MacroAction> action)
{
if (auto it = GetMap().find(id); it != GetMap().end())
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._createWidget(parent, action);
}
return nullptr;
return createUnknownActionWidget();
}
std::string MacroActionFactory::GetActionName(const std::string &id)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._name;
}
return "unknown action";
return obs_module_text("AdvSceneSwitcher.action.unknown");
}
std::string MacroActionFactory::GetIdByName(const QString &name)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
for (auto it : GetMap()) {
if (name == obs_module_text(it.second._name.c_str())) {
return it.first;
@@ -54,4 +102,14 @@ std::string MacroActionFactory::GetIdByName(const QString &name)
return "";
}
bool CanCreateDefaultAction()
{
const auto action = MacroActionFactory::Create(
MacroAction::GetDefaultID().data(), nullptr);
if (!action) {
return false;
}
return action->GetId() == MacroAction::GetDefaultID().data();
}
} // namespace advss

View File

@@ -6,12 +6,13 @@
namespace advss {
struct MacroActionInfo {
using CreateAction = std::shared_ptr<MacroAction> (*)(Macro *m);
using CreateActionWidget = QWidget *(*)(QWidget *parent,
std::shared_ptr<MacroAction>);
CreateAction _create = nullptr;
std::function<std::shared_ptr<MacroAction>(Macro *m)> _create = nullptr;
CreateActionWidget _createWidget = nullptr;
std::string _name;
bool _hidden = false;
};
class MacroActionFactory {
@@ -19,6 +20,7 @@ public:
MacroActionFactory() = delete;
EXPORT static bool Register(const std::string &id, MacroActionInfo);
static bool Deregister(const std::string &id);
static std::shared_ptr<MacroAction> Create(const std::string &id,
Macro *m);
static QWidget *CreateWidget(const std::string &id, QWidget *parent,
@@ -31,4 +33,6 @@ private:
static std::map<std::string, MacroActionInfo> &GetMap();
};
bool CanCreateDefaultAction();
} // namespace advss

View File

@@ -196,13 +196,13 @@ void MacroActionMacro::RunActions(Macro *actionMacro) const
}
if (_runOptions.reevaluateConditionState) {
conditionMacro->CeckMatch(true);
conditionMacro->CheckConditions(true);
}
if ((_runOptions.logic == RunOptions::Logic::CONDITIONS &&
conditionMacro->Matched()) ||
conditionMacro->ConditionsMatched()) ||
(_runOptions.logic == RunOptions::Logic::INVERT_CONDITIONS &&
!conditionMacro->Matched())) {
!conditionMacro->ConditionsMatched())) {
runActionsHelper(actionMacro, _runOptions.runElseActions,
_runOptions.setInputs, _runOptions.inputs);
}
@@ -309,11 +309,11 @@ MacroActionMacroEdit::MacroActionMacroEdit(
_loading = false;
}
void HighligthMacroSettingsButton(bool enable);
void HighlightMacroSettingsButton(bool enable);
MacroActionMacroEdit::~MacroActionMacroEdit()
{
HighligthMacroSettingsButton(false);
HighlightMacroSettingsButton(false);
}
void MacroActionMacroEdit::UpdateEntryData()
@@ -480,7 +480,7 @@ void MacroActionMacroEdit::SetWidgetVisibility()
_inputs->setVisible(_entryData->_action ==
MacroActionMacro::Action::RUN &&
_entryData->_runOptions.setInputs);
HighligthMacroSettingsButton(_entryData->_action ==
HighlightMacroSettingsButton(_entryData->_action ==
MacroActionMacro::Action::RUN &&
_entryData->_runOptions.setInputs &&
!_inputs->HasInputsToSet());

View File

@@ -0,0 +1,120 @@
#include "macro-action-script.hpp"
#include "layout-helpers.hpp"
#include "macro-helpers.hpp"
#include "properties-view.hpp"
#include "sync-helpers.hpp"
namespace advss {
MacroActionScript::MacroActionScript(
Macro *m, const std::string &id, const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignalName,
const std::string &completionSignalName,
const std::string &newInstanceSignalName,
const std::string &deletedInstanceSignalName)
: MacroAction(m),
MacroSegmentScript(defaultSettings, propertiesSignalName,
triggerSignalName, completionSignalName,
newInstanceSignalName, deletedInstanceSignalName),
_id(id)
{
}
MacroActionScript::MacroActionScript(const advss::MacroActionScript &other)
: MacroAction(other.GetMacro()),
MacroSegmentScript(other),
_id(other._id)
{
}
bool MacroActionScript::PerformAction()
{
if (!ScriptHandler::ActionIdIsValid(_id)) {
blog(LOG_WARNING, "skipping unknown script action \"%s\"",
_id.c_str());
return true;
}
(void)SendTriggerSignal();
return true;
}
void MacroActionScript::LogAction() const
{
ablog(LOG_INFO, "performing script action \"%s\"", GetId().c_str());
}
bool MacroActionScript::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
MacroSegmentScript::Save(obj);
return true;
}
bool MacroActionScript::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
MacroSegmentScript::Load(obj);
return true;
}
std::shared_ptr<MacroAction> MacroActionScript::Copy() const
{
return std::make_shared<MacroActionScript>(*this);
}
void MacroActionScript::WaitForCompletion() const
{
using namespace std::chrono_literals;
auto start = std::chrono::high_resolution_clock::now();
auto timePassed = std::chrono::duration_cast<std::chrono::milliseconds>(
start - start);
const auto timeoutMs = GetTimeoutSeconds() * 1000.0;
std::unique_lock<std::mutex> lock(*GetMutex());
while (!TriggerIsCompleted()) {
if (MacroWaitShouldAbort() || MacroIsStopped(GetMacro())) {
break;
}
if ((double)timePassed.count() > timeoutMs) {
blog(LOG_INFO, "script action timeout (%s)",
_id.c_str());
break;
}
GetMacroWaitCV().wait_for(lock, 10ms);
const auto now = std::chrono::high_resolution_clock::now();
timePassed =
std::chrono::duration_cast<std::chrono::milliseconds>(
now - start);
}
}
void MacroActionScript::RegisterTempVarHelper(const std::string &variableId,
const std::string &name,
const std::string &helpText)
{
AddTempvar(variableId, name, helpText);
}
void MacroActionScript::DeregisterAllTempVarsHelper()
{
MacroSegment::SetupTempVars();
}
void MacroActionScript::SetTempVarValueHelper(const std::string &variableId,
const std::string &value)
{
MacroAction::SetTempVarValue(variableId, value);
}
void MacroActionScript::SetupTempVars()
{
// This just exists so MacroSegment::SetupTempVars() is not called.
// We want the ScriptHandler to handle the registration and clearing of
// the temp vars.
}
} // namespace advss

View File

@@ -0,0 +1,38 @@
#pragma once
#include "macro-action-edit.hpp"
#include "macro-script-handler.hpp"
#include "macro-segment-script.hpp"
namespace advss {
class MacroActionScript : public MacroAction, public MacroSegmentScript {
public:
MacroActionScript(Macro *m, const std::string &id,
const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignalName,
const std::string &completionSignalName,
const std::string &newInstanceSignalName,
const std::string &deletedInstanceSignalName);
MacroActionScript(const advss::MacroActionScript &);
bool PerformAction();
void LogAction() const;
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetId() const { return _id; };
std::shared_ptr<MacroAction> Copy() const;
private:
void WaitForCompletion() const;
void RegisterTempVarHelper(const std::string &variableId,
const std::string &name,
const std::string &helpText);
void DeregisterAllTempVarsHelper();
void SetTempVarValueHelper(const std::string &variableId,
const std::string &value);
void SetupTempVars();
std::string _id = "";
};
} // namespace advss

File diff suppressed because it is too large Load Diff

View File

@@ -28,8 +28,8 @@ public:
int GetSegmentIndexValue() const;
void ResolveVariablesToFixedValues();
enum class Type {
SET_FIXED_VALUE,
enum class Action {
SET_VALUE,
APPEND,
APPEND_VAR,
INCREMENT,
@@ -50,18 +50,23 @@ public:
PAD,
TRUNCATE,
SWAP_VALUES,
TRIM,
CHANGE_CASE,
RANDOM_NUMBER,
QUERY_JSON,
ARRAY_JSON,
};
Type _type = Type::SET_FIXED_VALUE;
Action _action = Action::SET_VALUE;
std::weak_ptr<Variable> _variable;
std::weak_ptr<Variable> _variable2;
StringVariable _strValue = "";
double _numValue = 0;
int _subStringStart = 0;
int _subStringSize = 0;
DoubleVariable _numValue = 0;
IntVariable _subStringStart = 0;
IntVariable _subStringSize = 0;
RegexConfig _subStringRegex = RegexConfig::PartialMatchRegexConfig();
std::string _regexPattern = ".*";
int _regexMatchIdx = 0;
IntVariable _regexMatchIdx = 0;
RegexConfig _findRegex;
StringVariable _findStr = obs_module_text(
"AdvSceneSwitcher.action.variable.findAndReplace.find");
@@ -89,13 +94,30 @@ public:
IntVariable _stringLength = 1;
char _paddingChar = '0';
enum class CaseType {
LOWER_CASE,
UPPER_CASE,
CAPITALIZED,
START_CASE,
};
CaseType _caseType = CaseType::LOWER_CASE;
DoubleVariable _randomNumberStart = 0;
DoubleVariable _randomNumberEnd = 100;
bool _generateInteger = true;
StringVariable _jsonQuery = "$.some.nested.value";
IntVariable _jsonIndex = 0;
private:
void DecrementCurrentSegmentVariableRef();
void HandleIndexSubString(Variable *);
void HandleRegexSubString(Variable *);
void HandleFindAndReplace(Variable *);
void HandleMathExpression(Variable *);
void HandleCaseChange(Variable *);
void SetToSceneItemName(Variable *);
void GenerateRandomNumber(Variable *);
std::weak_ptr<MacroSegment> _macroSegment;
int _segmentIdxLoadValue = -1;
@@ -124,15 +146,15 @@ private slots:
void Variable2Changed(const QString &);
void ActionChanged(int);
void StrValueChanged();
void NumValueChanged(double);
void NumValueChanged(const NumberVariable<double> &value);
void SegmentIndexChanged(const IntVariable &);
void UpdateSegmentVariableValue();
void MacroSegmentOrderChanged();
void SubStringStartChanged(int val);
void SubStringSizeChanged(int val);
void SubStringStartChanged(const NumberVariable<int> &start);
void SubStringSizeChanged(const NumberVariable<int> &size);
void SubStringRegexChanged(const RegexConfig &conf);
void RegexPatternChanged();
void RegexMatchIdxChanged(int val);
void RegexMatchIdxChanged(const NumberVariable<int> &index);
void FindStrValueChanged();
void FindRegexChanged(const RegexConfig &conf);
void ReplaceStrValueChanged();
@@ -148,6 +170,12 @@ private slots:
void DirectionChanged(int);
void StringLengthChanged(const NumberVariable<int> &);
void CharSelectionChanged(const QString &);
void CaseTypeChanged(int index);
void RandomNumberStartChanged(const NumberVariable<double> &);
void RandomNumberEndChanged(const NumberVariable<double> &);
void GenerateIntegerChanged(int);
void JsonQueryChanged();
void JsonIndexChanged(const NumberVariable<int> &);
signals:
void HeaderInfoChanged(const QString &);
@@ -160,18 +188,18 @@ private:
VariableSelection *_variables2;
FilterComboBox *_actions;
VariableTextEdit *_strValue;
QDoubleSpinBox *_numValue;
VariableDoubleSpinBox *_numValue;
MacroSegmentSelection *_segmentIdx;
QLabel *_segmentValueStatus;
ResizingPlainTextEdit *_segmentValue;
QVBoxLayout *_substringLayout;
QHBoxLayout *_subStringIndexEntryLayout;
QHBoxLayout *_subStringRegexEntryLayout;
QSpinBox *_subStringStart;
QSpinBox *_subStringSize;
VariableSpinBox *_subStringStart;
VariableSpinBox *_subStringSize;
RegexConfigWidget *_substringRegex;
ResizingPlainTextEdit *_regexPattern;
QSpinBox *_regexMatchIdx;
VariableSpinBox *_regexMatchIdx;
QHBoxLayout *_findReplaceLayout;
RegexConfigWidget *_findRegex;
VariableTextEdit *_findStr;
@@ -192,6 +220,14 @@ private:
QComboBox *_direction;
VariableSpinBox *_stringLength;
SingleCharSelection *_paddingCharSelection;
FilterComboBox *_caseType;
VariableDoubleSpinBox *_randomNumberStart;
VariableDoubleSpinBox *_randomNumberEnd;
QCheckBox *_generateInteger;
QVBoxLayout *_randomLayout;
VariableLineEdit *_jsonQuery;
QLabel *_jsonQueryHelp;
VariableSpinBox *_jsonIndex;
QHBoxLayout *_entryLayout;
std::shared_ptr<MacroActionVariable> _entryData;

View File

@@ -11,15 +11,12 @@ bool MacroAction::Save(obs_data_t *obj) const
{
MacroSegment::Save(obj);
obs_data_set_string(obj, "id", GetId().c_str());
obs_data_set_bool(obj, "enabled", _enabled);
return true;
}
bool MacroAction::Load(obs_data_t *obj)
{
MacroSegment::Load(obj);
obs_data_set_default_bool(obj, "enabled", true);
_enabled = obs_data_get_bool(obj, "enabled");
return true;
}
@@ -28,16 +25,6 @@ void MacroAction::LogAction() const
ablog(LOG_INFO, "performed action %s", GetId().c_str());
}
void MacroAction::SetEnabled(bool value)
{
_enabled = value;
}
bool MacroAction::Enabled() const
{
return _enabled;
}
void MacroAction::ResolveVariablesToFixedValues() {}
std::string_view MacroAction::GetDefaultID()

View File

@@ -19,13 +19,9 @@ public:
// Used to resolve variables before actions are added to action queues
virtual void ResolveVariablesToFixedValues();
void SetEnabled(bool);
bool Enabled() const;
static std::string_view GetDefaultID();
private:
bool _enabled = true;
};
class EXPORT MacroRefAction : virtual public MacroAction {

View File

@@ -10,26 +10,6 @@
namespace advss {
static inline void populateLogicSelection(QComboBox *list, bool root = false)
{
if (root) {
for (const auto &entry : MacroCondition::logicTypes) {
if (static_cast<int>(entry.first) < logic_root_offset) {
list->addItem(obs_module_text(
entry.second._name.c_str()));
}
}
} else {
for (const auto &entry : MacroCondition::logicTypes) {
if (static_cast<int>(entry.first) >=
logic_root_offset) {
list->addItem(obs_module_text(
entry.second._name.c_str()));
}
}
}
}
static inline void populateConditionSelection(QComboBox *list)
{
for (const auto &[_, condition] :
@@ -120,13 +100,13 @@ void DurationModifierEdit::Collapse(bool collapse)
MacroConditionEdit::MacroConditionEdit(
QWidget *parent, std::shared_ptr<MacroCondition> *entryData,
const std::string &id, bool root)
const std::string &id, bool isRootCondition)
: MacroSegmentEdit(parent),
_logicSelection(new QComboBox()),
_conditionSelection(new FilterComboBox()),
_dur(new DurationModifierEdit()),
_entryData(entryData),
_isRoot(root)
_isRoot(isRootCondition)
{
QWidget::connect(_logicSelection, SIGNAL(currentIndexChanged(int)),
this, SLOT(LogicSelectionChanged(int)));
@@ -139,7 +119,7 @@ MacroConditionEdit::MacroConditionEdit(
this,
SLOT(DurationModifierChanged(DurationModifier::Type)));
populateLogicSelection(_logicSelection, root);
Logic::PopulateLogicTypeSelection(_logicSelection, isRootCondition);
populateConditionSelection(_conditionSelection);
_section->AddHeaderWidget(_logicSelection);
@@ -158,7 +138,7 @@ MacroConditionEdit::MacroConditionEdit(
mainLayout->addWidget(_frame);
setLayout(mainLayout);
UpdateEntryData(id);
SetupWidgets(true);
_loading = false;
}
@@ -168,31 +148,25 @@ void MacroConditionEdit::LogicSelectionChanged(int idx)
return;
}
LogicType type;
if (IsRootNode()) {
type = static_cast<LogicType>(idx);
} else {
type = static_cast<LogicType>(idx + logic_root_offset);
}
auto lock = LockContext();
(*_entryData)->SetLogicType(type);
const auto logic = static_cast<Logic::Type>(
_logicSelection->itemData(idx).toInt());
(*_entryData)->SetLogicType(logic);
SetEnableAppearance(logic != Logic::Type::NONE);
}
bool MacroConditionEdit::IsRootNode()
bool MacroConditionEdit::IsRootNode() const
{
return _isRoot;
}
void MacroConditionEdit::SetLogicSelection()
{
auto logic = (*_entryData)->GetLogicType();
if (IsRootNode()) {
_logicSelection->setCurrentIndex(static_cast<int>(logic));
} else {
_logicSelection->setCurrentIndex(static_cast<int>(logic) -
logic_root_offset);
}
const auto logic = (*_entryData)->GetLogicType();
_logicSelection->setCurrentIndex(
_logicSelection->findData(static_cast<int>(logic)));
SetEnableAppearance(logic != Logic::Type::NONE);
}
void MacroConditionEdit::SetRootNode(bool root)
@@ -200,27 +174,40 @@ void MacroConditionEdit::SetRootNode(bool root)
_isRoot = root;
const QSignalBlocker blocker(_logicSelection);
_logicSelection->clear();
populateLogicSelection(_logicSelection, root);
Logic::PopulateLogicTypeSelection(_logicSelection, root);
SetLogicSelection();
}
void MacroConditionEdit::UpdateEntryData(const std::string &id)
void MacroConditionEdit::SetupWidgets(bool basicSetup)
{
if (_allWidgetsAreSetup) {
return;
}
const auto id = (*_entryData)->GetId();
_conditionSelection->setCurrentText(obs_module_text(
MacroConditionFactory::GetConditionName(id).c_str()));
auto widget =
MacroConditionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &)));
HeaderInfoChanged(
QString::fromStdString((*_entryData)->GetShortDesc()));
SetLogicSelection();
_section->SetContent(widget, (*_entryData)->GetCollapsed());
_dur->setVisible(MacroConditionFactory::UsesDurationModifier(id));
auto modifier = (*_entryData)->GetDurationModifier();
_dur->SetValue(modifier);
if (basicSetup) {
return;
}
auto widget =
MacroConditionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &)));
_section->SetContent(widget, (*_entryData)->GetCollapsed());
SetFocusPolicyOfWidgets();
_allWidgetsAreSetup = true;
}
void MacroConditionEdit::SetEntryData(std::shared_ptr<MacroCondition> *data)
@@ -301,17 +288,17 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
}
std::string id;
LogicType logic;
Logic::Type logic;
if (idx >= 1) {
id = macro->Conditions().at(idx - 1)->GetId();
if (idx == 1) {
logic = LogicType::OR;
logic = Logic::Type::OR;
} else {
logic = macro->Conditions().at(idx - 1)->GetLogicType();
}
} else {
id = MacroCondition::GetDefaultID();
logic = LogicType::ROOT_NONE;
logic = Logic::Type::ROOT_NONE;
}
OBSDataAutoRelease data;
@@ -324,7 +311,7 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
void AdvSceneSwitcher::AddMacroCondition(Macro *macro, int idx,
const std::string &id,
obs_data_t *data, LogicType logic)
obs_data_t *data, Logic::Type logic)
{
if (idx < 0 || idx > (int)macro->Conditions().size()) {
assert(false);
@@ -389,7 +376,7 @@ void AdvSceneSwitcher::RemoveMacroCondition(int idx)
macro->UpdateConditionIndices();
if (idx == 0 && macro->Conditions().size() > 0) {
auto newRoot = macro->Conditions().at(0);
newRoot->SetLogicType(LogicType::ROOT_NONE);
newRoot->SetLogicType(Logic::Type::ROOT_NONE);
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(0))
->SetRootNode(true);
@@ -540,22 +527,23 @@ void AdvSceneSwitcher::MacroConditionReorder(int to, int from)
auto lock = LockContext();
auto condition = macro->Conditions().at(from);
if (to == 0) {
condition->SetLogicType(LogicType::ROOT_NONE);
condition->SetLogicType(Logic::Type::ROOT_NONE);
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(from))
->SetRootNode(true);
macro->Conditions().at(0)->SetLogicType(LogicType::AND);
macro->Conditions().at(0)->SetLogicType(
Logic::Type::AND);
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(0))
->SetRootNode(false);
}
if (from == 0) {
condition->SetLogicType(LogicType::AND);
condition->SetLogicType(Logic::Type::AND);
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(from))
->SetRootNode(false);
macro->Conditions().at(1)->SetLogicType(
LogicType::ROOT_NONE);
Logic::Type::ROOT_NONE);
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(1))
->SetRootNode(true);

View File

@@ -2,6 +2,7 @@
#include "macro-condition.hpp"
#include "macro-condition-factory.hpp"
#include "filter-combo-box.hpp"
#include "duration-control.hpp"
#include <memory>
@@ -38,9 +39,9 @@ public:
std::shared_ptr<MacroCondition> * = nullptr,
const std::string &id = MacroCondition::GetDefaultID().data(),
bool root = true);
bool IsRootNode();
bool IsRootNode() const;
void SetRootNode(bool);
void UpdateEntryData(const std::string &id);
void SetupWidgets(bool basicSetup = false);
void SetEntryData(std::shared_ptr<MacroCondition> *);
private slots:

View File

@@ -1,7 +1,24 @@
#include "macro-condition-factory.hpp"
#include <mutex>
namespace advss {
namespace {
class MacroConditionUnknown : public MacroCondition {
public:
MacroConditionUnknown(Macro *m) : MacroCondition(m) {}
bool CheckCondition() { return false; }
bool Save(obs_data_t *obj) const { return MacroCondition::Save(obj); };
bool Load(obs_data_t *obj) { return MacroCondition::Load(obj); };
std::string GetId() const { return "unknown"; }
};
} // namespace
static std::recursive_mutex mutex;
std::map<std::string, MacroConditionInfo> &MacroConditionFactory::GetMap()
{
static std::map<std::string, MacroConditionInfo> _methods;
@@ -11,6 +28,7 @@ std::map<std::string, MacroConditionInfo> &MacroConditionFactory::GetMap()
bool MacroConditionFactory::Register(const std::string &id,
MacroConditionInfo info)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it == GetMap().end()) {
GetMap()[id] = info;
return true;
@@ -18,35 +36,60 @@ bool MacroConditionFactory::Register(const std::string &id,
return false;
}
bool MacroConditionFactory::Deregister(const std::string &id)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (GetMap().count(id) == 0) {
return false;
}
GetMap().erase(id);
return true;
}
static std::shared_ptr<MacroCondition> createUnknownCondition(Macro *m)
{
return std::make_shared<MacroConditionUnknown>(m);
}
std::shared_ptr<MacroCondition>
MacroConditionFactory::Create(const std::string &id, Macro *m)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._create(m);
}
return nullptr;
return createUnknownCondition(m);
}
static QWidget *createUnknownConditionWidget()
{
return new QLabel(
obs_module_text("AdvSceneSwitcher.condition.unknown"));
}
QWidget *
MacroConditionFactory::CreateWidget(const std::string &id, QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._createWidget(parent, cond);
}
return nullptr;
return createUnknownConditionWidget();
}
std::string MacroConditionFactory::GetConditionName(const std::string &id)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._name;
}
return "unknown condition";
return obs_module_text("AdvSceneSwitcher.condition.unknown");
}
std::string MacroConditionFactory::GetIdByName(const QString &name)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
for (auto it : GetMap()) {
if (name == obs_module_text(it.second._name.c_str())) {
return it.first;
@@ -57,10 +100,21 @@ std::string MacroConditionFactory::GetIdByName(const QString &name)
bool MacroConditionFactory::UsesDurationModifier(const std::string &id)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._useDurationModifier;
}
return false;
}
bool CanCreateDefaultCondition()
{
const auto condition = MacroConditionFactory::Create(
MacroCondition::GetDefaultID().data(), nullptr);
if (!condition) {
return false;
}
return condition->GetId() == MacroCondition::GetDefaultID().data();
}
} // namespace advss

View File

@@ -6,10 +6,11 @@
namespace advss {
struct MacroConditionInfo {
using CreateCondition = std::shared_ptr<MacroCondition> (*)(Macro *m);
using CreateConditionWidget =
QWidget *(*)(QWidget *parent, std::shared_ptr<MacroCondition>);
CreateCondition _create = nullptr;
std::function<std::shared_ptr<MacroCondition>(Macro *m)> _create =
nullptr;
CreateConditionWidget _createWidget = nullptr;
std::string _name;
bool _useDurationModifier = true;
@@ -19,6 +20,7 @@ class MacroConditionFactory {
public:
MacroConditionFactory() = delete;
EXPORT static bool Register(const std::string &, MacroConditionInfo);
static bool Deregister(const std::string &);
static std::shared_ptr<MacroCondition> Create(const std::string &,
Macro *m);
static QWidget *CreateWidget(const std::string &id, QWidget *parent,
@@ -32,4 +34,6 @@ private:
static std::map<std::string, MacroConditionInfo> &GetMap();
};
bool CanCreateDefaultCondition();
} // namespace advss

View File

@@ -58,7 +58,7 @@ bool MacroConditionMacro::CheckStateCondition()
return false;
}
return macro->Matched();
return macro->ConditionsMatched();
}
bool MacroConditionMacro::CheckMultiStateCondition()
@@ -72,7 +72,7 @@ bool MacroConditionMacro::CheckMultiStateCondition()
if (!macro) {
continue;
}
if (macro->Matched()) {
if (macro->ConditionsMatched()) {
matchedCount++;
}
}

View File

@@ -9,7 +9,6 @@
#include <QPushButton>
#include <QHBoxLayout>
#include <QTimer>
#include <QListWidget>
namespace advss {

View File

@@ -27,6 +27,9 @@ bool MacroConditionQueue::CheckCondition()
return false;
}
SetTempVarValue("size", std::to_string(queue->Size()));
SetTempVarValue("running", queue->IsRunning());
switch (_condition) {
case Condition::STARTED:
return queue->IsRunning();
@@ -63,6 +66,18 @@ std::string MacroConditionQueue::GetShortDesc() const
return GetActionQueueName(_queue);
}
void MacroConditionQueue::SetupTempVars()
{
MacroCondition::SetupTempVars();
AddTempvar("size",
obs_module_text("AdvSceneSwitcher.tempVar.queue.size"));
AddTempvar(
"running",
obs_module_text("AdvSceneSwitcher.tempVar.queue.running"),
obs_module_text(
"AdvSceneSwitcher.tempVar.queue.running.description"));
}
static inline void populateQueueTypeSelection(QComboBox *list)
{
for (const auto &[_, name] : conditionTypes) {
@@ -141,6 +156,7 @@ void MacroConditionQueueEdit::UpdateEntryData()
}
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
_queues->SetActionQueue(_entryData->_queue);
_size->SetValue(_entryData->_size);
SetWidgetVisibility();
}

View File

@@ -32,6 +32,8 @@ public:
IntVariable _size = 1;
private:
void SetupTempVars();
static bool _registered;
static const std::string id;
};

View File

@@ -0,0 +1,108 @@
#include "macro-condition-script.hpp"
#include "layout-helpers.hpp"
#include "macro-helpers.hpp"
#include "sync-helpers.hpp"
namespace advss {
MacroConditionScript::MacroConditionScript(
Macro *m, const std::string &id, const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignalName,
const std::string &completionSignalName,
const std::string &newInstanceSignalName,
const std::string &deletedInstanceSignalName)
: MacroCondition(m),
MacroSegmentScript(defaultSettings, propertiesSignalName,
triggerSignalName, completionSignalName,
newInstanceSignalName, deletedInstanceSignalName),
_id(id)
{
}
MacroConditionScript::MacroConditionScript(
const advss::MacroConditionScript &other)
: MacroCondition(other.GetMacro()),
MacroSegmentScript(other),
_id(other._id)
{
}
bool MacroConditionScript::CheckCondition()
{
if (!ScriptHandler::ConditionIdIsValid(_id)) {
blog(LOG_WARNING, "skipping unknown script condition \"%s\"",
_id.c_str());
return false;
}
return SendTriggerSignal();
}
bool MacroConditionScript::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
MacroSegmentScript::Save(obj);
return true;
}
bool MacroConditionScript::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
MacroSegmentScript::Load(obj);
return true;
}
void MacroConditionScript::WaitForCompletion() const
{
using namespace std::chrono_literals;
auto start = std::chrono::high_resolution_clock::now();
auto timePassed = std::chrono::duration_cast<std::chrono::milliseconds>(
start - start);
const auto timeoutMs = GetTimeoutSeconds() * 1000.0;
while (!TriggerIsCompleted()) {
if (MacroWaitShouldAbort() || MacroIsStopped(GetMacro())) {
break;
}
if ((double)timePassed.count() > timeoutMs) {
blog(LOG_INFO, "script condition timeout (%s)",
_id.c_str());
break;
}
std::this_thread::sleep_for(10ms);
const auto now = std::chrono::high_resolution_clock::now();
timePassed =
std::chrono::duration_cast<std::chrono::milliseconds>(
now - start);
}
}
void MacroConditionScript::RegisterTempVarHelper(const std::string &variableId,
const std::string &name,
const std::string &helpText)
{
AddTempvar(variableId, name, helpText);
}
void MacroConditionScript::DeregisterAllTempVarsHelper()
{
MacroSegment::SetupTempVars();
}
void MacroConditionScript::SetTempVarValueHelper(const std::string &variableId,
const std::string &value)
{
MacroCondition::SetTempVarValue(variableId, value);
}
void MacroConditionScript::SetupTempVars()
{
// This just exists so MacroSegment::SetupTempVars() is not called.
// We want the ScriptHandler to handle the registration and clearing of
// the temp vars.
}
} // namespace advss

View File

@@ -0,0 +1,36 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "macro-script-handler.hpp"
#include "macro-segment-script.hpp"
namespace advss {
class MacroConditionScript : public MacroCondition, public MacroSegmentScript {
public:
MacroConditionScript(Macro *m, const std::string &id,
const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignalName,
const std::string &completionSignalName,
const std::string &newInstanceSignalName,
const std::string &deletedInstanceSignalName);
MacroConditionScript(const advss::MacroConditionScript &);
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetId() const { return _id; };
private:
void WaitForCompletion() const;
void RegisterTempVarHelper(const std::string &variableId,
const std::string &name,
const std::string &helpText);
void DeregisterAllTempVarsHelper();
void SetTempVarValueHelper(const std::string &variableId,
const std::string &value);
void SetupTempVars();
std::string _id = "";
};
} // namespace advss

View File

@@ -2,79 +2,6 @@
namespace advss {
const std::map<LogicType, LogicTypeInfo> MacroCondition::logicTypes = {
{LogicType::NONE, {"AdvSceneSwitcher.logic.none"}},
{LogicType::AND, {"AdvSceneSwitcher.logic.and"}},
{LogicType::OR, {"AdvSceneSwitcher.logic.or"}},
{LogicType::AND_NOT, {"AdvSceneSwitcher.logic.andNot"}},
{LogicType::OR_NOT, {"AdvSceneSwitcher.logic.orNot"}},
{LogicType::ROOT_NONE, {"AdvSceneSwitcher.logic.rootNone"}},
{LogicType::ROOT_NOT, {"AdvSceneSwitcher.logic.not"}},
};
void DurationModifier::Save(obs_data_t *obj, const char *condName,
const char *duration) const
{
obs_data_set_int(obj, condName, static_cast<int>(_type));
_dur.Save(obj, duration);
}
void DurationModifier::Load(obs_data_t *obj, const char *condName,
const char *duration)
{
// For backwards compatibility check if duration value exist without
// time constraint condition - if so assume DurationCondition::MORE
if (!obs_data_has_user_value(obj, condName) &&
obs_data_has_user_value(obj, duration)) {
obs_data_set_int(obj, condName, static_cast<int>(Type::MORE));
}
_type = static_cast<Type>(obs_data_get_int(obj, condName));
_dur.Load(obj, duration);
// TODO: remove this fallback
if (obs_data_has_user_value(obj, "displayUnit")) {
_dur.SetUnit(static_cast<Duration::Unit>(
obs_data_get_int(obj, "displayUnit")));
}
}
bool DurationModifier::DurationReached()
{
switch (_type) {
case DurationModifier::Type::NONE:
return true;
break;
case DurationModifier::Type::MORE:
return _dur.DurationReached();
break;
case DurationModifier::Type::EQUAL:
if (_dur.DurationReached() && !_timeReached) {
_timeReached = true;
return true;
}
break;
case DurationModifier::Type::LESS:
return !_dur.DurationReached();
break;
case DurationModifier::Type::WITHIN:
if (_dur.IsReset()) {
return false;
}
return !_dur.DurationReached();
break;
default:
break;
}
return false;
}
void DurationModifier::Reset()
{
_timeReached = false;
_dur.Reset();
}
MacroCondition::MacroCondition(Macro *m, bool supportsVariableValue)
: MacroSegment(m, supportsVariableValue)
{
@@ -84,73 +11,64 @@ bool MacroCondition::Save(obs_data_t *obj) const
{
MacroSegment::Save(obj);
obs_data_set_string(obj, "id", GetId().c_str());
obs_data_set_int(obj, "logic", static_cast<int>(_logic));
// To avoid conflicts with conditions which also use the Duration class
// save the duration modifier in a separate obj
auto durObj = obs_data_create();
_duration.Save(durObj);
obs_data_set_obj(obj, "durationModifier", durObj);
obs_data_release(durObj);
_logic.Save(obj, "logic");
_durationModifier.Save(obj);
return true;
}
bool MacroCondition::Load(obs_data_t *obj)
{
MacroSegment::Load(obj);
_logic = static_cast<LogicType>(obs_data_get_int(obj, "logic"));
if (obs_data_has_user_value(obj, "durationModifier")) {
auto durObj = obs_data_get_obj(obj, "durationModifier");
_duration.Load(durObj);
obs_data_release(durObj);
} else {
// For backwards compatibility
_duration.Load(obj);
}
_logic.Load(obj, "logic");
_durationModifier.Load(obj);
return true;
}
void MacroCondition::ValidateLogicSelection(bool isRootCondition,
const char *context)
{
if (_logic.IsValidSelection(isRootCondition)) {
return;
}
if (_logic.IsRootType()) {
_logic.SetType(Logic::Type::ROOT_NONE);
blog(LOG_WARNING,
"setting invalid logic selection to 'if' for macro %s",
context);
return;
}
_logic.SetType(Logic::Type::NONE);
blog(LOG_WARNING,
"setting invalid logic selection to 'ignore' for macro %s",
context);
}
void MacroCondition::ResetDuration()
{
_duration.Reset();
_durationModifier.ResetDuration();
}
void MacroCondition::CheckDurationModifier(bool &val)
bool MacroCondition::CheckDurationModifier(bool conditionValue)
{
if (_duration.GetType() != DurationModifier::Type::WITHIN && !val) {
_duration.Reset();
}
if (_duration.GetType() == DurationModifier::Type::WITHIN && val) {
_duration.Reset();
}
switch (_duration.GetType()) {
case DurationModifier::Type::NONE:
case DurationModifier::Type::MORE:
case DurationModifier::Type::EQUAL:
case DurationModifier::Type::LESS:
val = val && _duration.DurationReached();
return;
case DurationModifier::Type::WITHIN:
if (val) {
_duration.SetTimeRemaining(
_duration.GetDuration().Seconds());
}
val = val || _duration.DurationReached();
break;
default:
break;
}
return _durationModifier.CheckConditionWithDurationModifier(
conditionValue);
}
DurationModifier MacroCondition::GetDurationModifier() const
{
return _durationModifier;
}
void MacroCondition::SetDurationModifier(DurationModifier::Type m)
{
_duration.SetModifier(m);
_durationModifier.SetModifier(m);
}
void MacroCondition::SetDuration(const Duration &duration)
{
_duration.SetValue(duration);
_durationModifier.SetDuration(duration);
}
std::string_view MacroCondition::GetDefaultID()

View File

@@ -1,63 +1,11 @@
#pragma once
#include "macro-segment.hpp"
#include "condition-logic.hpp"
#include "duration-modifier.hpp"
#include "macro-ref.hpp"
#include <duration-control.hpp>
namespace advss {
constexpr auto logic_root_offset = 100;
enum class LogicType {
ROOT_NONE = 0,
ROOT_NOT,
ROOT_LAST,
// leave some space for potential expansion
NONE = 100,
AND,
OR,
AND_NOT,
OR_NOT,
LAST,
};
static inline bool isRootLogicType(LogicType l)
{
return static_cast<int>(l) < logic_root_offset;
}
struct LogicTypeInfo {
std::string _name;
};
class DurationModifier {
public:
enum class Type {
NONE,
MORE,
EQUAL,
LESS,
WITHIN, // Condition will remain true for a set a amount of time
// regardless of current state
};
void Save(obs_data_t *obj, const char *condName = "time_constraint",
const char *duration = "seconds") const;
void Load(obs_data_t *obj, const char *condName = "time_constraint",
const char *duration = "seconds");
void SetModifier(Type cond) { _type = cond; }
void SetTimeRemaining(const double &val) { _dur.SetTimeRemaining(val); }
void SetValue(const Duration &value) { _dur = value; }
Type GetType() { return _type; }
Duration GetDuration() { return _dur; }
bool DurationReached();
void Reset();
private:
Type _type = Type::NONE;
Duration _dur;
bool _timeReached = false;
};
class EXPORT MacroCondition : public MacroSegment {
public:
MacroCondition(Macro *m, bool supportsVariableValue = false);
@@ -65,20 +13,24 @@ public:
virtual bool CheckCondition() = 0;
virtual bool Save(obs_data_t *obj) const = 0;
virtual bool Load(obs_data_t *obj) = 0;
LogicType GetLogicType() { return _logic; }
void SetLogicType(LogicType logic) { _logic = logic; }
static const std::map<LogicType, LogicTypeInfo> logicTypes;
void ResetDuration();
void CheckDurationModifier(bool &val);
DurationModifier GetDurationModifier() { return _duration; }
Logic::Type GetLogicType() const { return _logic.GetType(); }
void SetLogicType(const Logic::Type &logic) { _logic.SetType(logic); }
void ValidateLogicSelection(bool isRootCondition, const char *context);
DurationModifier GetDurationModifier() const;
void SetDurationModifier(DurationModifier::Type m);
void SetDuration(const Duration &duration);
void ResetDuration();
bool CheckDurationModifier(bool conditionValue);
static std::string_view GetDefaultID();
private:
LogicType _logic = LogicType::ROOT_NONE;
DurationModifier _duration;
Logic _logic = Logic(Logic::Type::ROOT_NONE);
DurationModifier _durationModifier;
};
class EXPORT MacroRefCondition : virtual public MacroCondition {

View File

@@ -6,14 +6,14 @@
namespace advss {
MacroDock::MacroDock(std::weak_ptr<Macro> m, QWidget *parent,
MacroDock::MacroDock(std::weak_ptr<Macro> m,
const StringVariable &runButtonText,
const StringVariable &pauseButtonText,
const StringVariable &unpauseButtonText,
const StringVariable &conditionsTrueText,
const StringVariable &conditionsFalseText,
bool enableHighlight)
: OBSDock(parent),
: QFrame(),
_runButtonText(runButtonText),
_pauseButtonText(pauseButtonText),
_unpauseButtonText(unpauseButtonText),
@@ -25,19 +25,16 @@ MacroDock::MacroDock(std::weak_ptr<Macro> m, QWidget *parent,
_statusText(new QLabel(conditionsFalseText.c_str())),
_macro(m)
{
setFrameShape(QFrame::StyledPanel);
setFrameShadow(QFrame::Sunken);
auto macro = _macro.lock();
if (macro) {
setWindowTitle(QString::fromStdString(macro->Name()));
_run->setVisible(macro->DockHasRunButton());
_pauseToggle->setVisible(macro->DockHasPauseButton());
_statusText->setVisible(macro->DockHasStatusLabel());
} else {
setWindowTitle("<deleted macro>");
}
setFeatures(DockWidgetClosable | DockWidgetMovable |
DockWidgetFloatable);
QWidget::connect(_run, SIGNAL(clicked()), this, SLOT(RunClicked()));
QWidget::connect(_pauseToggle, SIGNAL(clicked()), this,
SLOT(PauseToggleClicked()));
@@ -52,21 +49,7 @@ MacroDock::MacroDock(std::weak_ptr<Macro> m, QWidget *parent,
QWidget::connect(&_timer, SIGNAL(timeout()), this, SLOT(Highlight()));
_timer.start(500);
// QFrame wrapper is necessary to avoid dock being partially
// transparent
QFrame *wrapper = new QFrame;
wrapper->setFrameShape(QFrame::StyledPanel);
wrapper->setFrameShadow(QFrame::Sunken);
wrapper->setLayout(layout);
setWidget(wrapper);
setFloating(true);
hide();
}
void MacroDock::SetName(const QString &name)
{
setWindowTitle(name);
setLayout(layout);
}
void MacroDock::ShowRunButton(bool value)
@@ -155,8 +138,9 @@ void MacroDock::UpdateText()
_pauseToggle->setText(macro->Paused() ? _unpauseButtonText.c_str()
: _pauseButtonText.c_str());
_statusText->setText(macro->Matched() ? _conditionsTrueText.c_str()
: _conditionsFalseText.c_str());
_statusText->setText(macro->ConditionsMatched()
? _conditionsTrueText.c_str()
: _conditionsFalseText.c_str());
}
void MacroDock::Highlight()

View File

@@ -1,10 +1,9 @@
#pragma once
#include "obs-dock.hpp"
#include "variable-string.hpp"
#include <QLabel>
#include <QPushButton>
#include <QTimer>
#include <QLabel>
#include <memory>
#include <chrono>
@@ -12,18 +11,16 @@ namespace advss {
class Macro;
class MacroDock : public OBSDock {
class MacroDock : public QFrame {
Q_OBJECT
public:
MacroDock(std::weak_ptr<Macro>, QWidget *parent,
const StringVariable &runButtonText,
MacroDock(std::weak_ptr<Macro>, const StringVariable &runButtonText,
const StringVariable &pauseButtonText,
const StringVariable &unpauseButtonText,
const StringVariable &conditionsTrueText,
const StringVariable &conditionsFalseText,
bool enableHighlight);
void SetName(const QString &);
void ShowRunButton(bool);
void SetRunButtonText(const StringVariable &);
void ShowPauseButton(bool);

View File

@@ -67,9 +67,7 @@ void SetMacroSwitchedScene(bool value)
{
static bool setupDone = false;
if (!setupDone) {
// Will always be called with switcher lock already held
AddIntervalResetStep([]() { macroSceneSwitched = false; },
false);
AddIntervalResetStep([]() { macroSceneSwitched = false; });
setupDone = true;
}
macroSceneSwitched = value;
@@ -85,9 +83,11 @@ std::string GetMacroName(Macro *macro)
return macro ? macro->Name() : "";
}
int64_t MillisecondsSinceMacroConditionCheck(Macro *macro)
std::chrono::high_resolution_clock::time_point
LastMacroConditionCheckTime(Macro *macro)
{
return macro ? macro->MsSinceLastCheck() : 0;
return macro ? macro->LastConditionCheckTime()
: std::chrono::high_resolution_clock::time_point{};
}
bool MacroIsStopped(Macro *macro)
@@ -107,6 +107,14 @@ bool MacroWasPausedSince(
return macro ? macro->WasPausedSince(time) : false;
}
bool MacroWasCheckedSinceLastStart(Macro *macro)
{
if (!macro) {
return false;
}
return macro->LastConditionCheckTime().time_since_epoch().count() != 0;
}
void AddMacroHelperThread(Macro *macro, std::thread &&newThread)
{
if (!macro) {

View File

@@ -43,13 +43,15 @@ EXPORT bool MacroSwitchedScene();
EXPORT std::string GetMacroName(Macro *);
EXPORT int64_t MillisecondsSinceMacroConditionCheck(Macro *);
EXPORT std::chrono::high_resolution_clock::time_point
LastMacroConditionCheckTime(Macro *);
EXPORT bool MacroIsStopped(Macro *);
EXPORT bool MacroIsPaused(Macro *);
EXPORT bool
MacroWasPausedSince(Macro *,
const std::chrono::high_resolution_clock::time_point &);
EXPORT bool MacroWasCheckedSinceLastStart(Macro *);
EXPORT void AddMacroHelperThread(Macro *, std::thread &&);

View File

@@ -49,15 +49,9 @@ void MacroInputVariables::Load(obs_data_t *obj)
void MacroInputVariables::SetValues(const StringList &values)
{
if (_inputVariables.size() != (size_t)values.size()) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
blog(LOG_WARNING,
"%s - sizes do not match (variables: %ld, values %d)",
__func__, _inputVariables.size(), values.size());
#else
blog(LOG_WARNING,
"%s - sizes do not match (variables: %ld, values %lld)",
__func__, _inputVariables.size(), values.size());
#endif
"%s - sizes do not match (variables: %d, values %d)",
__func__, (int)_inputVariables.size(), (int)values.size());
}
for (size_t index = 0;
@@ -98,7 +92,7 @@ void MacroInputSelection::SetInputs(const MacroInputVariables &inputs)
void MacroInputSelection::Add()
{
std::string varName;
bool accepted = VariableSelectionDialog::AskForVariable(this, varName);
bool accepted = VariableSelectionDialog::AskForVariable(varName);
if (!accepted || varName.empty()) {
return;
@@ -161,7 +155,7 @@ void MacroInputSelection::Down()
void MacroInputSelection::Clicked(QListWidgetItem *item)
{
std::string varName;
bool accepted = VariableSelectionDialog::AskForVariable(this, varName);
bool accepted = VariableSelectionDialog::AskForVariable(varName);
if (!accepted || varName.empty()) {
return;
@@ -187,15 +181,7 @@ void MacroInputEdit::SetInputVariablesAndValues(
_variables = inputs;
_values = values;
if ((size_t)_values.size() < _variables._inputVariables.size()) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
for (int i = 0;
_variables._inputVariables.size() - (size_t)_values.size();
i++) {
_values.push_back({});
}
#else
_values.resize(_variables._inputVariables.size());
#endif
}
SetupWidgets();
}
@@ -205,7 +191,7 @@ bool MacroInputEdit::HasInputsToSet() const
return !_variables._inputVariables.empty();
}
void HighligthMacroSettingsButton(bool enable);
void HighlightMacroSettingsButton(bool enable);
void MacroInputEdit::SetupWidgets()
{

View File

@@ -40,9 +40,12 @@ void MacroList::SetContent(const std::vector<MacroRef> &macros)
UpdateListSize();
}
void MacroList::AddControl(QWidget *widget)
QAction *MacroList::AddControl(QWidget *widget, bool addSeperator)
{
_controlsLayout->insertWidget(_controlsLayout->count() - 1, widget);
if (addSeperator) {
_controls->addSeparator();
}
return _controls->addWidget(widget);
}
int MacroList::CurrentRow()
@@ -76,7 +79,7 @@ void MacroList::MacroRemove(const QString &name)
void MacroList::Add()
{
std::string macroName;
bool accepted = MacroSelectionDialog::AskForMacro(this, macroName);
bool accepted = MacroSelectionDialog::AskForMacro(macroName);
if (!accepted || macroName.empty()) {
return;
@@ -129,7 +132,7 @@ void MacroList::Down()
void MacroList::Clicked(QListWidgetItem *item)
{
std::string macroName;
bool accepted = MacroSelectionDialog::AskForMacro(this, macroName);
bool accepted = MacroSelectionDialog::AskForMacro(macroName);
if (!accepted || macroName.empty()) {
return;

View File

@@ -9,7 +9,7 @@ class ADVSS_EXPORT MacroList final : public ListEditor {
public:
MacroList(QWidget *parent, bool allowDuplicates, bool reorder);
void SetContent(const std::vector<MacroRef> &);
void AddControl(QWidget *);
QAction *AddControl(QWidget *, bool addSeperator = true);
int CurrentRow();
private slots:

View File

@@ -0,0 +1,667 @@
#include "macro-script-handler.hpp"
#include "macro-action-script.hpp"
#include "macro-condition-script.hpp"
#include "plugin-state-helpers.hpp"
#include "log-helper.hpp"
#include "variable.hpp"
#include <obs-properties.h>
namespace advss {
std::mutex ScriptHandler::_mutex = {};
std::unordered_map<std::string, ScriptSegmentType> ScriptHandler::_actions = {};
std::unordered_map<std::string, ScriptSegmentType> ScriptHandler::_conditions =
{};
/* Procedure handler helpers */
#define RETURN_STATUS(status) \
{ \
calldata_set_bool(data, "success", status); \
return; \
}
#define RETURN_SUCCESS() RETURN_STATUS(true);
#define RETURN_FAILURE() RETURN_STATUS(false);
static constexpr std::string_view nameParam = "name";
static constexpr std::string_view defaultSettingsParam = "default_settings";
static constexpr std::string_view propertiesSignalParam =
"properties_signal_name";
static constexpr std::string_view triggerSignalParam = "trigger_signal_name";
static constexpr std::string_view newInstanceSignalNameParam =
"new_instance_signal_name";
static constexpr std::string_view deletedInstanceSignalNameParam =
"deleted_instance_signal_name";
static std::string getRegisterScriptSegmentDeclString(const char *funcName)
{
return std::string("bool ") + funcName + "(in string " +
nameParam.data() + ", in ptr " + defaultSettingsParam.data() +
", out string " + propertiesSignalParam.data() +
", out string " + triggerSignalParam.data() + ")";
}
static std::string getDeregisterScriptSegmentDeclString(const char *funcName)
{
return std::string("bool ") + funcName + "(in string " +
nameParam.data() + ")";
}
/* Script actions */
static constexpr std::string_view registerActionFuncName =
"advss_register_script_action";
static constexpr std::string_view deregisterActionFuncName =
"advss_deregister_script_action";
static const std::string registerScriptActionDeclString =
getRegisterScriptSegmentDeclString(registerActionFuncName.data());
static const std::string deregisterScriptActionDeclString =
getDeregisterScriptSegmentDeclString(deregisterActionFuncName.data());
/* Script conditions */
static constexpr std::string_view registerConditionFuncName =
"advss_register_script_condition";
static constexpr std::string_view deregisterConditionFuncName =
"advss_deregister_script_condition";
static const std::string registerScriptConditionDeclString =
getRegisterScriptSegmentDeclString(registerConditionFuncName.data());
static const std::string deregisterScriptConditionDeclString =
getDeregisterScriptSegmentDeclString(
deregisterConditionFuncName.data());
/* Script variables */
static constexpr std::string_view valueParam = "value";
static constexpr std::string_view tempVarIdParam = "temp_var_id";
static constexpr std::string_view tempVarNameParam = "temp_var_name";
static constexpr std::string_view tempVarHelpParam = "temp_var_help";
static constexpr std::string_view getVariableValueFuncName =
"advss_get_variable_value";
static constexpr std::string_view setVariableValueFuncName =
"advss_set_variable_value";
static constexpr std::string_view registerTempVarFuncName =
"advss_register_temp_var";
static constexpr std::string_view deregisterAllTempVarsFuncName =
"advss_deregister_temp_vars";
static constexpr std::string_view setTempVarValueFuncName =
"advss_set_temp_var_value";
static const std::string getVariableValueDeclString =
std::string("bool ") + getVariableValueFuncName.data() + "(in string " +
nameParam.data() + ", out string " + valueParam.data() + ")";
static const std::string setVariableValueDeclString =
std::string("bool ") + setVariableValueFuncName.data() + "(in string " +
nameParam.data() + ", in string " + valueParam.data() + ")";
static const std::string registerTempVarDeclString =
std::string("bool ") + registerTempVarFuncName.data() + "(in string " +
tempVarIdParam.data() + ", in string " + tempVarNameParam.data() +
", in string " + tempVarHelpParam.data() + ", in int " +
GetInstanceIdParamName().data() + ")";
static const std::string deregisterAllTempVarsDeclString =
std::string("bool ") + deregisterAllTempVarsFuncName.data() +
"(in int " + GetInstanceIdParamName().data() + ")";
static const std::string setTempVarValueDeclString =
std::string("bool ") + setTempVarValueFuncName.data() + "(in string " +
tempVarIdParam.data() + ", in string " + valueParam.data() +
", in int " + GetInstanceIdParamName().data() + ")";
/* Plugin status */
static constexpr std::string_view stopSignalName = "advss_plugin_stopped";
static constexpr std::string_view startSignalName = "advss_plugin_started";
static constexpr std::string_view getRunningStatusFuncName =
"advss_plugin_running";
static constexpr std::string_view resetIntervalSignalName =
"advss_interval_reset";
static const std::string stopSignalDeclString =
std::string("void ") + stopSignalName.data() + "()";
static const std::string startSignalDeclString =
std::string("void ") + startSignalName.data() + "()";
static const std::string resetIntervalDeclString =
std::string("void ") + resetIntervalSignalName.data() + "()";
static const std::string getRunningStatusDeclString =
std::string("bool ") + getRunningStatusFuncName.data() + "()";
static bool setup();
static bool setupDone = setup();
static bool setup()
{
proc_handler_t *ph = obs_get_proc_handler();
assert(ph != NULL);
proc_handler_add(ph, registerScriptActionDeclString.c_str(),
&ScriptHandler::RegisterScriptAction, nullptr);
proc_handler_add(ph, deregisterScriptActionDeclString.c_str(),
&ScriptHandler::DeregisterScriptAction, nullptr);
proc_handler_add(ph, registerScriptConditionDeclString.c_str(),
&ScriptHandler::RegisterScriptCondition, nullptr);
proc_handler_add(ph, deregisterScriptConditionDeclString.c_str(),
&ScriptHandler::DeregisterScriptCondition, nullptr);
proc_handler_add(ph, getVariableValueDeclString.c_str(),
&ScriptHandler::GetVariableValue, nullptr);
proc_handler_add(ph, setVariableValueDeclString.c_str(),
&ScriptHandler::SetVariableValue, nullptr);
proc_handler_add(ph, registerTempVarDeclString.c_str(),
&ScriptHandler::RegisterTempVar, nullptr);
proc_handler_add(ph, deregisterAllTempVarsDeclString.c_str(),
&ScriptHandler::DeregisterAllTempVars, nullptr);
proc_handler_add(ph, setTempVarValueDeclString.c_str(),
&ScriptHandler::SetTempVarValue, nullptr);
proc_handler_add(ph, getRunningStatusDeclString.c_str(),
&ScriptHandler::GetRunningStatus, nullptr);
auto sh = obs_get_signal_handler();
signal_handler_add(sh, stopSignalDeclString.c_str());
signal_handler_add(sh, startSignalDeclString.c_str());
signal_handler_add(sh, resetIntervalDeclString.c_str());
static constexpr auto triggerSignal = [](const std::string_view &name) {
auto sh = obs_get_signal_handler();
struct calldata data;
calldata_init(&data);
signal_handler_signal(sh, name.data(), &data);
calldata_free(&data);
};
AddIntervalResetStep([]() { triggerSignal(resetIntervalSignalName); });
AddStartStep([]() { triggerSignal(startSignalName); });
AddStopStep([]() { triggerSignal(stopSignalName); });
return true;
}
static void replaceProblematicChars(std::string &string)
{
std::transform(string.begin(), string.end(), string.begin(), [](char c) {
if (std::isspace(static_cast<unsigned char>(c)) || c == '(' ||
c == ')' || c == '[' || c == ']' || c == '{' || c == '}') {
return '_';
}
return c;
});
}
static std::string nameToScriptID(const std::string &name)
{
return std::string("script_") + name;
}
static std::string getTriggerSignal(const std::string &name,
const bool isAction)
{
std::string signal = name;
replaceProblematicChars(signal);
signal += "_run";
signal += isAction ? "_action" : "_condition";
return signal;
}
static std::string getCompletionSignal(const std::string &name,
const bool isAction)
{
auto signal = getTriggerSignal(name, isAction);
signal += "_complete";
return signal;
}
static std::string getPropertiesSignal(const std::string &name,
const bool isAction)
{
std::string signal = name;
replaceProblematicChars(signal);
signal += isAction ? "_action" : "_condition";
signal += "_get_properties";
return signal;
}
static std::string getNewInstanceSignal(const std::string &name,
const bool isAction)
{
std::string signal = name;
replaceProblematicChars(signal);
signal += isAction ? "_action" : "_condition";
signal += "_new_instance";
return signal;
}
static std::string getDeletedInstanceSignal(const std::string &name,
const bool isAction)
{
std::string signal = name;
replaceProblematicChars(signal);
signal += isAction ? "_action" : "_condition";
signal += "_deleted_instance";
return signal;
}
void ScriptHandler::RegisterScriptAction(void *, calldata_t *data)
{
const char *actionName;
if (!calldata_get_string(data, nameParam.data(), &actionName) ||
strlen(actionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerScriptActionDeclString.data(), nameParam.data());
RETURN_FAILURE();
}
obs_data_t *defaultSettingsPtr = nullptr;
if (!calldata_get_ptr(data, defaultSettingsParam.data(),
&defaultSettingsPtr)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerActionFuncName.data(),
defaultSettingsParam.data());
RETURN_FAILURE();
}
std::lock_guard<std::mutex> lock(_mutex);
OBSData defaultSettings(defaultSettingsPtr);
obs_data_release(defaultSettingsPtr);
if (_actions.count(actionName) > 0) {
blog(LOG_WARNING, "[%s] failed! Action \"%s\" already exists!",
registerActionFuncName.data(), actionName);
RETURN_FAILURE();
}
const std::string id = nameToScriptID(actionName);
auto triggerSignalName = getTriggerSignal(actionName, true);
auto completionSignalName = getCompletionSignal(actionName, true);
auto propertiesSignalName = getPropertiesSignal(actionName, true);
auto newInstanceSignalName = getNewInstanceSignal(actionName, true);
auto deletedInstanceSignalName =
getDeletedInstanceSignal(actionName, true);
const auto createScriptAction =
[id, defaultSettings, propertiesSignalName, triggerSignalName,
completionSignalName, newInstanceSignalName,
deletedInstanceSignalName](
Macro *m) -> std::shared_ptr<MacroAction> {
return std::make_shared<MacroActionScript>(
m, id, defaultSettings, propertiesSignalName,
triggerSignalName, completionSignalName,
newInstanceSignalName, deletedInstanceSignalName);
};
if (!MacroActionFactory::Register(id, {createScriptAction,
MacroSegmentScriptEdit::Create,
actionName})) {
blog(LOG_WARNING,
"[%s] failed! Action id \"%s\" already exists!",
registerActionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
blog(LOG_INFO, "[%s] successful for \"%s\"",
registerActionFuncName.data(), actionName);
calldata_set_string(data, triggerSignalParam.data(),
triggerSignalName.c_str());
calldata_set_string(data, propertiesSignalParam.data(),
propertiesSignalName.c_str());
calldata_set_string(data, newInstanceSignalNameParam.data(),
newInstanceSignalName.c_str());
calldata_set_string(data, deletedInstanceSignalNameParam.data(),
deletedInstanceSignalName.c_str());
_actions.emplace(id, ScriptSegmentType(id, propertiesSignalName,
triggerSignalName,
completionSignalName,
newInstanceSignalName,
deletedInstanceSignalName));
RETURN_SUCCESS();
}
void ScriptHandler::DeregisterScriptAction(void *, calldata_t *data)
{
const char *actionName;
if (!calldata_get_string(data, nameParam.data(), &actionName) ||
strlen(actionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
deregisterActionFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
const std::string id = nameToScriptID(actionName);
std::lock_guard<std::mutex> lock(_mutex);
if (_actions.count(id) == 0) {
blog(LOG_WARNING,
"[%s] failed! Action \"%s\" was never registered!",
deregisterActionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
if (!MacroActionFactory::Deregister(id)) {
blog(LOG_WARNING,
"[%s] failed! Action id \"%s\" does not exist!",
deregisterActionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
auto it = _actions.find(id);
if (it != _actions.end()) {
_actions.erase(it);
}
RETURN_SUCCESS();
}
void ScriptHandler::RegisterScriptCondition(void *, calldata_t *data)
{
const char *conditionName;
if (!calldata_get_string(data, nameParam.data(), &conditionName) ||
strlen(conditionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerScriptConditionDeclString.data(),
nameParam.data());
RETURN_FAILURE();
}
obs_data_t *defaultSettingsPtr = nullptr;
if (!calldata_get_ptr(data, defaultSettingsParam.data(),
&defaultSettingsPtr)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerScriptConditionDeclString.data(),
defaultSettingsParam.data());
RETURN_FAILURE();
}
std::lock_guard<std::mutex> lock(_mutex);
OBSData defaultSettings(defaultSettingsPtr);
obs_data_release(defaultSettingsPtr);
if (_conditions.count(conditionName) > 0) {
blog(LOG_WARNING,
"[%s] failed! Condition \"%s\" already exists!",
registerConditionFuncName.data(), conditionName);
RETURN_FAILURE();
}
const std::string id = nameToScriptID(conditionName);
auto triggerSignalName = getTriggerSignal(conditionName, false);
auto completionSignalName = getCompletionSignal(conditionName, false);
auto propertiesSignalName = getPropertiesSignal(conditionName, false);
auto newInstanceSignalName = getNewInstanceSignal(conditionName, false);
auto deletedInstanceSignalName =
getDeletedInstanceSignal(conditionName, false);
const auto createScriptCondition =
[id, defaultSettings, propertiesSignalName, triggerSignalName,
completionSignalName, newInstanceSignalName,
deletedInstanceSignalName](
Macro *m) -> std::shared_ptr<MacroCondition> {
return std::make_shared<MacroConditionScript>(
m, id, defaultSettings, propertiesSignalName,
triggerSignalName, completionSignalName,
newInstanceSignalName, deletedInstanceSignalName);
};
if (!MacroConditionFactory::Register(
id, {createScriptCondition, MacroSegmentScriptEdit::Create,
conditionName})) {
blog(LOG_WARNING,
"[%s] failed! Condition id \"%s\" already exists!",
registerConditionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
blog(LOG_INFO, "[%s] successful for \"%s\"",
registerConditionFuncName.data(), conditionName);
calldata_set_string(data, triggerSignalParam.data(),
triggerSignalName.c_str());
calldata_set_string(data, propertiesSignalParam.data(),
propertiesSignalName.c_str());
calldata_set_string(data, newInstanceSignalNameParam.data(),
newInstanceSignalName.c_str());
calldata_set_string(data, deletedInstanceSignalNameParam.data(),
deletedInstanceSignalName.c_str());
_conditions.emplace(id, ScriptSegmentType(id, propertiesSignalName,
triggerSignalName,
completionSignalName,
newInstanceSignalName,
deletedInstanceSignalName));
RETURN_SUCCESS();
}
void ScriptHandler::DeregisterScriptCondition(void *, calldata_t *data)
{
const char *conditionName;
if (!calldata_get_string(data, nameParam.data(), &conditionName) ||
strlen(conditionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
deregisterConditionFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
const std::string id = nameToScriptID(conditionName);
std::lock_guard<std::mutex> lock(_mutex);
if (_conditions.count(id) == 0) {
blog(LOG_WARNING,
"[%s] failed! Condition \"%s\" was never registered!",
deregisterConditionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
if (!MacroConditionFactory::Deregister(id)) {
blog(LOG_WARNING,
"[%s] failed! Condition id \"%s\" does not exist!",
deregisterConditionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
auto it = _conditions.find(id);
if (it != _conditions.end()) {
_conditions.erase(it);
}
RETURN_SUCCESS();
}
void ScriptHandler::GetVariableValue(void *, calldata_t *data)
{
const char *variableName;
if (!calldata_get_string(data, nameParam.data(), &variableName) ||
strlen(variableName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
getVariableValueFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
auto weakVariable = GetWeakVariableByName(variableName);
auto variable = weakVariable.lock();
if (!variable) {
blog(LOG_WARNING,
"[%s] failed! \"%s\" variable does not exist!",
getVariableValueFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
calldata_set_string(data, valueParam.data(), variable->Value().c_str());
RETURN_SUCCESS();
}
void ScriptHandler::SetVariableValue(void *, calldata_t *data)
{
const char *variableName;
if (!calldata_get_string(data, nameParam.data(), &variableName) ||
strlen(variableName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
getVariableValueFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
const char *variableValue;
if (!calldata_get_string(data, valueParam.data(), &variableValue) ||
strlen(variableValue) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
getVariableValueFuncName.data(), valueParam.data());
RETURN_FAILURE();
}
auto weakVariable = GetWeakVariableByName(variableName);
auto variable = weakVariable.lock();
if (!variable) {
blog(LOG_WARNING,
"[%s] failed! \"%s\" variable does not exist!",
getVariableValueFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
variable->SetValue(variableValue);
RETURN_SUCCESS();
}
void ScriptHandler::GetRunningStatus(void *ctx, calldata_t *data)
{
calldata_set_bool(data, "is_running", PluginIsRunning());
}
void ScriptHandler::RegisterTempVar(void *, calldata_t *data)
{
const char *variableId;
if (!calldata_get_string(data, tempVarIdParam.data(), &variableId) ||
strlen(variableId) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerTempVarFuncName.data(), tempVarIdParam.data());
RETURN_FAILURE();
}
const char *name;
if (!calldata_get_string(data, tempVarNameParam.data(), &name) ||
strlen(name) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerTempVarFuncName.data(), tempVarNameParam.data());
RETURN_FAILURE();
}
const char *helpText;
if (!calldata_get_string(data, tempVarHelpParam.data(), &helpText)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerTempVarFuncName.data(), tempVarHelpParam.data());
RETURN_FAILURE();
}
long long instanceId;
if (!calldata_get_int(data, GetInstanceIdParamName().data(),
&instanceId)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerTempVarFuncName.data(),
GetInstanceIdParamName().data());
RETURN_FAILURE();
}
std::lock_guard<std::mutex> lock(_mutex);
MacroSegmentScript::RegisterTempVar(variableId, name, helpText,
instanceId);
RETURN_SUCCESS();
}
void ScriptHandler::DeregisterAllTempVars(void *, calldata_t *data)
{
long long instanceId;
if (!calldata_get_int(data, GetInstanceIdParamName().data(),
&instanceId)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
deregisterAllTempVarsFuncName.data(),
GetInstanceIdParamName().data());
RETURN_FAILURE();
}
std::lock_guard<std::mutex> lock(_mutex);
MacroSegmentScript::DeregisterAllTempVars(instanceId);
RETURN_SUCCESS();
}
void ScriptHandler::SetTempVarValue(void *, calldata_t *data)
{
const char *variableId;
if (!calldata_get_string(data, tempVarIdParam.data(), &variableId) ||
strlen(variableId) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
setTempVarValueFuncName.data(), tempVarIdParam.data());
RETURN_FAILURE();
}
const char *variableValue;
if (!calldata_get_string(data, valueParam.data(), &variableValue) ||
strlen(variableValue) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
setTempVarValueFuncName.data(), valueParam.data());
RETURN_FAILURE();
}
long long instanceId;
if (!calldata_get_int(data, GetInstanceIdParamName().data(),
&instanceId)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
setTempVarValueFuncName.data(),
GetInstanceIdParamName().data());
RETURN_FAILURE();
}
std::lock_guard<std::mutex> lock(_mutex);
MacroSegmentScript::SetTempVarValue(variableId, variableValue,
instanceId);
RETURN_SUCCESS();
}
bool ScriptHandler::ActionIdIsValid(const std::string &id)
{
std::lock_guard<std::mutex> lock(_mutex);
return _actions.count(id) > 0;
}
bool ScriptHandler::ConditionIdIsValid(const std::string &id)
{
std::lock_guard<std::mutex> lock(_mutex);
return _conditions.count(id) > 0;
}
static std::string signalNameToTriggerSignalDecl(const std::string &name)
{
return std::string("void ") + name + "()";
}
static std::string signalNameToPropertiesSignalDecl(const std::string &name)
{
return std::string("void ") + name + "(in ptr " +
GetPropertiesSignalParamName().data() + ")";
}
static std::string signalNameToCompletionSignalDecl(const std::string &name)
{
return std::string("bool ") + name + "(in int " +
GetCompletionIdParamName().data() + ")";
}
static std::string signalNameToInstanceSignalDecl(const std::string &name)
{
return std::string("void ") + name + "(out int " +
GetInstanceIdParamName().data() + ")";
}
ScriptSegmentType::ScriptSegmentType(
const std::string &id, const std::string &propertiesSignalName,
const std::string &triggerSignalName,
const std::string &completionSignalName,
const std::string &newInstanceSignalName,
const std::string &deletedInstanceSignalName)
: _id(id)
{
auto sh = obs_get_signal_handler();
signal_handler_add(
sh,
signalNameToPropertiesSignalDecl(propertiesSignalName).c_str());
signal_handler_add(
sh, signalNameToTriggerSignalDecl(triggerSignalName).c_str());
signal_handler_add(
sh,
signalNameToCompletionSignalDecl(completionSignalName).c_str());
signal_handler_add(
sh,
signalNameToInstanceSignalDecl(newInstanceSignalName).c_str());
signal_handler_add(
sh, signalNameToInstanceSignalDecl(deletedInstanceSignalName)
.c_str());
}
} // namespace advss

View File

@@ -0,0 +1,70 @@
#pragma once
#include <obs.hpp>
#include <mutex>
#include <string>
#include <unordered_map>
namespace advss {
class ScriptSegmentType {
public:
ScriptSegmentType() = delete;
ScriptSegmentType(const std::string &id,
const std::string &propertiesSignalName,
const std::string &triggerSignalName,
const std::string &completionSignalName,
const std::string &newInstanceSignalName,
const std::string &deletedInstanceSignalName);
private:
std::string _id;
};
class ScriptHandler {
public:
ScriptHandler() = delete;
static void RegisterScriptAction(void *ctx, calldata_t *data);
static void DeregisterScriptAction(void *ctx, calldata_t *data);
static void RegisterScriptCondition(void *ctx, calldata_t *data);
static void DeregisterScriptCondition(void *ctx, calldata_t *data);
static void GetVariableValue(void *ctx, calldata_t *data);
static void RegisterTempVar(void *ctx, calldata_t *data);
static void DeregisterAllTempVars(void *ctx, calldata_t *data);
static void SetTempVarValue(void *ctx, calldata_t *data);
static void SetVariableValue(void *ctx, calldata_t *data);
static void GetRunningStatus(void *ctx, calldata_t *data);
static bool ActionIdIsValid(const std::string &id);
static bool ConditionIdIsValid(const std::string &id);
private:
static std::mutex _mutex;
static std::unordered_map<std::string, ScriptSegmentType> _actions;
static std::unordered_map<std::string, ScriptSegmentType> _conditions;
};
static constexpr std::string_view GetPropertiesSignalParamName()
{
return "properties";
}
static constexpr std::string_view GetActionCompletionSignalParamName()
{
return "completion_signal_name";
}
static constexpr std::string_view GetCompletionIdParamName()
{
return "completion_id";
}
static constexpr std::string_view GetResultSignalParamName()
{
return "result";
}
static constexpr std::string_view GetInstanceIdParamName()
{
return "instance_id";
}
} // namespace advss

View File

@@ -1,3 +1,4 @@
#include "macro-segment-copy-paste.hpp"
#include "advanced-scene-switcher.hpp"
#include "macro.hpp"
@@ -61,13 +62,13 @@ void AdvSceneSwitcher::PasteMacroSegment()
const auto condition = std::static_pointer_cast<MacroCondition>(
copyInfo.segment);
auto logic = condition->GetLogicType();
if (logic > LogicType::ROOT_LAST &&
if (logic > Logic::Type::ROOT_LAST &&
macro->Conditions().empty()) {
logic = LogicType::ROOT_NONE;
logic = Logic::Type::ROOT_NONE;
}
if (logic < LogicType::ROOT_LAST &&
if (logic < Logic::Type::ROOT_LAST &&
!macro->Conditions().empty()) {
logic = LogicType::OR;
logic = Logic::Type::OR;
}
AddMacroCondition(macro.get(), macro->Conditions().size(),
copyInfo.segment->GetId(), data.Get(), logic);
@@ -91,6 +92,27 @@ bool MacroSegmentIsInClipboard()
return copyInfo.type != MacroSegmentCopyInfo::Type::NONE;
}
bool MacroActionIsInClipboard()
{
return copyInfo.type == MacroSegmentCopyInfo::Type::ACTION ||
copyInfo.type == MacroSegmentCopyInfo::Type::ELSE;
}
void SetCopySegmentTargetActionType(bool setToElseAction)
{
if (copyInfo.type == MacroSegmentCopyInfo::Type::ACTION &&
setToElseAction) {
copyInfo.type = MacroSegmentCopyInfo::Type::ELSE;
return;
}
if (copyInfo.type == MacroSegmentCopyInfo::Type::ELSE &&
!setToElseAction) {
copyInfo.type = MacroSegmentCopyInfo::Type::ACTION;
return;
}
}
void SetupSegmentCopyPasteShortcutHandlers(AdvSceneSwitcher *window)
{
auto copyShortcut = new QShortcut(QKeySequence("Ctrl+C"), window);

View File

@@ -3,6 +3,8 @@ namespace advss {
class AdvSceneSwitcher;
bool MacroSegmentIsInClipboard();
bool MacroActionIsInClipboard();
void SetCopySegmentTargetActionType(bool setToElseAction);
void SetupSegmentCopyPasteShortcutHandlers(AdvSceneSwitcher *window);
} // namespace advss

View File

@@ -35,6 +35,9 @@ MacroSegmentList::MacroSegmentList(QWidget *parent)
setWidget(wrapper);
setWidgetResizable(true);
setAcceptDrops(true);
connect(verticalScrollBar(), &QScrollBar::valueChanged, this,
[this]() { SetupVisibleMacroSegmentWidgets(); });
}
MacroSegmentList::~MacroSegmentList()
@@ -59,7 +62,7 @@ static bool posIsInScrollbar(const QScrollBar *scrollbar, const QPoint &pos)
return globalGeo.contains(pos);
}
int MacroSegmentList::GetDragIndex(const QPoint &pos)
int MacroSegmentList::GetDragIndex(const QPoint &pos) const
{
// Don't drag widget when interacting with the scrollbars
if (posIsInScrollbar(horizontalScrollBar(), mapTo(this, pos)) ||
@@ -88,34 +91,36 @@ int MacroSegmentList::GetDragIndex(const QPoint &pos)
return -1;
}
void MacroSegmentList::SetHelpMsg(const QString &msg)
void MacroSegmentList::SetHelpMsg(const QString &msg) const
{
_helpMsg->setText(msg);
}
void MacroSegmentList::SetHelpMsgVisible(bool visible)
void MacroSegmentList::SetHelpMsgVisible(bool visible) const
{
_helpMsg->setVisible(visible);
}
void MacroSegmentList::Insert(int idx, MacroSegmentEdit *widget)
void MacroSegmentList::Insert(int idx, QWidget *widget)
{
widget->installEventFilter(this);
_contentLayout->insertWidget(idx, widget);
SetupVisibleMacroSegmentWidgets();
}
void MacroSegmentList::Add(QWidget *widget)
{
widget->installEventFilter(this);
_contentLayout->addWidget(widget);
SetupVisibleMacroSegmentWidgets();
}
void MacroSegmentList::Remove(int idx)
void MacroSegmentList::Remove(int idx) const
{
DeleteLayoutItemWidget(_contentLayout->takeAt(idx));
}
void MacroSegmentList::Clear(int idx)
void MacroSegmentList::Clear(int idx) const
{
ClearLayout(_contentLayout, idx);
}
@@ -133,7 +138,7 @@ void MacroSegmentList::Highlight(int idx, QColor color)
HighlightWidget(widget, color, QColor(0, 0, 0, 0), true);
}
void MacroSegmentList::SetCollapsed(bool collapse)
void MacroSegmentList::SetCollapsed(bool collapse) const
{
QLayoutItem *item = nullptr;
for (int i = 0; i < _contentLayout->count(); i++) {
@@ -145,7 +150,7 @@ void MacroSegmentList::SetCollapsed(bool collapse)
}
}
void MacroSegmentList::SetSelection(int idx)
void MacroSegmentList::SetSelection(int idx) const
{
for (int i = 0; i < _contentLayout->count(); ++i) {
auto widget = static_cast<MacroSegmentEdit *>(
@@ -178,11 +183,7 @@ void MacroSegmentList::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton ||
event->button() == Qt::RightButton) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
_dragPosition = GetDragIndex(event->globalPos());
#else
_dragPosition = GetDragIndex(event->globalPosition().toPoint());
#endif
emit SelectionChanged(_dragPosition);
} else {
_dragPosition = -1;
@@ -235,7 +236,7 @@ void MacroSegmentList::dragEnterEvent(QDragEnterEvent *event)
}
}
MacroSegmentEdit *MacroSegmentList::WidgetAt(int idx)
MacroSegmentEdit *MacroSegmentList::WidgetAt(int idx) const
{
if (idx < 0 || idx >= _contentLayout->count()) {
return nullptr;
@@ -247,11 +248,16 @@ MacroSegmentEdit *MacroSegmentList::WidgetAt(int idx)
return static_cast<MacroSegmentEdit *>(item->widget());
}
MacroSegmentEdit *MacroSegmentList::WidgetAt(const QPoint &pos)
MacroSegmentEdit *MacroSegmentList::WidgetAt(const QPoint &pos) const
{
return WidgetAt(GetSegmentIndexFromPos(mapToGlobal(pos)));
}
int MacroSegmentList::IndexAt(const QPoint &pos) const
{
return GetSegmentIndexFromPos(mapToGlobal(pos));
}
void MacroSegmentList::HideLastDropLine()
{
if (_dropLineIdx >= 0 && _dropLineIdx < _contentLayout->count()) {
@@ -295,15 +301,11 @@ void MacroSegmentList::dragMoveEvent(QDragMoveEvent *event)
return;
}
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
_dragCursorPos = (mapToGlobal(event->pos()));
#else
_dragCursorPos = (mapToGlobal(event->position().toPoint()));
#endif
CheckDropLine(_dragCursorPos);
}
QRect MacroSegmentList::GetContentItemRectWithPadding(int idx)
QRect MacroSegmentList::GetContentItemRectWithPadding(int idx) const
{
auto item = _contentLayout->itemAt(idx);
if (!item) {
@@ -325,7 +327,37 @@ QRect MacroSegmentList::GetContentItemRectWithPadding(int idx)
return rect;
}
int MacroSegmentList::GetSegmentIndexFromPos(const QPoint &pos)
void MacroSegmentList::SetVisibilityCheckEnable(bool enable)
{
_checkVisibility = enable;
if (_checkVisibility) {
QTimer::singleShot(0, this, [this]() {
SetupVisibleMacroSegmentWidgets();
});
}
}
void MacroSegmentList::SetupVisibleMacroSegmentWidgets()
{
if (!_checkVisibility) {
return;
}
const auto viewportRect = viewport()->rect();
for (auto segment : widget()->findChildren<MacroSegmentEdit *>()) {
const auto pos = segment->mapTo(viewport(), QPoint(0, 0));
const QRect rect(pos, segment->size());
if (!viewportRect.intersects(rect)) {
continue;
}
segment->SetupWidgets();
}
}
int MacroSegmentList::GetSegmentIndexFromPos(const QPoint &pos) const
{
int idx = -1;
for (int i = 0; i < _contentLayout->count(); ++i) {
@@ -337,7 +369,7 @@ int MacroSegmentList::GetSegmentIndexFromPos(const QPoint &pos)
return idx;
}
void MacroSegmentList::CheckScroll()
void MacroSegmentList::CheckScroll() const
{
while (_autoScroll) {
const int scrollTrigger = 15;
@@ -407,14 +439,14 @@ void MacroSegmentList::CheckDropLine(const QPoint &pos)
}
}
bool MacroSegmentList::IsInListArea(const QPoint &pos)
bool MacroSegmentList::IsInListArea(const QPoint &pos) const
{
const QRect layoutRect(mapToGlobal(_layout->contentsRect().topLeft()),
_layout->contentsRect().size());
return layoutRect.contains(pos);
}
int MacroSegmentList::GetDropIndex(const QPoint &pos)
int MacroSegmentList::GetDropIndex(const QPoint &pos) const
{
int idx = GetSegmentIndexFromPos(pos);
if (idx == _dragPosition) {
@@ -445,16 +477,6 @@ void MacroSegmentList::dropEvent(QDropEvent *event)
{
HideLastDropLine();
auto widget = qobject_cast<QWidget *>(event->source());
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
if (widget && !widget->geometry().contains(event->pos()) &&
widgetIsInLayout(widget, _contentLayout)) {
int dropPosition = GetDropIndex(mapToGlobal(event->pos()));
if (dropPosition == -1) {
return;
}
emit Reorder(dropPosition, _dragPosition);
}
#else
if (widget &&
!widget->geometry().contains(event->position().toPoint()) &&
widgetIsInLayout(widget, _contentLayout)) {
@@ -465,8 +487,13 @@ void MacroSegmentList::dropEvent(QDropEvent *event)
}
emit Reorder(dropPosition, _dragPosition);
}
#endif
_dragPosition = -1;
}
void MacroSegmentList::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
SetupVisibleMacroSegmentWidgets();
}
} // namespace advss

View File

@@ -15,18 +15,20 @@ class MacroSegmentList : public QScrollArea {
public:
MacroSegmentList(QWidget *parent = nullptr);
virtual ~MacroSegmentList();
void SetHelpMsg(const QString &msg);
void SetHelpMsgVisible(bool visible);
MacroSegmentEdit *WidgetAt(int idx);
MacroSegmentEdit *WidgetAt(const QPoint &);
void Insert(int idx, MacroSegmentEdit *widget);
void SetHelpMsg(const QString &msg) const;
void SetHelpMsgVisible(bool visible) const;
MacroSegmentEdit *WidgetAt(int idx) const;
MacroSegmentEdit *WidgetAt(const QPoint &) const;
int IndexAt(const QPoint &) const;
void Insert(int idx, QWidget *widget);
void Add(QWidget *widget);
void Remove(int idx);
void Clear(int idx = 0); // Clear all elements >= idx
void Remove(int idx) const;
void Clear(int idx = 0) const; // Clear all elements >= idx
void Highlight(int idx, QColor color = QColor(Qt::green));
void SetCollapsed(bool);
void SetSelection(int idx);
QVBoxLayout *ContentLayout() { return _contentLayout; }
void SetCollapsed(bool) const;
void SetSelection(int idx) const;
QVBoxLayout *ContentLayout() const { return _contentLayout; }
void SetVisibilityCheckEnable(bool enable);
signals:
void SelectionChanged(int idx);
@@ -41,15 +43,17 @@ protected:
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
void resizeEvent(QResizeEvent *event);
private:
int GetDragIndex(const QPoint &);
int GetDropIndex(const QPoint &);
int GetSegmentIndexFromPos(const QPoint &);
void CheckScroll();
void SetupVisibleMacroSegmentWidgets();
int GetSegmentIndexFromPos(const QPoint &) const;
int GetDragIndex(const QPoint &) const;
int GetDropIndex(const QPoint &) const;
void CheckScroll() const;
void CheckDropLine(const QPoint &);
bool IsInListArea(const QPoint &);
QRect GetContentItemRectWithPadding(int idx);
bool IsInListArea(const QPoint &) const;
QRect GetContentItemRectWithPadding(int idx) const;
void HideLastDropLine();
int _dragPosition = -1;
@@ -61,6 +65,8 @@ private:
QVBoxLayout *_layout;
QVBoxLayout *_contentLayout;
QLabel *_helpMsg;
bool _checkVisibility = true;
};
} // namespace advss

Some files were not shown because too many files have changed in this diff Show More