Compare commits

...

274 Commits

Author SHA1 Message Date
WarmUpTill
953a05054d handle import 2026-09-04 22:18:05 +02:00
WarmUpTill
bb9e3efa90 nested macros 2026-09-04 22:18:05 +02:00
WarmUpTill
71a2a83c3b type undo / redo 2026-09-04 22:18:04 +02:00
WarmUpTill
d946ed485c Add undo / redo support for macros 2026-09-04 22:18:04 +02:00
WarmUpTill
33ee7ac6cb Clear widget cache for active macro to enable undo / redo 2026-09-04 22:18:04 +02:00
WarmUpTill
3bcc041487 Fix crash when rapidly inserting / removing macro segments 2026-09-04 22:18:04 +02:00
WarmUpTill
8a603b1675 Add audio condition check if source is enabled on a specific track
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
2026-09-04 22:17:49 +02:00
WarmUpTill
67dda18929 Fix crash when changing scene collection on MacOS 2026-09-04 21:57:47 +02:00
WarmUpTill
e11499c308 Add "Now Playing" condition 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
2026-08-29 23:33:58 +02:00
WarmUpTill
e76df589e2 Fix cmake configure errors during download retry
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
2026-08-14 22:45:29 +02:00
WarmUpTill
1390565fc6 Disable additional warnings for whisper.cpp 2026-08-14 22:45:29 +02:00
WarmUpTill
d76a37c785 Add AdvancedSceneSwitcherVersion vendor request
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
Returns version and commit information
2026-08-12 20:55:34 +02:00
WarmUpTill
ce8a35adea CI: Include Ubuntu 26 artifacts in release draft 2026-08-12 20:55:34 +02:00
Rygtx
9030ba1adf Update locale (zh-CN.ini)
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
2026-08-10 21:27:25 +02:00
WarmUpTill
5b8d829bba Add "Speech Recognition" condition
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
Allows checking for speech patterns on a given OBS audio source
2026-08-05 12:00:34 +02:00
WarmUpTill
ad03b92d2d CI: Install vulkan SDK
Required to build whisper.cpp with GPU acceleration support
2026-08-05 12:00:34 +02:00
WarmUpTill
88fc412900 deps: Add whisper.cpp 2026-08-05 12:00:34 +02:00
WarmUpTill
c2ec6848aa Fix sources of type "Group" being listed twice in filter selections
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
2026-08-04 21:05:55 +02:00
WarmUpTill
01da2877b0 CI: Add Ubuntu 26 build 2026-08-03 20:52:39 +02:00
WarmUpTill
107d77f1b8 Split macro-condition-video.cpp UI into per-widget files
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
This makes it easier to conditionally exclude video-edit-object.cpp
from the build when OpenCV >= 5 drops CascadeClassifier support.
2026-08-01 22:34:01 +02:00
WarmUpTill
1030868a75 CI: Build "xobjdetect" when building OpenCV 5.x 2026-08-01 22:34:01 +02:00
WarmUpTill
797dc2ceb1 deps: Add OpenCV contrib repo
Needed for Haar Cascade Classifiers support since OpenCV 5.0.0
2026-08-01 22:34:01 +02:00
WarmUpTill
b971bfe800 deps: Switch to OpenCV 5.0.0 2026-08-01 22:34:01 +02:00
WarmUpTill
f88407b731 Export Section symbols 2026-08-01 22:34:01 +02:00
WarmUpTill
725e3cacb8 Add license files
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
2026-07-30 20:47:39 +02:00
WarmUpTill
c9eed5d997 Add un-/pause option to macro tab context menu
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
2026-07-22 11:27:15 +02:00
Arimil
bf682136c3 OpenCV 5 support
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
2026-07-19 19:56:07 +02:00
WarmUpTill
7be2a4f544 Add support to snapshot vars before queue insertion
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
2026-07-17 23:24:02 +02:00
WarmUpTill
616664e6f3 Add "Loop" action 2026-07-17 23:24:02 +02:00
WarmUpTill
0caf8628b7 Add HideElseSection() in preparation for loop action 2026-07-17 23:24:02 +02:00
WarmUpTill
463e7caa2c Reword locale keys 2026-07-17 23:24:02 +02:00
WarmUpTill
0e2315807f Statically link SSL to avoid dll conflicts on Windows 2026-07-17 21:43:58 +02:00
WarmUpTill
46bf6c666f Cleanup includes
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
2026-07-11 19:50:58 +02:00
WarmUpTill
902be7759d Speed up build times by using pre-compiled headers 2026-07-11 19:50:58 +02:00
WarmUpTill
b4a1593dc9 Fix crash on shutdown caused by "Profile" condition
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
2026-07-07 20:19:51 +02:00
WarmUpTill
577cfc6ba9 Add message_text temp var for subscription message event
Some checks failed
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
Check locale / ubuntu64 (push) Has been cancelled
The existing "message" temp var for the subscription message event
contained a raw JSON object (including emote metadata) rather than
plain text, making it difficult to use.
2026-07-06 17:19:32 +02:00
WarmUpTill
f7a29dfa69 Add option to start and stop polls 2026-07-06 17:19:32 +02:00
WarmUpTill
e3475cf8ee Add option to wait for scene item transition
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
2026-07-05 20:41:12 +02:00
WarmUpTill
b344f75a30 Add variable setter / getter to inline script generated code 2026-07-05 20:26:36 +02:00
WarmUpTill
338a478ead Cleanup Twitch timestamp verification
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
2026-07-04 12:09:31 +02:00
WarmUpTill
2658ced18e Add "Audio" option to the first run wizard
Some checks failed
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
Check locale / ubuntu64 (push) Has been cancelled
2026-07-03 18:57:32 +02:00
WarmUpTill
cd6ff4d52b Add "Sequence" option to the first run wizard 2026-07-03 18:57:32 +02:00
WarmUpTill
5438a6a364 Split first run wizard into multiple files 2026-07-03 18:57:32 +02:00
WarmUpTill
ea1dea8621 Add option to wait for variable changes to "Wait" action type
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
2026-07-03 15:59:23 +02:00
WarmUpTill
3a556113aa Expose API to notify about variable value change 2026-07-03 15:59:23 +02:00
WarmUpTill
eded0367db Add option to get commercial info 2026-07-03 15:59:23 +02:00
WarmUpTill
6c90f517e4 Fix freeze when stopping waiting macro via "Run macro" button 2026-07-03 15:59:23 +02:00
WarmUpTill
c07c00325f Switch to VariableColorButton for OCR and color match
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
2026-06-30 20:30:50 +02:00
WarmUpTill
0a2e2cd771 Add VariableColorButton
Used for color selection which also support variables
2026-06-30 20:30:50 +02:00
WarmUpTill
8f3b5f9dfc CI: Rename "legacy" Windows artifacts to "portable"
This is consistent with the naming used by other plugin developers
2026-06-30 19:54:52 +02:00
WarmUpTill
78499bcf5f Improve layout spacing for "Recording" action
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
2026-06-29 17:48:42 +02:00
WarmUpTill
78fa776ce5 Move recording tips to HelpIcons 2026-06-29 17:48:42 +02:00
WarmUpTill
93c6415547 Add restart hint to recording folder/format actions 2026-06-29 17:48:42 +02:00
WarmUpTill
12f26c29e6 Clarify COPY_VAR layout to show source and destination variables
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
2026-06-26 22:29:06 +02:00
WarmUpTill
e4f6d94247 Add option to automatically extract value from JSON query result 2026-06-26 22:29:06 +02:00
WarmUpTill
a04c51397d Fix variables of nested macros' actions not being resolved
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
2026-06-17 21:12:48 +02:00
WarmUpTill
2073ef6ce9 Fix nested macro actions being executed twice when added to a queue 2026-06-17 21:12:48 +02:00
WarmUpTill
7dfd9ddcb5 Print plugin enabled/disabled summary during CMake configure
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
2026-06-16 17:40:22 +02:00
WarmUpTill
6215c39024 Don't fail cmake configure step if openvr deps are not met 2026-06-16 17:40:22 +02:00
WarmUpTill
1c856911e8 Set default value for CMAKE_GENERATOR_PLATFORM on Windows 2026-06-16 17:40:22 +02:00
WarmUpTill
ed17c2ac6d Disconnect Twitch chat and event connection on plugin stop
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
2026-06-13 22:05:32 +02:00
WarmUpTill
1ce332a91d Track all kWin wayland windows 2026-06-13 22:01:52 +02:00
WarmUpTill
2aa27fc051 Fix window conditions not triggering for native Wayland windows
When KWin compat is active, GetWindows() only returned XWayland windows
since it relied on X11's _NET_CLIENT_LIST. Native Wayland windows were
invisible to conditions even though GetCurrentWindowTitle() reported them
correctly via the KWin DBus notifier.
2026-06-13 22:01:52 +02:00
WarmUpTill
31b239d5dc Focus segment type selection when new segment is added
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
2026-06-11 18:25:29 +02:00
WarmUpTill
7c3779e287 Sort macro export dialog resources alphabetically and add scrollbars 2026-06-11 18:25:29 +02:00
WarmUpTill
3f13c33b10 Fix crash on transition start in studio mode 2026-06-11 18:25:29 +02:00
WarmUpTill
8727a65513 Support both recommended and legacy layouts in Windows installer
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
Default to %ProgramData%\obs-studio\plugins\ on fresh installs.
Restore the previously recorded install path on upgrades, so users
who installed to a custom location are not silently moved.
2026-05-30 16:01:11 +02:00
WarmUpTill
baf3d0e63c Fix race condition in Twitch event sub reconnection logic 2026-05-30 16:01:11 +02:00
WarmUpTill
ea2d1e7e9d Fix crash when importing macros is slow
Probably not a real issue.
Noticed during debug of another problem.
2026-05-30 16:01:11 +02:00
WarmUpTill
f9f26ad50b Fix crash on shutdown
StopAllMacros was called before th->wait(), allowing Macro::Stop()
to run concurrently with the other threads still inside
CheckConditions. This caused races on _conditionCheckFuture (called
from both threads) and _helperThreads.

This could happen if conditions / actions were marked to run in parallel
to the main loop.
2026-05-30 16:01:11 +02:00
WarmUpTill
9911c41a35 Split advanced-scene-switcher-*-windows-x64.zip
One zip archive for the ProgramData path.
Another zip archive for the legacy Program Files path.

Requested in:
https://github.com/WarmUpTill/SceneSwitcher/discussions/1628
2026-05-30 16:01:11 +02:00
WarmUpTill
d590bc11f8 Fix freeze when choosing invalid OCR model 2026-05-30 16:01:11 +02:00
WarmUpTill
31c4c1f5d7 Convert "Program Files/" paths to "ProgramData/" paths on Windows
Affects OCR and object detection functionality of the video condition
2026-05-30 16:01:11 +02:00
WarmUpTill
42ff48d637 Add option to lock settings dialog with simple password
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
This is meant as a simple tampering protection.
The settings are not encrypted and the password is stored in plaintext.
2026-05-28 21:50:00 +02:00
WarmUpTill
d44376e869 deps: Update cpp-httplib to v0.46.0
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
2026-05-27 11:38:26 +02:00
WarmUpTill
4b1fd9e700 Fix crash on startup
Resize of vector could invalidate the this pointers passed to the
PostLoad step lambdas
2026-05-27 11:32:30 +02:00
WarmUpTill
d61099f9c0 Fix freeze when showing setup wizzard
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
2026-05-25 12:04:57 +02:00
WarmUpTill
c2b73a02ca Prevent plugin from being loaded twice 2026-05-25 11:49:37 +02:00
WarmUpTill
49a681bcfe Show "Macro Schedule" tab when date / timer condition is created 2026-05-25 11:49:37 +02:00
WarmUpTill
4cedb29123 Fix issues with Windows installer
Don't reuse the old installation dir since it might not be compatible
with the new layout.
Ensure previous legacy install is cleaned up to avoid plugin being
installed twice.
2026-05-25 11:49:37 +02:00
WarmUpTill
d80b3aaf69 CI: Ensure ccache is used on macOS 2026-05-25 11:49:37 +02:00
WarmUpTill
b1cc15386f Fix warnings 2026-05-25 11:49:37 +02:00
WarmUpTill
748744d1b7 Fix packaging issue on Ubuntu 2026-05-25 11:49:37 +02:00
WarmUpTill
912ff98e27 Ignore warnings generated by libremidi 2026-05-25 11:49:37 +02:00
WarmUpTill
a9614c5306 CI: Fix warnings 2026-05-25 11:49:34 +02:00
WarmUpTill
cb2f97f11d Adapt macOS fullscreen window detection to be more reliable 2026-05-25 00:22:59 +02:00
WarmUpTill
017ea233ec Fix macOS fullscreen window detection across "Spaces" 2026-05-25 00:22:59 +02:00
WarmUpTill
e8206173ff Tests: Add test-macro-condition-window.cpp 2026-05-25 00:22:59 +02:00
WarmUpTill
680f72e4f9 Rework temp var mapping handling on copy of macro segments 2026-05-25 00:22:59 +02:00
WarmUpTill
930e8b38d4 Refactor window platform helper functions 2026-05-25 00:22:59 +02:00
WarmUpTill
ee1d0136d9 Add temp vars for window position and size 2026-05-25 00:22:59 +02:00
WarmUpTill
20c276f4ea CI: Fix occasional formatting check failures 2026-05-25 00:22:59 +02:00
WarmUpTill
bea4f66eb1 Fix crash when accessing files in profile dir 2026-05-25 00:22:59 +02:00
WarmUpTill
c5185765ee CI: Pin openssl version on Windows
Must stick with OpenSSL 3 for now to stay compatbile with the asio
version supported by websocketpp
2026-05-25 00:22:59 +02:00
WarmUpTill
90130c119e Fix crash due to "keep source active" setting 2026-05-25 00:22:59 +02:00
WarmUpTill
a7ca06d25c Prevent crash when OCR traineddata file is missing 2026-05-25 00:22:59 +02:00
WarmUpTill
5a5d590c51 CI: Trigger rebuild of dependencies 2026-05-25 00:22:58 +02:00
WarmUpTill
336b398bc3 deps: Update libusb submodule to v1.0.30-rc2
Required for v145 toolset support on Windows
2026-05-25 00:22:58 +02:00
WarmUpTill
56c8f335db deps: Update leptonica submodule to 1.87.0
Required to support cmake 4+
2026-05-25 00:22:58 +02:00
WarmUpTill
b6a4d48948 Fix temp var mapping not working for new macro segments 2026-05-25 00:22:58 +02:00
WarmUpTill
774ccb7fbd Fix temp var mapping not working after copy-paste of segment 2026-05-25 00:22:58 +02:00
WarmUpTill
eda9dde23d Add temp vars indicating OCR match location 2026-05-25 00:22:58 +02:00
WarmUpTill
bbf91efd0d Add "Macro Schedule" tab 2026-05-25 00:22:54 +02:00
WarmUpTill
b022d59391 Add calendar widget 2026-05-15 19:15:19 +02:00
WarmUpTill
cf46a010a7 Add support for units "days" 2026-05-15 19:15:19 +02:00
WarmUpTill
595bab0d83 Add option to export Websocket, Twitch, HTTP connections 2026-05-15 19:15:19 +02:00
WarmUpTill
6fc819d72b Add API to expand export / import dialog 2026-05-15 19:15:19 +02:00
WarmUpTill
0553a9b2ee Call MacroRef::PostLoad() automatically 2026-05-15 19:15:19 +02:00
WarmUpTill
3caa3a4735 Add helpers to check, run, stop, and pause macros 2026-05-15 19:15:19 +02:00
WarmUpTill
2469057ad6 Fix double free
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
2026-05-11 17:30:18 +02:00
WarmUpTill
b230ec5bc5 locale: Clarify "reevaluate condition state" option for run macro action 2026-05-11 17:30:18 +02:00
WarmUpTill
1669b4cb72 Fix "Run Macro" help being visible when option is not selected 2026-05-11 17:30:18 +02:00
WarmUpTill
2841fcaadb Fix variable dock not listing variables on startup 2026-05-11 17:30:11 +02:00
WarmUpTill
91810d9c40 Enable resolving plugin symbols in OBS crash reports 2026-05-11 17:24:35 +02:00
WarmUpTill
ccb5c8ba2c Don't highlight paused macro in macro list 2026-05-11 17:24:35 +02:00
WarmUpTill
265daebdf3 Don't log "on change" when macro is paused 2026-05-11 17:24:35 +02:00
WarmUpTill
a149a11c57 Don't highlight action trigger mode when macro is paused 2026-05-11 17:24:35 +02:00
WarmUpTill
d7cc7a4c7b Ensure plugin is stopped before running cleanup steps 2026-05-11 17:24:35 +02:00
WarmUpTill
41866bcb24 Replace "Ignore Entry" condition logic with SwitchButton toggle 2026-05-11 17:24:35 +02:00
WarmUpTill
c73a5866d7 Add option to keep source of video condition active
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
2026-05-10 12:47:55 +02:00
WarmUpTill
18aaf1dd31 Add option to keep selected game capture sources active 2026-05-10 12:47:55 +02:00
WarmUpTill
d523a2e674 Add helper to keep sources active
To be used for e.g. the video condition / game capture condition, which
can only get the relevant information while a given source is "active".

A source can become active if it is not part of the currently selected
scene.
2026-05-10 12:47:55 +02:00
WarmUpTill
0f7eed6257 Fix "transitioning from" check not working in studio mode
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
2026-05-09 13:05:50 +02:00
WarmUpTill
1f5a346190 Fix transition signals not being connected on scene selection change
This would be the case if a new transition condition was created, which
immediately is changed to "transitioning from ..." or "transitioning to
..." type.
2026-05-09 13:05:50 +02:00
WarmUpTill
7fd2979e86 Support legacy and new Windows install paths in zip archive
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
See https://obsproject.com/kb/plugins-guide for more details
2026-05-08 22:15:44 +02:00
WarmUpTill
2077e1971d Adapt to new plugin folder structure / location for Windows 2026-05-08 22:15:44 +02:00
WarmUpTill
873e073eb6 Show all feature tabs when setup wizard is skipped
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
2026-05-05 19:36:41 +02:00
WarmUpTill
4f3d9e3a00 Use static self-registration for action queue and variable setup
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
Removes explicit setup calls from the plugin core. Each module now
registers its own save/load/cleanup steps via a static initializer,
decoupling them from InitSceneSwitcher and SwitcherData.
2026-04-30 22:39:06 +02:00
WarmUpTill
c02896ea17 Clear websocket and mqtt connections on shutdown
This was previously implicitly done by a settings reload on shutdown,
but this has since been removed, and we need to clear the connections
explicitly.
2026-04-30 22:39:06 +02:00
WarmUpTill
4bfd40219b Deregister all inline scripts on SCRIPTING_SHUTDOWN 2026-04-30 22:39:06 +02:00
WarmUpTill
6a3de18069 Clear macros on shutdown
This was done implicitly in the shutdown handler by clearing the
plugin's settings.
Due to unwanted side effects this behavior has been disabled for the
shutdown and thus macros have to be cleared explicitly on shutdown.
2026-04-30 22:39:06 +02:00
WarmUpTill
8c51b44fbb Fix potential event sub freeze on disconnect 2026-04-30 22:39:06 +02:00
WarmUpTill
10daa38921 Cleanup 2026-04-30 22:39:06 +02:00
WarmUpTill
6e0f7ab38f Default to assuming OBS is shutting down
This reverts commit eea91b79b38b7c33561dc6c639d01dea8a802cdd.
2026-04-30 22:39:06 +02:00
WarmUpTill
86061b3cf1 Ignore OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP on shutdown
This prevents the plugin being restarted, connections being reset, and
potentially delaying the shutdown of OBS.
2026-04-30 22:39:06 +02:00
WarmUpTill
c3669cae3e Tests: Make building tests optional, enable for CI 2026-04-30 20:11:52 +02:00
WarmUpTill
a116360f87 Tests: Handle QFile::open() return value 2026-04-30 20:11:52 +02:00
WarmUpTill
713f695aa4 Show variable mapping for "Source" and "Twitch" action
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
2026-04-27 19:20:51 +02:00
WarmUpTill
dec5c2d763 Add option to directly map temp var values to variables 2026-04-27 19:20:51 +02:00
WarmUpTill
f090dea136 Add "Run macro" option
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
2026-04-21 17:48:05 +02:00
WarmUpTill
d00986df03 Add button to get current values to "Streaming" action 2026-04-21 17:45:35 +02:00
WarmUpTill
03e5397b7e CI: remove openssl workaround
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
2026-04-20 20:47:46 +02:00
WarmUpTill
94d49db196 Add tests for process condition
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
2026-04-15 14:02:59 +02:00
WarmUpTill
1483f9d9dc Cleanup / style changes 2026-04-15 14:02:59 +02:00
WarmUpTill
99629e8c66 Add process path check 2026-04-15 14:02:59 +02:00
WarmUpTill
ba38b8bf27 Don't block UI while executing long runnig actions
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 previous approach had the problem of losing any action internal
state changes in the created copy.

Revert "Fix temp var values of actions not being accessible"
This reverts commit df42538319.
Revert "Don't block UI while running actions"
This reverts commit a01d26e25d.
2026-04-04 21:14:05 +02:00
WarmUpTill
38513735dd Refresh available interactable soruces on show 2026-04-04 21:14:05 +02:00
WarmUpTill
acfc7d605b Add "Get macro info" action
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
2026-04-03 13:44:34 +02:00
WarmUpTill
df42538319 Fix temp var values of actions not being accessible
This a bug introduced with ec3052c823.
2026-04-03 13:44:34 +02:00
WarmUpTill
b02259e926 Add "Source interact" action 2026-04-03 09:41:09 +02:00
WarmUpTill
2b6bd9fb51 Add helpers to add widgets and separators to ListControls 2026-04-03 09:41:09 +02:00
WarmUpTill
aadedbb610 Enable setting min and max list size 2026-04-03 09:41:09 +02:00
WarmUpTill
d15a324137 Add placeholder text support 2026-04-03 09:41:09 +02:00
WarmUpTill
119fac17bf Cleanup
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
* Member access
* Refresh settings on show to ensure "repeat" changes are visible
2026-04-02 22:39:12 +02:00
WarmUpTill
088881b674 Add HTTP 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
2026-04-02 17:26:16 +02:00
WarmUpTill
f008d5367b Adjust layout of HTTP action 2026-04-02 17:26:16 +02:00
WarmUpTill
1ace185a57 Add action to play sound 2026-04-02 17:25:41 +02:00
WarmUpTill
a01d26e25d Don't block UI while running actions 2026-04-02 17:25:41 +02:00
WarmUpTill
7e2ef9aacc Add tests for "File" condition
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
2026-04-01 11:13:01 +02:00
WarmUpTill
4a98d064f7 Enable testing 2026-04-01 11:13:01 +02:00
WarmUpTill
b0d6cea52a Remove deprecated functionality from "File" condition 2026-04-01 11:13:01 +02:00
WarmUpTill
d6bf9ed101 Fix date / content changed check triggering on plugin start 2026-04-01 11:13:01 +02:00
WarmUpTill
7e940e515b Add option to suppress crash dialog
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
2026-03-31 23:04:35 +02:00
WarmUpTill
29e1ff0754 Adjust video condition temp vars
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
2026-03-30 21:20:00 +02:00
WarmUpTill
6309100cc4 Rework pattern count temp var 2026-03-30 21:20:00 +02:00
WarmUpTill
ec3052c823 Enable checking if temp var is in use 2026-03-30 21:20:00 +02:00
WarmUpTill
a28b800228 Remove Snap installation instructions from README
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
Removed Snap package installation instructions for OBS Studio.
2026-03-27 18:36:35 +01:00
WarmUpTill
713a64ab45 Fix crash when using math helpers concurrently
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
2026-03-25 19:38:22 +01:00
WarmUpTill
4d5ffe38fc Fix race conditions when accessing variables 2026-03-25 19:38:22 +01:00
WarmUpTill
85460157b1 Fix title of script open dialog 2026-03-25 19:38:22 +01:00
WarmUpTill
35e5b7620e Add option to "release the T-Bar"
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
2026-03-23 19:05:49 +01:00
WarmUpTill
e4c06b14a7 Cleanup 2026-03-23 19:05:49 +01:00
WarmUpTill
7fe3258269 Add support for changing T-bar value 2026-03-23 19:05:49 +01:00
WarmUpTill
ec19b4d4fb Add option to add new recording chapter
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
2026-03-22 21:24:59 +01:00
WarmUpTill
9301ead060 Add chat settings toggles
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
2026-03-21 22:52:27 +01:00
WarmUpTill
bc29ece526 Cleanup and layout adjustments 2026-03-21 22:52:27 +01:00
WarmUpTill
4158c7a363 Add Twitch actions
* Send shoutout
* Cancel raid
* Enable/disable shield mode
* Enable/disable branded content
* Snooze next ad
2026-03-21 22:52:27 +01:00
WarmUpTill
944d1059da Add user moderation actions 2026-03-21 22:52:27 +01:00
WarmUpTill
bf18d8e106 Validate that user id matches token 2026-03-21 22:52:27 +01:00
WarmUpTill
bf216e0917 Fix subscription handling and improve logging
* Revocations were not handled properly
* Active subscriptions were not cleared on reconnect
* Migration client handling could cause crash
* Add error logging
2026-03-21 22:52:27 +01:00
WarmUpTill
2405b6dbbf Add "Next Macro" temp var to Sequence actions when setting index
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
2026-03-21 00:05:58 +01:00
WarmUpTill
e64f2d195e Add temp vars to "Sequence" and "Random" action for executed macro 2026-03-21 00:05:58 +01:00
WarmUpTill
f66bec8caf Add option to expose current settings of source as temp var 2026-03-20 23:40:05 +01:00
WarmUpTill
3eb79e3adb Scroll to new macro segments
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
2026-03-19 20:21:08 +01:00
WarmUpTill
07e2ac3ca0 Fix macro list / macro edit area splitter resizing
When horizontally large widgets (e.g. the Window condition) were part of
the currently selected macro moving the splitter would result in it
either fully hiding the macro list or the macro edit area.

Now the list and edit area can be resized smoothly.
2026-03-19 20:21:08 +01:00
WarmUpTill
8e2c466c2d Rework libproc2 API version check
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 previous version was not behaving as expected for flatpak builds
2026-03-15 16:27:01 +01:00
WarmUpTill
cc68e2366c Fix file selection displaying resolved variable values
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
2026-03-15 13:17:06 +01:00
WarmUpTill
7a0e08b0d8 Adapt to enable testing and add more tests
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
2026-03-14 13:34:50 +01:00
WarmUpTill
be8744f0d0 Add action trigger modes
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
* always -> same as old behavior, if "on change" was disabled
* results changes -> same as old behavior, if "on change" was enabled
* any condition changes
* any condition changes and evaluates to true
2026-03-12 20:45:57 +01:00
WarmUpTill
d4425df694 Add FirstRunWizard 2026-03-12 20:45:57 +01:00
WarmUpTill
70e5f6002d Cleanup 2026-03-12 20:45:57 +01:00
WarmUpTill
ff98ec36d6 Show dialogs after OBS_FRONTEND_EVENT_FINISHED_LOADING is fired
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
2026-02-28 13:09:27 +01:00
WarmUpTill
4966802f14 Fix first action of paused macros being executed
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
2026-02-26 21:20:37 +01:00
kak hil imup
b23a90557f Fixed get_filename_component command call
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
2026-02-22 20:51:48 +01:00
WarmUpTill
d6ea815b85 Expose "Similarity Rating" as temp var when pattern matching 2026-02-22 13:27:20 +01:00
WarmUpTill
f35ef558ee Fix crash when restoring scene item transition in parallel
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
2026-02-19 20:28:10 +01:00
WarmUpTill
d7ff9088f8 Enable selecting multiple days in simple view of date condition 2026-02-19 20:11:12 +01:00
WarmUpTill
7048925d6e Add DayOfWeekSelector 2026-02-19 20:11:12 +01:00
WarmUpTill
fcb3ea50d3 Add QTimer::singleShot() wrappers to display startup dialogs on macOS 2026-02-19 20:11:12 +01:00
WarmUpTill
87c45e2b32 Focus name edit for new items 2026-02-19 20:11:12 +01:00
WarmUpTill
eb5046a9d6 Add websocket callbacks to run macros and set variables
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
2026-02-17 22:07:54 +01:00
WarmUpTill
58c05f3f6e Fix race condition in plugin post load steps 2026-02-17 22:07:54 +01:00
WarmUpTill
0b284da3de Fix stream deck condition not matching key "down" state properly 2026-02-17 22:07:54 +01:00
WarmUpTill
03fe7016e4 Fix crash in game capture condition when receiving null data
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
2026-02-07 22:38:26 +01:00
WarmUpTill
2ea89912a3 Fix transition duration selection not being editable
Some checks failed
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
Check locale / ubuntu64 (push) Has been cancelled
2026-02-07 21:32:58 +01:00
WarmUpTill
78d2efa083 Skip invalid json object settings value 2026-02-07 21:32:58 +01:00
WarmUpTill
149fce1c2b Show tooltip updates while the tooltip is visible 2026-02-07 21:32:58 +01:00
WarmUpTill
2dd9120265 Add tooltip for last execution time of macro in macro list 2026-02-07 21:32:58 +01:00
WarmUpTill
3a9f315b67 Highlight macros blocked by the "on change" setting in the macro list 2026-02-07 21:32:58 +01:00
WarmUpTill
6ba01dca39 Fix Twitch event sub reconnect handling
If the connection was aborted by the local machine, the active
subsciptions were not cleared causing the new connection not attempt to
register any new subsciptions.

An event sub connection without any active subsciptions will be dropped
by Twitch after a certain amount of time.

Additionally the reconnection logic was triggering to frequently causing
unecessary load.
2026-02-07 21:32:58 +01:00
WarmUpTill
ad1f1effeb Add option to pick random value 2026-02-07 21:32:58 +01:00
WarmUpTill
0a53a8649f Add initializer_list support and set default add dialog window title 2026-02-07 21:32:57 +01:00
WarmUpTill
7ea721f9f1 Add option to set transition when changing scene item visibility 2026-01-27 19:28:45 +01:00
WarmUpTill
718a899a98 Move functions 2026-01-27 19:28:45 +01:00
WarmUpTill
4c493451f4 Keep URL parameters if parameters input is not used 2026-01-18 19:44:19 +01:00
WarmUpTill
a8d483f5a7 Enable un-/pausing all macros in a group 2026-01-13 21:45:04 +01:00
WarmUpTill
dc786a5313 Cleanup 'find and replace' layout in variable action 2026-01-13 21:44:17 +01:00
WarmUpTill
628a4d896c Add option to stop plugin in case unclean shutdown is detected 2026-01-05 10:58:55 +01:00
WarmUpTill
74a9681c8e Fix deadlock when opening settings window 2026-01-05 10:58:55 +01:00
WarmUpTill
faf52c38bb Add temp var for window text 2026-01-05 10:58:55 +01:00
WarmUpTill
bad1a548fb Fix resource table shortcuts conflicting with OBS shortcuts
This could happen when adding the variable tab as a dock.
The problem is visible in the OBS log:
QAction::event: Ambiguous shortcut overload: Del
2025-12-27 20:38:53 +01:00
WarmUpTill
f706416df5 Fix warnings
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-12-21 14:33:08 +01:00
WarmUpTill
c344c88acd Add support for scenes of secondary canvases in source selection 2025-12-21 14:33:08 +01:00
WarmUpTill
2cd9e61717 Adapt paths to new OBS directory structure 2025-12-21 14:33:08 +01:00
WarmUpTill
6314de8f37 Add option to always show feature tabs
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-12-14 12:54:17 +01:00
WarmUpTill
6bfcabc4af Add option to get channel info 2025-12-14 12:54:17 +01:00
WarmUpTill
69711d973a Fix memory leak 2025-12-14 12:54:17 +01:00
WarmUpTill
225913b44d Fix warnings 2025-12-14 10:42:51 +01:00
WarmUpTill
7c6d657fdd Add temp var for last recording save path 2025-12-14 10:42:51 +01:00
WarmUpTill
12e38274f5 Fix freeze on shutdown / scene collection change 2025-12-14 10:31:45 +01:00
WarmUpTill
5d49e8825b Add group support to MacroSelection 2025-12-14 10:30:58 +01:00
WarmUpTill
21c34356ed Add macro group helper functions 2025-12-14 10:30:58 +01:00
WarmUpTill
266e470509 Remove unused function 2025-12-14 10:30:58 +01:00
WarmUpTill
de20c93b14 Rearrange layout of "General" tab
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-12-06 19:27:54 +01:00
WarmUpTill
b0d877db8c Cleanup unnecessary nested layouts 2025-12-06 19:27:54 +01:00
WarmUpTill
caf9e59475 Remove option to disable widget caching
Option was only introduced in case instabilities arise.
However, there weren't any reports of issues for a long time.
2025-12-06 19:27:54 +01:00
WarmUpTill
5f6982b5bb Add macro search functionality
Allows search by ...
* Macro name
* Segment type
* Segment label
2025-12-06 19:27:54 +01:00
WarmUpTill
ce399cc647 Move functions 2025-12-06 19:27:54 +01:00
WarmUpTill
03f67534c7 Default to main canvas in scene selection
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
New scene selections would have no canvas selected at all.
This would cause issues in places which do not support an empty canvas
selection.

For example, the "scene changed" check of the scene condition would not
work as without any canvas it is not clear which scene to check for.
2025-12-05 17:30:16 +01:00
WarmUpTill
246667e65e Fix scene list scene switches not working
* Assume default canvas to be main canvas
* Add proper handling for the preview scene for next / previous
* Only display canvas warning if a canvas was selected
* Fix canvas selection not showing backend value
2025-12-05 17:30:16 +01:00
WarmUpTill
68fe7716e7 Add special handling for main canvas 2025-12-05 17:30:16 +01:00
WarmUpTill
98cc710b4a Add tooltip to "verify timestamps" option 2025-12-05 17:30:16 +01:00
WarmUpTill
c602c30c54 Display warning if Twitch token expired or is invalid 2025-12-05 17:30:16 +01:00
WarmUpTill
cc1b89fe1d Fix crash on macro tab setup 2025-12-05 17:30:16 +01:00
WarmUpTill
1de4ef7ec2 Add retry mechanism for downloading dependencies 2025-12-05 17:30:16 +01:00
WarmUpTill
e29060fdea Throttle Twitch API access for at least 1 second
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-12-03 18:30:17 +01:00
WarmUpTill
a0e6e6f528 Add options to close source dialogs
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-11-14 13:56:03 +01:00
WarmUpTill
e30d5845fa Run macro via dock button in parallel and ignore pause 2025-11-14 13:56:03 +01:00
WarmUpTill
d12911cd02 Fix typo 2025-11-14 13:56:03 +01:00
WarmUpTill
b8ecc40e8c Add search clear button to the variable tab 2025-11-14 12:38:24 +01:00
WarmUpTill
8ec4849b1d Add option copy value of another variable 2025-11-14 12:38:24 +01:00
WarmUpTill
e6e9f3a831 Add temp var for last replay buffer save path
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-11-13 21:06:29 +01:00
WarmUpTill
555f7c1381 Add option to enable dock containing variable tab
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-11-10 18:45:06 +01:00
WarmUpTill
f0f8b0fd92 Add option to search in variable tab 2025-11-10 18:45:06 +01:00
WarmUpTill
661e83162f Cleanup 2025-11-10 18:45:06 +01:00
WarmUpTill
4606f80a9d Enable filtering in "Hotkey" action 2025-11-10 18:45:06 +01:00
WarmUpTill
9c109742fb Add option to change action state based on label and 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
2025-11-05 20:49:24 +01:00
WarmUpTill
b8b0682aaf Add support for else actions to MacroSegmentSelection 2025-11-05 20:49:24 +01:00
WarmUpTill
f93175db77 Sort scenes alphabetically
Prior to implementing canvas suppor they were in the order they are in
the scenes dock.
After switching from obs_frontend_get_scene_names() to
obs_canvas_enum_scenes() this seems to not always be the case.
2025-11-05 20:49:24 +01:00
WarmUpTill
8f92ba3ffa Increase slide index selection maximum to 9999 2025-11-05 20:49:24 +01:00
WarmUpTill
0d56de11d1 Refactor to support loading macro properties on import 2025-11-05 20:49:24 +01:00
WarmUpTill
e1020a1909 Fix poad load steps being executed too frequently
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-10-30 20:48:33 +01:00
WarmUpTill
8b0bd4193b Fix temp var save / loading not working 2025-10-30 20:48:33 +01:00
WarmUpTill
d55bb6bc86 Fall back to obs_frontend_get_current_scene()
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
If there wasn't any scene change yet GetCurrentScene() would always
return nullptr and break various scene checks.
For example, this could happen when startup up a fresh OBS install for
the first time.
2025-10-29 12:26:44 +01:00
WarmUpTill
0583331bfd Fix scene selection not working without secondary canvases 2025-10-29 12:26:44 +01:00
WarmUpTill
6932de866d Refactor Twitch event server migration and reconnect handling
Some checks failed
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
Check locale / ubuntu64 (push) Has been cancelled
This should avoid any events being lost due to server migration.
2025-10-28 19:20:52 +01:00
WarmUpTill
00db0cf7c4 CI: Refactor OpenSSL handling on MacOS to support MQTT SSL 2025-10-28 19:20:52 +01:00
WarmUpTill
e9baf27ca2 Add helper to find recent versions of OpenSSL on Windows 2025-10-28 19:20:52 +01:00
WarmUpTill
b1a5db0c9c Fix crash when switching macros after deleting one containing temp refs 2025-10-28 19:20:52 +01:00
WarmUpTill
8f3b868fd9 Limit projector action to main canvas and improve layout 2025-10-28 19:20:52 +01:00
WarmUpTill
b3bf89840b Add GetPath() 2025-10-28 19:20:52 +01:00
WarmUpTill
84f7d0d214 Add SSL support to MQTT connections
Also fixes crash on startup if SSL was used while there was no support
for encrypted connections yet
2025-10-28 19:20:52 +01:00
WarmUpTill
e1164c4fa3 Refactor help icon usage 2025-10-28 19:20:52 +01:00
WarmUpTill
4534b23bad Enable Windows and MacOS build with OBS versions older than 31.1.1 2025-10-28 19:20:52 +01:00
370 changed files with 31560 additions and 6785 deletions

View File

@@ -19,14 +19,9 @@ inputs:
runs: runs:
using: 'composite' using: 'composite'
steps: steps:
- name: Setup cmake
uses: jwlawson/actions-setup-cmake@v1.13
with:
cmake-version: '3.28.x'
- name: Restore cached dependencies - name: Restore cached dependencies
id: restore-cache id: restore-cache
uses: actions/cache@v4 uses: actions/cache@v5
with: with:
path: ${{ env.DEP_DIR }} path: ${{ env.DEP_DIR }}
key: ${{ env.DEP_DIR }}-${{ runner.os }}-${{ inputs.target }} key: ${{ env.DEP_DIR }}-${{ runner.os }}-${{ inputs.target }}

View File

@@ -29,6 +29,7 @@ runs:
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH
echo "/home/linuxbrew/.linuxbrew/opt/clang-format@16/bin" >> $GITHUB_PATH echo "/home/linuxbrew/.linuxbrew/opt/clang-format@16/bin" >> $GITHUB_PATH
brew update
brew install --quiet zsh brew install --quiet zsh
echo ::endgroup:: echo ::endgroup::
@@ -52,6 +53,7 @@ runs:
if (( ${changes[(I)(*.c|*.h|*.cpp|*.hpp|*.m|*.mm)]} )) { if (( ${changes[(I)(*.c|*.h|*.cpp|*.hpp|*.m|*.mm)]} )) {
echo ::group::Install clang-format-16 echo ::group::Install clang-format-16
brew update
brew install --quiet obsproject/tools/clang-format@16 brew install --quiet obsproject/tools/clang-format@16
echo ::endgroup:: echo ::endgroup::

View File

@@ -8,6 +8,7 @@ package 'libcurl4-openssl-dev'
package 'libxtst-dev' package 'libxtst-dev'
package 'libxss-dev' package 'libxss-dev'
package 'libopencv-dev' package 'libopencv-dev'
package 'libopencv-contrib-dev'
package 'libtesseract-dev' package 'libtesseract-dev'
package 'libproc2-dev' package 'libproc2-dev'
package 'libusb-1.0-0-dev' package 'libusb-1.0-0-dev'

View File

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

View File

@@ -284,30 +284,56 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
case ${host_os} { case ${host_os} {
macos) macos)
local opencv_dir="${project_root}/deps/opencv" local opencv_dir="${project_root}/deps/opencv"
local opencv_build_dir="${opencv_dir}/build_${target##*-}" local opencv_contrib_dir="${project_root}/deps/opencv_contrib"
local opencv_install_x86="${opencv_dir}/install_x86_64"
local opencv_install_arm="${opencv_dir}/install_arm64"
local -a opencv_cmake_args=( local -a opencv_cmake_args_common=(
-DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_TYPE=Release
-DBUILD_LIST=core,imgproc,objdetect -DBUILD_LIST=core,imgproc,objdetect,xobjdetect,dnn
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64} -DOPENCV_EXTRA_MODULES_PATH="${opencv_contrib_dir}/modules"
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15} -DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
-DCMAKE_PREFIX_PATH="${advss_dep_path};${_plugin_deps}" -DCMAKE_PREFIX_PATH="${advss_dep_path};${_plugin_deps}"
-DCMAKE_INSTALL_PREFIX="${advss_dep_path}" -DWITH_KLEIDICV=OFF
) )
if [ "${target}" != "macos-x86_64" ]; then
opencv_cmake_args+=(-DWITH_IPP=OFF)
fi
pushd ${opencv_dir} pushd ${opencv_dir}
log_info "Configure OpenCV ..."
cmake -S . -B ${opencv_build_dir} ${opencv_cmake_args}
log_info "Building OpenCV ..." log_info "Configure OpenCV (x86_64) ..."
cmake --build ${opencv_build_dir} --config Release cmake -S . -B build_x86_64 ${opencv_cmake_args_common} \
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
-DCMAKE_PROJECT_INCLUDE="${SCRIPT_HOME}/opencv-force-processor-x86_64.cmake" \
-DCMAKE_INSTALL_PREFIX="${opencv_install_x86}" \
-DWITH_IPP=OFF
log_info "Installing OpenCV ..." log_info "Building OpenCV (x86_64) ..."
cmake --install ${opencv_build_dir} --prefix "${advss_dep_path}" --config Release || true cmake --build build_x86_64 --config Release
log_info "Installing OpenCV (x86_64) ..."
cmake --install build_x86_64 --prefix "${opencv_install_x86}" --config Release || true
log_info "Configure OpenCV (arm64) ..."
cmake -S . -B build_arm64 ${opencv_cmake_args_common} \
-DCMAKE_OSX_ARCHITECTURES=arm64 \
-DCMAKE_INSTALL_PREFIX="${opencv_install_arm}"
log_info "Building OpenCV (arm64) ..."
cmake --build build_arm64 --config Release
log_info "Installing OpenCV (arm64) ..."
cmake --install build_arm64 --prefix "${opencv_install_arm}" --config Release || true
log_info "Merging OpenCV into universal binaries ..."
cp -R "${opencv_install_arm}/." "${advss_dep_path}"
for arm_lib in ${opencv_install_arm}/lib/**/*.(dylib|a)(.); do
local rel="${arm_lib#${opencv_install_arm}/}"
local x86_lib="${opencv_install_x86}/${rel}"
if [[ -f "${x86_lib}" ]]; then
lipo -create "${x86_lib}" "${arm_lib}" -output "${advss_dep_path}/${rel}"
fi
done
rm -rf "${opencv_install_x86}" "${opencv_install_arm}"
popd popd
local leptonica_dir="${project_root}/deps/leptonica" local leptonica_dir="${project_root}/deps/leptonica"
@@ -319,7 +345,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15} -DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
-DSW_BUILD=OFF -DSW_BUILD=OFF
-DOPENJPEG_SUPPORT=OFF -DOPENJPEG_SUPPORT=OFF
-DLIBWEBP_SUPPORT=OFF -DENABLE_WEBP=OFF
-DCMAKE_DISABLE_FIND_PACKAGE_GIF=TRUE -DCMAKE_DISABLE_FIND_PACKAGE_GIF=TRUE
-DCMAKE_DISABLE_FIND_PACKAGE_JPEG=TRUE -DCMAKE_DISABLE_FIND_PACKAGE_JPEG=TRUE
-DCMAKE_DISABLE_FIND_PACKAGE_TIFF=TRUE -DCMAKE_DISABLE_FIND_PACKAGE_TIFF=TRUE
@@ -394,8 +420,11 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
popd popd
pushd ${advss_dep_path}
log_info "Prepare openssl ..." log_info "Prepare openssl ..."
rm -rf ${advss_dep_path}/openssl ${advss_dep_path}/openssl_build
mkdir ${advss_dep_path}/openssl_build
pushd ${advss_dep_path}/openssl_build
rm -rf openssl rm -rf openssl
git clone https://github.com/openssl/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 mv openssl openssl_x86
@@ -403,25 +432,27 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
log_info "Building openssl x86 ..." log_info "Building openssl x86 ..."
export MACOSX_DEPLOYMENT_TARGET=10.9 export MACOSX_DEPLOYMENT_TARGET=10.9
cd openssl_x86 pushd openssl_x86
./Configure darwin64-x86_64-cc shared ./Configure darwin64-x86_64-cc no-shared no-module no-zlib --prefix=${advss_dep_path}
make make -j$(nproc)
popd
log_info "Building openssl arm ..." log_info "Building openssl arm ..."
export MACOSX_DEPLOYMENT_TARGET=10.15 export MACOSX_DEPLOYMENT_TARGET=10.15
cd ../openssl_arm pushd openssl_arm
./Configure enable-rc5 zlib darwin64-arm64-cc no-asm ./Configure enable-rc5 darwin64-arm64-cc no-shared no-module no-asm no-zlib --prefix=${advss_dep_path}
make make -j$(nproc)
log_info "Install openssl ..."
make install
popd
log_info "Combine arm and x86 openssl binaries ..." log_info "Combine arm and x86 openssl binaries ..."
cd .. lipo -create openssl_x86/libcrypto.a openssl_arm/libcrypto.a -output ${advss_dep_path}/lib/libcrypto.a
mkdir openssl-combined lipo -create openssl_x86/libssl.a openssl_arm/libssl.a -output ${advss_dep_path}/lib/libssl.a
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_x86 openssl_arm
rm -rf openssl_arm
popd popd
pushd ${project_root}/deps/libusb pushd ${project_root}/deps/libusb
@@ -439,14 +470,16 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
mkdir ${project_root}/deps/libusb/out_x86 mkdir ${project_root}/deps/libusb/out_x86
./autogen.sh ./autogen.sh
./configure --host=x86_64-apple-darwin --prefix=${advss_dep_path} ./configure --host=x86_64-apple-darwin --prefix=${advss_dep_path}
make && make install make -j$(nproc)
make install
log_info "Configure libusb arm ..." log_info "Configure libusb arm ..."
make clean make clean
rm -r ${project_root}/deps/libusb/out_x86 rm -r ${project_root}/deps/libusb/out_x86
mkdir ${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 ./configure --host=aarch64-apple-darwin --prefix=${project_root}/deps/libusb/out_x86
make && make install make -j$(nproc)
make install
log_info "Building libusb arm ..." log_info "Building libusb arm ..."
make clean make clean
@@ -459,7 +492,8 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
export MACOSX_DEPLOYMENT_TARGET=10.15 export MACOSX_DEPLOYMENT_TARGET=10.15
mkdir ${project_root}/deps/libusb/out_arm mkdir ${project_root}/deps/libusb/out_arm
./configure --host=aarch64-apple-darwin --prefix=${project_root}/deps/libusb/out_arm ./configure --host=aarch64-apple-darwin --prefix=${project_root}/deps/libusb/out_arm
make && make install make -j$(nproc)
make install
log_info "Combine arm and x86 libusb binaries ..." log_info "Combine arm and x86 libusb binaries ..."
lipo -create ${project_root}/deps/libusb/out_x86/lib/libusb-1.0.0.dylib \ lipo -create ${project_root}/deps/libusb/out_x86/lib/libusb-1.0.0.dylib \
@@ -491,8 +525,8 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
-DPAHO_BUILD_SHARED=OFF -DPAHO_BUILD_SHARED=OFF
-DPAHO_BUILD_STATIC=ON -DPAHO_BUILD_STATIC=ON
-DPAHO_WITH_MQTT_C=ON -DPAHO_WITH_MQTT_C=ON
-DOPENSSL_ROOT_DIR="${advss_dep_path}" -DPAHO_WITH_SSL=ON
-DPAHO_WITH_SSL=OFF # TODO: figure out linking issues with openssl -DOPENSSL_USE_STATIC_LIBS=ON
) )
pushd ${mqtt_dir} pushd ${mqtt_dir}

View File

@@ -253,12 +253,12 @@ ${_usage_host:-}"
macos-*) macos-*)
if (( ${+CI} )) typeset -gx NSUnbufferedIO=YES if (( ${+CI} )) typeset -gx NSUnbufferedIO=YES
local openssl_lib_dir="${advss_deps_path}/openssl-combined/"
local openssl_include_dir="${advss_deps_path}/openssl/include"
cmake_args+=( cmake_args+=(
-DOPENSSL_INCLUDE_DIR="${openssl_include_dir}" -DCMAKE_PREFIX_PATH="${advss_deps_path}"
-DOPENSSL_LIBRARIES="${openssl_lib_dir}/libcrypto.a;${openssl_lib_dir}/libssl.a" -DOPENSSL_ROOT_DIR="${advss_deps_path}"
-DOPENSSL_INCLUDE_DIR="${advss_deps_path}/include"
-DOPENSSL_CRYPTO_LIBRARY="${advss_deps_path}/lib/libcrypto.a"
-DOPENSSL_SSL_LIBRARY="${advss_deps_path}/lib/libssl.a"
--preset ${_preset} --preset ${_preset}
) )

View File

@@ -259,7 +259,7 @@ ${_usage_host:-}"
cmake --build build_${target##*-} --config ${config} -t package ${cmake_args} cmake --build build_${target##*-} --config ${config} -t package ${cmake_args}
# Mark certain deps as optional # Mark certain deps as optional
build-aux/CI/linux/demote-deps.sh ${project_root}/release/*.deb Recommends '(mqtt)|(opencv)|(tesseract)|(usb)|(x11)' build-aux/CI/linux/demote-deps.sh ${project_root}/release/*.deb Recommends '(mqtt)|(opencv)|(tesseract)|(usb)|(x11)|(libobs)'
if [ ! -e ${project_root}/release/${output_name}.deb ]; then if [ ! -e ${project_root}/release/${output_name}.deb ]; then
mv ${project_root}/release/*.deb ${project_root}/release/${output_name}.deb mv ${project_root}/release/*.deb ${project_root}/release/${output_name}.deb

View File

@@ -106,6 +106,7 @@ function Build {
$ADVSSDepPath = "$(Resolve-Path -Path ${ProjectRoot}/${OutDirName})" $ADVSSDepPath = "$(Resolve-Path -Path ${ProjectRoot}/${OutDirName})"
$OpenCVPath = "${ProjectRoot}/deps/opencv" $OpenCVPath = "${ProjectRoot}/deps/opencv"
$OpenCVContribPath = "${ProjectRoot}/deps/opencv_contrib"
$OpenCVBuildPath = "${OpenCVPath}/build" $OpenCVBuildPath = "${OpenCVPath}/build"
Push-Location -Stack BuildOpenCVTemp Push-Location -Stack BuildOpenCVTemp
@@ -115,7 +116,8 @@ function Build {
"-DCMAKE_BUILD_TYPE=Release" "-DCMAKE_BUILD_TYPE=Release"
"-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}" "-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}"
"-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}" "-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}"
"-DBUILD_LIST=core,imgproc,objdetect" "-DBUILD_LIST=core,imgproc,objdetect,xobjdetect"
"-DOPENCV_EXTRA_MODULES_PATH:PATH=${OpenCVContribPath}/modules"
) )
Log-Information "Configuring OpenCV..." Log-Information "Configuring OpenCV..."
@@ -234,8 +236,24 @@ function Build {
"-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}" "-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}"
"-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}" "-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}"
"-DPAHO_WITH_MQTT_C=ON" "-DPAHO_WITH_MQTT_C=ON"
"-DPAHO_WITH_SSL=ON"
) )
# Try to find OpenSSL installed via winget
$pf64 = Join-Path $Env:ProgramFiles "OpenSSL-Win64"
$pf = Join-Path $Env:ProgramFiles "OpenSSL"
$possibleDirs = @($pf64, $pf)
$opensslDir = $possibleDirs | Where-Object { Test-Path (Join-Path $_ "include\openssl\ssl.h") } | Select-Object -First 1
if ($opensslDir) {
Write-Host "Detected OpenSSL at: $opensslDir"
$MqttCmakeArgs += "-DOPENSSL_ROOT_DIR=$opensslDir"
$MqttCmakeArgs += "-DOPENSSL_CRYPTO_LIBRARY=$opensslDir\lib\VC\x64\MD\libcrypto.lib"
$MqttCmakeArgs += "-DOPENSSL_SSL_LIBRARY=$opensslDir\lib\VC\x64\MD\libssl.lib"
} else {
Write-Warning "OpenSSL not found - maybe cmake will find it ..."
}
Log-Information "Configuring paho.mqtt.cpp..." Log-Information "Configuring paho.mqtt.cpp..."
Invoke-External cmake -S ${MqttPath} -B ${MqttBuildPath} @MqttCmakeArgs Invoke-External cmake -S ${MqttPath} -B ${MqttBuildPath} @MqttCmakeArgs

View File

@@ -68,14 +68,53 @@ function Package {
Remove-Item @RemoveArgs Remove-Item @RemoveArgs
Log-Group "Archiving ${ProductName}..." $ReleasePath = "${ProjectRoot}/release/${Configuration}"
$CompressArgs = @{ $NewBinPath = "${ReleasePath}/${ProductName}/bin/64bit"
Path = (Get-ChildItem -Path "${ProjectRoot}/release/${Configuration}" -Exclude "${OutputName}*.*") $NewDataPath = "${ReleasePath}/${ProductName}/data"
CompressionLevel = 'Optimal' $CIWindowsDir = "${ProjectRoot}/build-aux/CI/windows"
DestinationPath = "${ProjectRoot}/release/${OutputName}.zip"
Verbose = ($Env:CI -ne $null) # --- Recommended zip (new layout, extract to %ProgramData%\obs-studio\) ---
Log-Group "Archiving ${ProductName} (recommended)..."
$RecStaging = "${ProjectRoot}/release/zip-staging-rec"
Remove-Item -Path $RecStaging -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $RecStaging | Out-Null
Copy-Item -Path "${CIWindowsDir}/README.txt" -Destination "${RecStaging}/README.txt"
if ( Test-Path -Path $NewBinPath ) {
$RecBinPath = "${RecStaging}/${ProductName}/bin/64bit"
New-Item -ItemType Directory -Force -Path $RecBinPath | Out-Null
Copy-Item -Path "${NewBinPath}/*" -Destination $RecBinPath -Recurse -Force
} }
Compress-Archive -Force @CompressArgs if ( Test-Path -Path $NewDataPath ) {
$RecDataPath = "${RecStaging}/${ProductName}/data"
New-Item -ItemType Directory -Force -Path $RecDataPath | Out-Null
Copy-Item -Path "${NewDataPath}/*" -Destination $RecDataPath -Recurse -Force
}
Compress-Archive -Force -Path (Get-ChildItem -Path $RecStaging) `
-CompressionLevel Optimal `
-DestinationPath "${ProjectRoot}/release/${OutputName}.zip"
Remove-Item -Path $RecStaging -Recurse -Force
Log-Group
# --- Legacy zip (old layout, extract to OBS install directory) ---
Log-Group "Archiving ${ProductName} (portable)..."
$PortableStaging = "${ProjectRoot}/release/zip-staging-portable"
Remove-Item -Path $PortableStaging -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $PortableStaging | Out-Null
Copy-Item -Path "${CIWindowsDir}/README-portable.txt" -Destination "${PortableStaging}/README.txt"
if ( Test-Path -Path $NewBinPath ) {
$PortableBinPath = "${PortableStaging}/obs-plugins/64bit"
New-Item -ItemType Directory -Force -Path $PortableBinPath | Out-Null
Copy-Item -Path "${NewBinPath}/*" -Destination $PortableBinPath -Recurse -Force
}
if ( Test-Path -Path $NewDataPath ) {
$PortableDataPath = "${PortableStaging}/data/obs-plugins/${ProductName}"
New-Item -ItemType Directory -Force -Path $PortableDataPath | Out-Null
Copy-Item -Path "${NewDataPath}/*" -Destination $PortableDataPath -Recurse -Force
}
Compress-Archive -Force -Path (Get-ChildItem -Path $PortableStaging) `
-CompressionLevel Optimal `
-DestinationPath "${ProjectRoot}/release/${OutputName}-portable.zip"
Remove-Item -Path $PortableStaging -Recurse -Force
Log-Group Log-Group
if ( ( $BuildInstaller ) ) { if ( ( $BuildInstaller ) ) {
@@ -89,7 +128,25 @@ function Package {
Log-Information 'Creating InnoSetup installer...' Log-Information 'Creating InnoSetup installer...'
Push-Location -Stack BuildTemp Push-Location -Stack BuildTemp
Ensure-Location -Path "${ProjectRoot}/release" Ensure-Location -Path "${ProjectRoot}/release"
Copy-Item -Path ${Configuration} -Destination Package -Recurse Remove-Item -Path Package -Recurse -Force -ErrorAction SilentlyContinue
# Recommended layout (for %ProgramData%\obs-studio\plugins\)
$PkgRec = "Package/recommended"
New-Item -ItemType Directory -Force -Path $PkgRec | Out-Null
Copy-Item -Path "${Configuration}/*" -Destination $PkgRec -Recurse -Force
# Legacy layout (for OBS installation directory)
if ( Test-Path "${Configuration}/${ProductName}/bin/64bit" ) {
$PkgLegBin = "Package/portable/obs-plugins/64bit"
New-Item -ItemType Directory -Force -Path $PkgLegBin | Out-Null
Copy-Item -Path "${Configuration}/${ProductName}/bin/64bit/*" -Destination $PkgLegBin -Recurse -Force
}
if ( Test-Path "${Configuration}/${ProductName}/data" ) {
$PkgLegData = "Package/portable/data/obs-plugins/${ProductName}"
New-Item -ItemType Directory -Force -Path $PkgLegData | Out-Null
Copy-Item -Path "${Configuration}/${ProductName}/data/*" -Destination $PkgLegData -Recurse -Force
}
Invoke-External iscc ${IsccFile} /O"${ProjectRoot}/release" /F"${OutputName}-Installer" Invoke-External iscc ${IsccFile} /O"${ProjectRoot}/release" /F"${OutputName}-Installer"
Remove-Item -Path Package -Recurse Remove-Item -Path Package -Recurse
Pop-Location -Stack BuildTemp Pop-Location -Stack BuildTemp

View File

@@ -0,0 +1,7 @@
# Injected via CMAKE_PROJECT_INCLUDE after opencv's project() call. Forces
# CMAKE_SYSTEM_PROCESSOR into the cache so that third-party subdirectories
# (mlas) that read the cache directly see x86_64 instead of the arm64 host
# processor on Apple Silicon CI runners.
set(CMAKE_SYSTEM_PROCESSOR
x86_64
CACHE INTERNAL "" FORCE)

View File

@@ -35,14 +35,21 @@ if (( ! (${skips[(Ie)all]} + ${skips[(Ie)deps]}) )) {
sudo apt-get install ${apt_args} gcc-${${target##*-}//_/-}-linux-gnu g++-${${target##*-}//_/-}-linux-gnu sudo apt-get install ${apt_args} gcc-${${target##*-}//_/-}-linux-gnu g++-${${target##*-}//_/-}-linux-gnu
} }
sudo add-apt-repository --yes ppa:obsproject/obs-studio local dist_version
sudo apt update read -r dist_version <<< "$(source /etc/os-release; print "${VERSION_ID}")"
sudo apt-get install ${apt_args} \ # The OBS PPA may not yet support newer Ubuntu versions.
build-essential \ # On those, use the native obs-studio packages and install libobs-dev separately,
libgles2-mesa-dev \ # as it is not bundled in obs-studio itself on newer Ubuntu releases.
libsimde-dev \ local -a obs_packages=(build-essential libgles2-mesa-dev libsimde-dev obs-studio)
obs-studio if is-at-least 26.04 ${dist_version}; then
obs_packages+=(libobs-dev)
else
sudo add-apt-repository --yes ppa:obsproject/obs-studio
sudo apt update
fi
sudo apt-get install ${apt_args} ${obs_packages}
local -a _qt_packages=() local -a _qt_packages=()

View File

@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
submodules: "recursive" submodules: "recursive"
- name: check_libobs_revision - name: check_libobs_revision
@@ -50,7 +50,7 @@ jobs:
mv ../*.deb . mv ../*.deb .
- name: Publish - name: Publish
if: success() if: success()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: "obs-scene-switcher.deb" name: "obs-scene-switcher.deb"
path: ${{ github.workspace }}/*.deb path: ${{ github.workspace }}/*.deb

View File

@@ -6,7 +6,7 @@ on:
description: "Project name detected by parsing build spec file" description: "Project name detected by parsing build spec file"
value: ${{ jobs.check-event.outputs.pluginName }} value: ${{ jobs.check-event.outputs.pluginName }}
env: env:
DEP_DIR: .deps/advss-build-dependencies-2 DEP_DIR: .deps/advss-build-dependencies-6
jobs: jobs:
check-event: check-event:
name: Check GitHub Event Data 🔎 name: Check GitHub Event Data 🔎
@@ -22,7 +22,7 @@ jobs:
commitHash: ${{ steps.setup.outputs.commitHash }} commitHash: ${{ steps.setup.outputs.commitHash }}
pluginName: ${{ steps.setup.outputs.pluginName }} pluginName: ${{ steps.setup.outputs.pluginName }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Check Event Data ☑️ - name: Check Event Data ☑️
@@ -81,7 +81,7 @@ jobs:
run: run:
shell: zsh --no-rcs --errexit --pipefail {0} shell: zsh --no-rcs --errexit --pipefail {0}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
fetch-depth: 0 fetch-depth: 0
@@ -111,7 +111,7 @@ jobs:
sudo xcode-select --switch /Applications/Xcode_16.1.0.app/Contents/Developer sudo xcode-select --switch /Applications/Xcode_16.1.0.app/Contents/Developer
print '::endgroup::' print '::endgroup::'
- uses: actions/cache@v4 - uses: actions/cache@v5
id: ccache-cache id: ccache-cache
with: with:
path: ${{ github.workspace }}/.ccache path: ${{ github.workspace }}/.ccache
@@ -163,13 +163,13 @@ jobs:
codesignPass: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }} codesignPass: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
- name: Upload Artifacts 📡 - name: Upload Artifacts 📡
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-macos-universal-${{ needs.check-event.outputs.commitHash }} name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-macos-universal-${{ needs.check-event.outputs.commitHash }}
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-macos-universal.* path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-macos-universal.*
- name: Upload Debug Symbol Artifacts 🪲 - name: Upload Debug Symbol Artifacts 🪲
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
if: ${{ needs.check-event.outputs.config == 'Release' }} if: ${{ needs.check-event.outputs.config == 'Release' }}
with: with:
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-macos-universal-${{ needs.check-event.outputs.commitHash }}-dSYMs name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-macos-universal-${{ needs.check-event.outputs.commitHash }}-dSYMs
@@ -183,7 +183,7 @@ jobs:
run: run:
shell: bash shell: bash
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
fetch-depth: 0 fetch-depth: 0
@@ -202,7 +202,7 @@ jobs:
echo "pluginName=${product_name}" >> $GITHUB_OUTPUT echo "pluginName=${product_name}" >> $GITHUB_OUTPUT
echo "pluginVersion=${git_tag}" >> $GITHUB_OUTPUT echo "pluginVersion=${git_tag}" >> $GITHUB_OUTPUT
- uses: actions/cache@v4 - uses: actions/cache@v5
id: ccache-cache id: ccache-cache
with: with:
path: ${{ github.workspace }}/.ccache path: ${{ github.workspace }}/.ccache
@@ -210,13 +210,19 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-ccache-x86_64- ${{ 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 🍺 - name: Set up Homebrew 🍺
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
- name: Install Vulkan SDK 🌋
shell: bash
run: |
wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc \
| sudo tee /etc/apt/trusted.gpg.d/lunarg.asc
sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-noble.list \
https://packages.lunarg.com/vulkan/lunarg-vulkan-noble.list
sudo apt-get update -qq
sudo apt-get install -y vulkan-sdk
echo "VULKAN_SDK=/usr" >> "$GITHUB_ENV"
- name: Build Plugin 🧱 - name: Build Plugin 🧱
uses: ./.github/actions/build-plugin uses: ./.github/actions/build-plugin
@@ -238,23 +244,111 @@ jobs:
config: ${{ needs.check-event.outputs.config }} config: ${{ needs.check-event.outputs.config }}
- name: Upload Source Tarball 🗜️ - name: Upload Source Tarball 🗜️
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-sources-${{ needs.check-event.outputs.commitHash }} name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-sources-${{ needs.check-event.outputs.commitHash }}
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-source.* path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-source.*
- name: Rename artifacts for Ubuntu 24 🏷️
run: |
: Rename artifacts for Ubuntu 24 🏷️
for f in ${{ github.workspace }}/release/*-x86_64-linux-gnu.*; do
[ -e "${f}" ] || continue
mv "${f}" "${f/x86_64-linux-gnu/x86_64-ubuntu24.04-linux-gnu}"
done
- name: Upload Artifacts 📡 - name: Upload Artifacts 📡
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-24.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*.* path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-x86_64*.*
- name: Upload debug symbol artifacts 🪲 - name: Upload debug symbol artifacts 🪲
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
if: ${{ fromJSON(needs.check-event.outputs.package) }} if: ${{ fromJSON(needs.check-event.outputs.package) }}
with: with:
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-24.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 path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-x86_64*-dbgsym.ddeb
if-no-files-found: ignore
ubuntu-26-build:
name: Build for Ubuntu 26 🐧
runs-on: ubuntu-26.04
needs: check-event
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
- name: Set Up Environment 🔧
id: setup
run: |
: Set Up Environment 🔧
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
git_tag="$(git describe --tags)"
read -r product_name product_version <<< \
"$(jq -r '. | {name, version} | join(" ")' buildspec.json)"
echo "pluginName=${product_name}" >> $GITHUB_OUTPUT
echo "pluginVersion=${git_tag}" >> $GITHUB_OUTPUT
- uses: actions/cache@v5
id: ccache-cache
with:
path: ${{ github.workspace }}/.ccache
key: ${{ runner.os }}-ccache-x86_64-${{ needs.check-event.outputs.config }}
restore-keys: |
${{ runner.os }}-ccache-x86_64-
- name: Set up Homebrew 🍺
uses: Homebrew/actions/setup-homebrew@main
- name: Build Plugin 🧱
uses: ./.github/actions/build-plugin
with:
target: x86_64
config: ${{ needs.check-event.outputs.config }}
- name: Run tests
uses: ./.github/actions/run-tests
with:
target: x86_64
config: ${{ needs.check-event.outputs.config }}
- name: Package Plugin 📀
uses: ./.github/actions/package-plugin
with:
package: ${{ fromJSON(needs.check-event.outputs.package) }}
target: x86_64
config: ${{ needs.check-event.outputs.config }}
- name: Rename artifacts for Ubuntu 26 🏷️
run: |
: Rename artifacts for Ubuntu 26 🏷️
for f in ${{ github.workspace }}/release/*-x86_64-linux-gnu.*; do
[ -e "${f}" ] || continue
mv "${f}" "${f/x86_64-linux-gnu/x86_64-ubuntu26.04-linux-gnu}"
done
- name: Upload Artifacts 📡
uses: actions/upload-artifact@v7
with:
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-26.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@v7
if: ${{ fromJSON(needs.check-event.outputs.package) }}
with:
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-26.04-x86_64-${{ needs.check-event.outputs.commitHash }}-dbgsym
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-x86_64*-dbgsym.ddeb
if-no-files-found: ignore
windows-build: windows-build:
name: Build for Windows 🪟 name: Build for Windows 🪟
@@ -264,7 +358,7 @@ jobs:
run: run:
shell: pwsh shell: pwsh
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
fetch-depth: 0 fetch-depth: 0
@@ -291,6 +385,12 @@ jobs:
target: x64 target: x64
config: ${{ needs.check-event.outputs.config }} config: ${{ needs.check-event.outputs.config }}
- name: Install Vulkan SDK 🌋
uses: jakoch/install-vulkan-sdk-action@v1
with:
install_runtime: false
cache: true
- name: Build Plugin 🧱 - name: Build Plugin 🧱
uses: ./.github/actions/build-plugin uses: ./.github/actions/build-plugin
with: with:
@@ -311,7 +411,7 @@ jobs:
package: ${{ fromJSON(needs.check-event.outputs.package) }} package: ${{ fromJSON(needs.check-event.outputs.package) }}
- name: Upload Artifacts 📡 - name: Upload Artifacts 📡
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-windows-x64-${{ needs.check-event.outputs.commitHash }} name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-windows-x64-${{ needs.check-event.outputs.commitHash }}
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-windows-x64*.* path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-windows-x64*.*

View File

@@ -5,7 +5,7 @@ jobs:
clang-format: clang-format:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: clang-format check 🐉 - name: clang-format check 🐉
@@ -17,7 +17,7 @@ jobs:
cmake-format: cmake-format:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: cmake-format check 🎛️ - name: cmake-format check 🎛️

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Check locale files - name: Check locale files
run: | run: |

View File

@@ -59,7 +59,7 @@ jobs:
esac esac
- name: Download Build Artifacts 📥 - name: Download Build Artifacts 📥
uses: actions/download-artifact@v4 uses: actions/download-artifact@v8
if: fromJSON(steps.check.outputs.validTag) if: fromJSON(steps.check.outputs.validTag)
id: download id: download
@@ -78,6 +78,7 @@ jobs:
'windows-x64;zip|exe' 'windows-x64;zip|exe'
'macos-universal;tar.xz|pkg' 'macos-universal;tar.xz|pkg'
'ubuntu-24.04-x86_64;tar.xz|deb|ddeb' 'ubuntu-24.04-x86_64;tar.xz|deb|ddeb'
'ubuntu-26.04-x86_64;tar.xz|deb|ddeb'
'sources;tar.xz' 'sources;tar.xz'
) )
@@ -106,7 +107,7 @@ jobs:
- name: Create Release 🛫 - name: Create Release 🛫
if: fromJSON(steps.check.outputs.validTag) if: fromJSON(steps.check.outputs.validTag)
id: create_release id: create_release
uses: softprops/action-gh-release@d4e8205d7e959a9107da6396278b2f1f07af0f9b uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda
with: with:
draft: true draft: true
prerelease: ${{ fromJSON(steps.check.outputs.prerelease) }} prerelease: ${{ fromJSON(steps.check.outputs.prerelease) }}

9
.gitmodules vendored
View File

@@ -28,12 +28,15 @@
[submodule "deps/libusb"] [submodule "deps/libusb"]
path = deps/libusb path = deps/libusb
url = https://github.com/libusb/libusb.git url = https://github.com/libusb/libusb.git
[submodule "deps/date"]
path = deps/date
url = https://github.com/HowardHinnant/date.git
[submodule "deps/jsoncons"] [submodule "deps/jsoncons"]
path = deps/jsoncons path = deps/jsoncons
url = https://github.com/danielaparker/jsoncons.git url = https://github.com/danielaparker/jsoncons.git
[submodule "deps/paho.mqtt.cpp"] [submodule "deps/paho.mqtt.cpp"]
path = deps/paho.mqtt.cpp path = deps/paho.mqtt.cpp
url = https://github.com/eclipse-paho/paho.mqtt.cpp.git url = https://github.com/eclipse-paho/paho.mqtt.cpp.git
[submodule "deps/opencv_contrib"]
path = deps/opencv_contrib
url = https://github.com/opencv/opencv_contrib.git
[submodule "deps/whisper.cpp"]
path = deps/whisper.cpp
url = https://github.com/ggml-org/whisper.cpp.git

View File

@@ -6,18 +6,18 @@ You have the option to ...
Both methods require [CMake](https://cmake.org/download/). Both methods require [CMake](https://cmake.org/download/).
The plugin can be compiled for OBS 27 and above, although using the latest version of OBS is recommended to support all features. The plugin can be compiled for OBS 31 and above, although using the latest version of OBS is recommended to support all features.
## Compiling in tree (recommended for development) ## Compiling in tree (recommended for development)
This section assumes that you have a working [OBS Studio development environment](https://obsproject.com/wiki/Building-OBS-Studio). This section assumes that you have a working [OBS Studio development environment](https://obsproject.com/wiki/Building-OBS-Studio).
Add the "SceneSwitcher" source directory to your obs-studio source directory under obs-studio/UI/frontend-plugins/: Add the "SceneSwitcher" source directory to your obs-studio source directory under obs-studio/plugins:
``` ```
cd obs-studio/UI/frontend-plugins/ cd obs-studio/plugins
git clone --recursive https://github.com/WarmUpTill/SceneSwitcher.git git clone --recursive https://github.com/WarmUpTill/SceneSwitcher.git
``` ```
Then modify the obs-studio/UI/frontend-plugins/CMakeLists.txt Example and add an entry for the scene switcher: Then modify the obs-studio/plugins/CMakeLists.txt and add an entry for the scene switcher:
``` ```
add_subdirectory(SceneSwitcher) add_subdirectory(SceneSwitcher)
``` ```

View File

@@ -16,6 +16,14 @@ if(BUILD_OUT_OF_TREE)
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/bootstrap.cmake" include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/bootstrap.cmake"
NO_POLICY_SCOPE) NO_POLICY_SCOPE)
include(compilerconfig) include(compilerconfig)
# OPENSSL_USE_STATIC_LIBS here ensures FindOpenSSL picks up the static libs
# preventing DLL name collisions with other plugins (e.g.NVIDIA AR SDK).
if(OS_WINDOWS)
set(OPENSSL_USE_STATIC_LIBS
ON
CACHE BOOL "Use static OpenSSL" FORCE)
include(cmake/windows/wingetssl.cmake)
endif()
include(defaults) include(defaults)
include(helpers) include(helpers)
endif() endif()
@@ -99,6 +107,8 @@ target_sources(
lib/macro/macro-action-edit.hpp lib/macro/macro-action-edit.hpp
lib/macro/macro-action-factory.cpp lib/macro/macro-action-factory.cpp
lib/macro/macro-action-factory.hpp lib/macro/macro-action-factory.hpp
lib/macro/macro-action-loop.cpp
lib/macro/macro-action-loop.hpp
lib/macro/macro-action-macro.cpp lib/macro/macro-action-macro.cpp
lib/macro/macro-action-macro.hpp lib/macro/macro-action-macro.hpp
lib/macro/macro-action-queue.cpp lib/macro/macro-action-queue.cpp
@@ -129,6 +139,9 @@ target_sources(
lib/macro/macro-dock-window.hpp lib/macro/macro-dock-window.hpp
lib/macro/macro-edit.cpp lib/macro/macro-edit.cpp
lib/macro/macro-edit.hpp lib/macro/macro-edit.hpp
lib/macro/macro-export-builtin-extensions.cpp
lib/macro/macro-export-extensions.cpp
lib/macro/macro-export-extensions.hpp
lib/macro/macro-export-import-dialog.cpp lib/macro/macro-export-import-dialog.cpp
lib/macro/macro-export-import-dialog.hpp lib/macro/macro-export-import-dialog.hpp
lib/macro/macro-helpers.cpp lib/macro/macro-helpers.cpp
@@ -141,6 +154,8 @@ target_sources(
lib/macro/macro-ref.hpp lib/macro/macro-ref.hpp
lib/macro/macro-run-button.cpp lib/macro/macro-run-button.cpp
lib/macro/macro-run-button.hpp lib/macro/macro-run-button.hpp
lib/macro/macro-search.cpp
lib/macro/macro-search.hpp
lib/macro/macro-segment-copy-paste.cpp lib/macro/macro-segment-copy-paste.cpp
lib/macro/macro-segment-copy-paste.hpp lib/macro/macro-segment-copy-paste.hpp
lib/macro/macro-segment-list.cpp lib/macro/macro-segment-list.cpp
@@ -158,7 +173,10 @@ target_sources(
lib/macro/macro-signals.hpp lib/macro/macro-signals.hpp
lib/macro/macro-tab.cpp lib/macro/macro-tab.cpp
lib/macro/macro-tree.cpp lib/macro/macro-tree.cpp
lib/macro/macro-undo-redo.cpp
lib/macro/macro-undo-redo.hpp
lib/macro/macro-tree.hpp lib/macro/macro-tree.hpp
lib/macro/macro-websocket-trigger.cpp
lib/macro/macro.cpp lib/macro/macro.cpp
lib/macro/macro.hpp) lib/macro/macro.hpp)
@@ -177,6 +195,8 @@ target_sources(
lib/utils/canvas-helpers.hpp lib/utils/canvas-helpers.hpp
lib/utils/condition-logic.cpp lib/utils/condition-logic.cpp
lib/utils/condition-logic.hpp lib/utils/condition-logic.hpp
lib/utils/crash-handler.cpp
lib/utils/crash-handler.hpp
lib/utils/curl-helper.cpp lib/utils/curl-helper.cpp
lib/utils/curl-helper.hpp lib/utils/curl-helper.hpp
lib/utils/cursor-shape-changer.cpp lib/utils/cursor-shape-changer.cpp
@@ -194,6 +214,15 @@ target_sources(
lib/utils/file-selection.hpp lib/utils/file-selection.hpp
lib/utils/filter-combo-box.cpp lib/utils/filter-combo-box.cpp
lib/utils/filter-combo-box.hpp lib/utils/filter-combo-box.hpp
lib/utils/first-run-wizard.cpp
lib/utils/first-run-wizard.hpp
lib/utils/first-run-wizard-audio.cpp
lib/utils/first-run-wizard-audio.hpp
lib/utils/first-run-wizard-helpers.hpp
lib/utils/first-run-wizard-sequence.cpp
lib/utils/first-run-wizard-sequence.hpp
lib/utils/first-run-wizard-window.cpp
lib/utils/first-run-wizard-window.hpp
lib/utils/help-icon.hpp lib/utils/help-icon.hpp
lib/utils/help-icon.cpp lib/utils/help-icon.cpp
lib/utils/item-selection-helpers.cpp lib/utils/item-selection-helpers.cpp
@@ -276,6 +305,10 @@ target_sources(
lib/utils/volume-control.hpp lib/utils/volume-control.hpp
lib/utils/websocket-api.cpp lib/utils/websocket-api.cpp
lib/utils/websocket-api.hpp lib/utils/websocket-api.hpp
lib/variables/variable-color-button.cpp
lib/variables/variable-color-button.hpp
lib/variables/variable-color.cpp
lib/variables/variable-color.hpp
lib/variables/variable-line-edit.cpp lib/variables/variable-line-edit.cpp
lib/variables/variable-line-edit.hpp lib/variables/variable-line-edit.hpp
lib/variables/variable-number.hpp lib/variables/variable-number.hpp
@@ -348,6 +381,9 @@ set_target_properties(${LIB_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden)
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
target_compile_features(${LIB_NAME} PUBLIC cxx_std_17) target_compile_features(${LIB_NAME} PUBLIC cxx_std_17)
target_precompile_headers(${LIB_NAME} PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/lib/pch.hpp")
target_include_directories( target_include_directories(
${LIB_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/deps/obs-websocket/lib" ${LIB_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/deps/obs-websocket/lib"
"${CMAKE_CURRENT_SOURCE_DIR}/deps/exprtk") "${CMAKE_CURRENT_SOURCE_DIR}/deps/exprtk")
@@ -444,14 +480,29 @@ else()
endif() endif()
if(PROCPS2_INCLUDE_DIR) if(PROCPS2_INCLUDE_DIR)
find_package(PkgConfig REQUIRED) 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) pkg_check_modules(libproc2 REQUIRED IMPORTED_TARGET libproc2)
set(PROC_INCLUDE_DIR "${PROCPS2_INCLUDE_DIR}") set(PROC_INCLUDE_DIR "${PROCPS2_INCLUDE_DIR}")
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS2_AVAILABLE) target_compile_definitions(${LIB_NAME} PRIVATE PROCPS2_AVAILABLE)
set(PROCESS_CONDITION_SUPPORTED 1) set(PROCESS_CONDITION_SUPPORTED 1)
# Check if PIDS_VAL takes 4 arguments (old API, pre-4.0.5) or 3 (new API)
include(CheckCSourceCompiles)
set(CMAKE_REQUIRED_INCLUDES "${PROCPS2_INCLUDE_DIR}")
set(CMAKE_REQUIRED_LIBRARIES proc2)
check_c_source_compiles(
"
#include <libproc2/pids.h>
int main(void) {
struct pids_stack *s = 0;
struct pids_info *i = 0;
(void)PIDS_VAL(0, str, s, i);
return 0;
}
"
PROCPS2_PIDS_VAL_TAKES_INFO)
if(PROCPS2_PIDS_VAL_TAKES_INFO)
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS2_USE_INFO)
endif()
endif() endif()
if(NOT DEFINED PROCESS_CONDITION_SUPPORTED) if(NOT DEFINED PROCESS_CONDITION_SUPPORTED)
message( message(
@@ -477,7 +528,11 @@ endif()
# --- End of section --- # --- End of section ---
add_subdirectory(plugins) add_subdirectory(plugins)
add_subdirectory(tests)
option(ADVSS_ENABLE_TESTS "Build advanced-scene-switcher unit tests" OFF)
if(ADVSS_ENABLE_TESTS)
add_subdirectory(tests)
endif()
# --- Install --- # --- Install ---
@@ -506,3 +561,59 @@ else()
set_target_properties_obs(${PROJECT_NAME} PROPERTIES PREFIX "") set_target_properties_obs(${PROJECT_NAME} PROPERTIES PREFIX "")
endif() endif()
endif() endif()
# --- Install third-party dependency licenses ---
#
# Each entry is "display-name:path/to/license/file" relative to deps/. Files are
# silently skipped when a submodule is not checked out.
set(_dep_license_files
"asio:asio/asio/LICENSE_1_0.txt"
"cpp-httplib:cpp-httplib/LICENSE"
"json:json/LICENSE.MIT"
"jsoncons:jsoncons/LICENSE"
"leptonica:leptonica/leptonica-license.txt"
"libremidi:libremidi/LICENSE.md"
"libusb:libusb/COPYING"
"opencv:opencv/LICENSE"
"openvr:openvr/LICENSE"
"paho-mqtt:paho.mqtt.cpp/LICENSE"
"tesseract:tesseract/LICENSE"
"websocketpp:websocketpp/COPYING"
"whisper:whisper.cpp/LICENSE")
if(DEB_INSTALL)
if(NOT DATA_OUT_DIR)
set(DATA_OUT_DIR "/share/obs/obs-plugins/${PROJECT_NAME}")
endif()
set(_license_dest "${CMAKE_INSTALL_PREFIX}/${DATA_OUT_DIR}/licenses")
elseif(OS_MACOS)
# macOS: license files become bundle Resources/licenses/* entries
set(_license_dest "")
else()
# Windows and non-DEB Linux: alongside the plugin's data directory
set(_license_dest "${CMAKE_PROJECT_NAME}/data/licenses")
endif()
foreach(_entry IN LISTS _dep_license_files)
string(REGEX MATCH "^([^:]+):(.+)$" _m "${_entry}")
set(_dep_name "${CMAKE_MATCH_1}")
set(_dep_file "${CMAKE_CURRENT_SOURCE_DIR}/deps/${CMAKE_MATCH_2}")
if(NOT EXISTS "${_dep_file}")
continue()
endif()
if(OS_MACOS)
# Copy to binary dir so we can give the file a clean name
set(_staged "${CMAKE_CURRENT_BINARY_DIR}/licenses/LICENSE-${_dep_name}")
configure_file("${_dep_file}" "${_staged}" COPYONLY)
target_sources(${PROJECT_NAME} PRIVATE "${_staged}")
set_source_files_properties("${_staged}" PROPERTIES MACOSX_PACKAGE_LOCATION
"Resources/licenses")
else()
install(
FILES "${_dep_file}"
DESTINATION "${_license_dest}"
RENAME "LICENSE-${_dep_name}")
endif()
endforeach()

View File

@@ -41,7 +41,8 @@
"description": "Build for macOS 11.0+ (Universal binary) for CI", "description": "Build for macOS 11.0+ (Universal binary) for CI",
"generator": "Xcode", "generator": "Xcode",
"cacheVariables": { "cacheVariables": {
"CMAKE_COMPILE_WARNING_AS_ERROR": true "CMAKE_COMPILE_WARNING_AS_ERROR": true,
"ADVSS_ENABLE_TESTS": true
} }
}, },
{ {
@@ -69,7 +70,8 @@
"displayName": "Windows x64 CI build", "displayName": "Windows x64 CI build",
"description": "Build for Windows x64 on CI", "description": "Build for Windows x64 on CI",
"cacheVariables": { "cacheVariables": {
"CMAKE_COMPILE_WARNING_AS_ERROR": true "CMAKE_COMPILE_WARNING_AS_ERROR": true,
"ADVSS_ENABLE_TESTS": true
} }
}, },
{ {
@@ -97,7 +99,8 @@
"description": "Build for Linux x86_64 on CI", "description": "Build for Linux x86_64 on CI",
"cacheVariables": { "cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo", "CMAKE_BUILD_TYPE": "RelWithDebInfo",
"CMAKE_COMPILE_WARNING_AS_ERROR": true "CMAKE_COMPILE_WARNING_AS_ERROR": true,
"ADVSS_ENABLE_TESTS": true
} }
}, },
{ {
@@ -125,7 +128,8 @@
"description": "Build for Linux aarch64 on CI", "description": "Build for Linux aarch64 on CI",
"cacheVariables": { "cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo", "CMAKE_BUILD_TYPE": "RelWithDebInfo",
"CMAKE_COMPILE_WARNING_AS_ERROR": true "CMAKE_COMPILE_WARNING_AS_ERROR": true,
"ADVSS_ENABLE_TESTS": true
} }
} }
], ],

View File

@@ -14,11 +14,6 @@ On Linux the plugin is available via the **Flatpak** package manager for users w
flatpak install com.obsproject.Studio.Plugin.SceneSwitcher flatpak install com.obsproject.Studio.Plugin.SceneSwitcher
``` ```
The **Snap** package manager offers an OBS Studio installation which is bundled with the plugin:
```
sudo snap install obs-studio
```
More information can be found [here](https://github.com/WarmUpTill/SceneSwitcher/wiki/Installation). More information can be found [here](https://github.com/WarmUpTill/SceneSwitcher/wiki/Installation).
## Contributing ## Contributing

View File

@@ -0,0 +1,23 @@
Advanced Scene Switcher - Windows Installation (Portable)
==========================================================
For full installation instructions visit:
https://github.com/WarmUpTill/SceneSwitcher/wiki/Installation
This archive uses the portable plugin layout for older OBS versions,
portable OBS installs, and Steam.
Extract the CONTENTS of this archive into your OBS installation directory,
typically:
C:\Program Files\obs-studio\
Merge the data\ and obs-plugins\ folders with the existing ones when prompted.
Steam users: right-click OBS in your library -> Manage -> Browse local files,
then extract the contents of this archive into that directory.
For OBS 28 and newer the recommended archive is preferred:
advanced-scene-switcher-<version>-windows-x64.zip

View File

@@ -0,0 +1,21 @@
Advanced Scene Switcher - Windows Installation
===============================================
For full installation instructions visit:
https://github.com/WarmUpTill/SceneSwitcher/wiki/Installation
This archive uses the recommended plugin layout for OBS 28 and newer.
Extract the CONTENTS of this archive into:
C:\ProgramData\obs-studio\plugins\
Tip: press Win+R and type %ProgramData%\obs-studio\plugins to open that folder.
After extracting, the path should look like:
C:\ProgramData\obs-studio\plugins\advanced-scene-switcher\bin\64bit\advanced-scene-switcher.dll
If the plugin does not appear in OBS, try the portable archive instead:
advanced-scene-switcher-<version>-windows-x64-portable.zip

View File

@@ -2,7 +2,7 @@
if(BUILD_OUT_OF_TREE) if(BUILD_OUT_OF_TREE)
if(OS_WINDOWS) if(OS_WINDOWS)
set(OBS_PLUGIN_DESTINATION "obs-plugins/64bit") set(OBS_PLUGIN_DESTINATION "${CMAKE_PROJECT_NAME}/bin/64bit")
else() else()
set(OBS_PLUGIN_DESTINATION "${CMAKE_INSTALL_LIBDIR}/obs-plugins") set(OBS_PLUGIN_DESTINATION "${CMAKE_INSTALL_LIBDIR}/obs-plugins")
endif() endif()
@@ -157,7 +157,9 @@ else()
set_target_properties(${target} PROPERTIES INSTALL_RPATH set_target_properties(${target} PROPERTIES INSTALL_RPATH
"$ORIGIN:$ORIGIN/..") "$ORIGIN:$ORIGIN/..")
endif() endif()
message(STATUS "ADVSS: ENABLED PLUGIN ${target}") get_filename_component(_plugin_dir_name "${CMAKE_CURRENT_SOURCE_DIR}" NAME)
set_property(GLOBAL APPEND PROPERTY ADVSS_PLUGINS_ENABLED
${_plugin_dir_name})
endfunction() endfunction()
function(install_advss_plugin_dependency_target target dep) function(install_advss_plugin_dependency_target target dep)
@@ -331,6 +333,8 @@ function(setup_advss_plugin target)
PRIVATE "${ADVSS_SOURCE_DIR}/lib" "${ADVSS_SOURCE_DIR}/lib/legacy" PRIVATE "${ADVSS_SOURCE_DIR}/lib" "${ADVSS_SOURCE_DIR}/lib/legacy"
"${ADVSS_SOURCE_DIR}/lib/macro" "${ADVSS_SOURCE_DIR}/lib/utils" "${ADVSS_SOURCE_DIR}/lib/macro" "${ADVSS_SOURCE_DIR}/lib/utils"
"${ADVSS_SOURCE_DIR}/lib/variables" "${ADVSS_SOURCE_DIR}/forms") "${ADVSS_SOURCE_DIR}/lib/variables" "${ADVSS_SOURCE_DIR}/forms")
target_precompile_headers(${target} PRIVATE "${ADVSS_SOURCE_DIR}/lib/pch.hpp")
endfunction() endfunction()
function(install_advss_plugin_dependency) function(install_advss_plugin_dependency)

View File

@@ -180,24 +180,53 @@ function(_check_dependencies)
set(url ${url}/${version}/${file}) set(url ${url}/${version}/${file})
endif() endif()
set(MAX_DOWNLOAD_RETRIES 3)
set(RETRY_DELAY 60) # seconds
if(NOT EXISTS "${dependencies_dir}/${file}") if(NOT EXISTS "${dependencies_dir}/${file}")
message(STATUS "Downloading ${url}") message(STATUS "Downloading ${url}")
file(
DOWNLOAD "${url}" "${dependencies_dir}/${file}"
STATUS download_status
EXPECTED_HASH SHA256=${hash})
list(GET download_status 0 error_code) set(download_success FALSE)
list(GET download_status 1 error_message)
if(error_code GREATER 0) foreach(i RANGE 1 ${MAX_DOWNLOAD_RETRIES})
message(STATUS "Downloading ${url} - Failure") message(STATUS "Attempt ${i}/${MAX_DOWNLOAD_RETRIES} for ${url}")
file(DOWNLOAD "${url}" "${dependencies_dir}/${file}"
STATUS download_status)
list(GET download_status 0 error_code)
list(GET download_status 1 error_message)
if(error_code EQUAL 0)
file(SHA256 "${dependencies_dir}/${file}" actual_hash)
if(NOT actual_hash STREQUAL hash)
set(error_code 1)
set(error_message
"hash mismatch (expected ${hash}, got ${actual_hash})")
endif()
endif()
if(error_code EQUAL 0)
message(STATUS "Downloading ${url} - success on attempt ${i}")
set(download_success TRUE)
break()
else()
message(WARNING "Download failed (attempt ${i}): ${error_message}")
file(REMOVE "${dependencies_dir}/${file}")
if(NOT i EQUAL MAX_DOWNLOAD_RETRIES)
message(STATUS "Retrying in ${RETRY_DELAY} seconds...")
execute_process(COMMAND ${CMAKE_COMMAND} -E sleep ${RETRY_DELAY})
endif()
endif()
endforeach()
if(NOT download_success)
message( message(
FATAL_ERROR FATAL_ERROR
"Unable to download ${url}, failed with error: ${error_message}") "Unable to download ${url} after ${MAX_DOWNLOAD_RETRIES} attempts")
file(REMOVE "${dependencies_dir}/${file}")
else()
message(STATUS "Downloading ${url} - done")
endif() endif()
message(STATUS "Downloading ${url} - done")
endif() endif()
if(NOT EXISTS "${dependencies_dir}/${destination}") if(NOT EXISTS "${dependencies_dir}/${destination}")

View File

@@ -64,7 +64,7 @@ function(_git_find_closest_git_dir _start_dir _git_dir_var)
while(NOT EXISTS "${git_dir}") while(NOT EXISTS "${git_dir}")
# .git dir not found, search parent directories # .git dir not found, search parent directories
set(git_previous_parent "${cur_dir}") set(git_previous_parent "${cur_dir}")
get_filename_component(cur_dir ${cur_dir} DIRECTORY) get_filename_component(cur_dir "${cur_dir}" DIRECTORY)
if(cur_dir STREQUAL git_previous_parent) if(cur_dir STREQUAL git_previous_parent)
# We have reached the root directory, we are not in git # We have reached the root directory, we are not in git
set(${_git_dir_var} set(${_git_dir_var}

View File

@@ -18,8 +18,11 @@ set(CPACK_PACKAGE_FILE_NAME
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_C_LIBRARY_ARCHITECTURE}" "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_C_LIBRARY_ARCHITECTURE}"
) )
set(CPACK_GENERATOR "DEB") set(CPACK_GENERATOR
"DEB"
CACHE STRING "CPack generator to use")
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "obs-studio")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${PLUGIN_EMAIL}") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${PLUGIN_EMAIL}")
set(CPACK_SET_DESTDIR ON) set(CPACK_SET_DESTDIR ON)
@@ -46,6 +49,19 @@ set(CPACK_SOURCE_PACKAGE_FILE_NAME
set(CPACK_ARCHIVE_THREADS 0) set(CPACK_ARCHIVE_THREADS 0)
include(CPack) include(CPack)
# CPack.cmake has a side effect that leaves binary packaging variables in the
# state intended for source packaging when it finishes. Specifically it sets
# CPACK_GENERATOR to CPACK_SOURCE_GENERATOR ("TXZ"), sets
# CPACK_INSTALLED_DIRECTORIES to the source root (which would cause CPackDeb to
# scan the entire source tree, including ccache object files that lack a Build
# ID), and clears CPACK_INSTALL_CMAKE_PROJECTS (preventing the cmake install
# mechanism from being used). In some cmake versions these overrides happen
# before CPackConfig.cmake is written. Reset all three so that binary DEB
# packaging works correctly regardless of cmake version.
set(CPACK_GENERATOR "DEB")
set(CPACK_INSTALLED_DIRECTORIES "")
set(CPACK_INSTALL_CMAKE_PROJECTS
"${CMAKE_BINARY_DIR};${CMAKE_PROJECT_NAME};ALL;/")
find_package(libobs QUIET) find_package(libobs QUIET)

View File

@@ -6,7 +6,11 @@ include(buildspec_common)
# _check_dependencies_windows: Set up Windows slice for _check_dependencies # _check_dependencies_windows: Set up Windows slice for _check_dependencies
function(_check_dependencies_windows) function(_check_dependencies_windows)
set(arch ${CMAKE_GENERATOR_PLATFORM}) if(CMAKE_GENERATOR_PLATFORM)
set(arch ${CMAKE_GENERATOR_PLATFORM})
else()
set(arch x64)
endif()
set(platform windows-${arch}) set(platform windows-${arch})
set(dependencies_dir "${CMAKE_CURRENT_SOURCE_DIR}/.deps") set(dependencies_dir "${CMAKE_CURRENT_SOURCE_DIR}/.deps")

View File

@@ -6,3 +6,9 @@ include_guard(GLOBAL)
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL TRUE) set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL TRUE)
include(buildspec) include(buildspec)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX
"$ENV{ALLUSERSPROFILE}/obs-studio/plugins"
CACHE STRING "Default plugin installation directory" FORCE)
endif()

View File

@@ -30,13 +30,13 @@ function(set_target_properties_plugin target)
install( install(
TARGETS ${target} TARGETS ${target}
RUNTIME DESTINATION bin/64bit RUNTIME DESTINATION "${target}/bin/64bit"
LIBRARY DESTINATION obs-plugins/64bit) LIBRARY DESTINATION "${target}/bin/64bit")
install( install(
FILES "$<TARGET_PDB_FILE:${target}>" FILES "$<TARGET_PDB_FILE:${target}>"
CONFIGURATIONS RelWithDebInfo Debug Release CONFIGURATIONS RelWithDebInfo Debug Release
DESTINATION obs-plugins/64bit DESTINATION "${target}/bin/64bit"
OPTIONAL) OPTIONAL)
if(OBS_BUILD_DIR) if(OBS_BUILD_DIR)
@@ -44,11 +44,11 @@ function(set_target_properties_plugin target)
TARGET ${target} TARGET ${target}
POST_BUILD POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E make_directory COMMAND "${CMAKE_COMMAND}" -E make_directory
"${OBS_BUILD_DIR}/obs-plugins/64bit" "${OBS_BUILD_DIR}/plugins/${target}/bin/64bit"
COMMAND COMMAND
"${CMAKE_COMMAND}" -E copy_if_different "$<TARGET_FILE:${target}>" "${CMAKE_COMMAND}" -E copy_if_different "$<TARGET_FILE:${target}>"
"$<$<CONFIG:Debug,RelWithDebInfo,Release>:$<TARGET_PDB_FILE:${target}>>" "$<$<CONFIG:Debug,RelWithDebInfo,Release>:$<TARGET_PDB_FILE:${target}>>"
"${OBS_BUILD_DIR}/obs-plugins/64bit" "${OBS_BUILD_DIR}/plugins/${target}/bin/64bit"
COMMENT "Copy ${target} to obs-studio directory ${OBS_BUILD_DIR}" COMMENT "Copy ${target} to obs-studio directory ${OBS_BUILD_DIR}"
VERBATIM) VERBATIM)
endif() endif()
@@ -105,7 +105,7 @@ function(target_install_resources target)
install( install(
DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/" DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/"
DESTINATION data/obs-plugins/${target} DESTINATION "${target}/data"
USE_SOURCE_PERMISSIONS) USE_SOURCE_PERMISSIONS)
if(OBS_BUILD_DIR) if(OBS_BUILD_DIR)
@@ -113,11 +113,11 @@ function(target_install_resources target)
TARGET ${target} TARGET ${target}
POST_BUILD POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E make_directory COMMAND "${CMAKE_COMMAND}" -E make_directory
"${OBS_BUILD_DIR}/data/obs-plugins/${target}" "${OBS_BUILD_DIR}/plugins/${target}/data"
COMMAND COMMAND
"${CMAKE_COMMAND}" -E copy_directory "${CMAKE_COMMAND}" -E copy_directory
"${CMAKE_CURRENT_SOURCE_DIR}/data" "${CMAKE_CURRENT_SOURCE_DIR}/data"
"${OBS_BUILD_DIR}/data/obs-plugins/${target}" "${OBS_BUILD_DIR}/plugins/${target}/data"
COMMENT "Copy ${target} resources to data directory" COMMENT "Copy ${target} resources to data directory"
VERBATIM) VERBATIM)
endif() endif()
@@ -133,7 +133,7 @@ function(target_add_resource target resource)
install( install(
FILES "${resource}" FILES "${resource}"
DESTINATION data/obs-plugins/${target} DESTINATION "${target}/data"
COMPONENT Runtime) COMPONENT Runtime)
if(OBS_BUILD_DIR) if(OBS_BUILD_DIR)
@@ -141,9 +141,9 @@ function(target_add_resource target resource)
TARGET ${target} TARGET ${target}
POST_BUILD POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E make_directory COMMAND "${CMAKE_COMMAND}" -E make_directory
"${OBS_BUILD_DIR}/data/obs-plugins/${target}" "${OBS_BUILD_DIR}/plugins/${target}/data"
COMMAND "${CMAKE_COMMAND}" -E copy "${resource}" COMMAND "${CMAKE_COMMAND}" -E copy "${resource}"
"${OBS_BUILD_DIR}/data/obs-plugins/${target}" "${OBS_BUILD_DIR}/plugins/${target}/data"
COMMENT "Copy ${target} resource ${resource} to library directory" COMMENT "Copy ${target} resource ${resource} to library directory"
VERBATIM) VERBATIM)
endif() endif()

View File

@@ -2,12 +2,13 @@
#define MyAppVersion "@CMAKE_PROJECT_VERSION@" #define MyAppVersion "@CMAKE_PROJECT_VERSION@"
#define MyAppPublisher "@PLUGIN_AUTHOR@" #define MyAppPublisher "@PLUGIN_AUTHOR@"
#define MyAppURL "@PLUGIN_WEBSITE@" #define MyAppURL "@PLUGIN_WEBSITE@"
#define MyAppId "@UUID_APP@"
[Setup] [Setup]
; NOTE: The value of AppId uniquely identifies this application. ; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications. ; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{@UUID_APP@} AppId={{{#MyAppId}}
AppName={#MyAppName} AppName={#MyAppName}
AppVersion={#MyAppVersion} AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher} AppPublisher={#MyAppPublisher}
@@ -20,15 +21,19 @@ OutputBaseFilename={#MyAppName}-{#MyAppVersion}-Windows-Installer
Compression=lzma Compression=lzma
SolidCompression=yes SolidCompression=yes
DirExistsWarning=no DirExistsWarning=no
UsePreviousAppDir=no
SetupIconFile=installer.ico SetupIconFile=installer.ico
UninstallDisplayIcon={app}\data\obs-plugins\advanced-scene-switcher\res\images\logo.ico UninstallDisplayIcon={code:GetUninstallIconPath}
DisableDirPage=no DisableDirPage=no
[Languages] [Languages]
Name: "english"; MessagesFile: "compiler:Default.isl" Name: "english"; MessagesFile: "compiler:Default.isl"
[Files] [Files]
Source: "..\release\Package\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs ; Recommended layout - used when installing to %ProgramData%\obs-studio\plugins\
Source: "..\release\Package\recommended\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Check: IsRecommendedLayout
; Legacy layout - used when installing to the OBS installation directory
Source: "..\release\Package\portable\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Check: IsLegacyLayout
Source: "..\LICENSE"; Flags: dontcopy Source: "..\LICENSE"; Flags: dontcopy
; NOTE: Don't use "Flags: ignoreversion" on any shared system files ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
@@ -51,17 +56,64 @@ begin
); );
end; end;
// credit where it's due : // Returns true when the selected install directory is under %ProgramData%,
// following function come from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45 // meaning the recommended plugin layout should be used.
function GetDirName(Value: string): string; function IsRecommendedLayout(): Boolean;
var
InstallPath: string;
begin begin
// initialize default path, which will be returned when the following registry Result := Pos(LowerCase(ExpandConstant('{commonappdata}')),
// key queries fail due to missing keys or for some different reason LowerCase(WizardDirValue())) = 1;
Result := '{autopf}\obs-studio';
// query the first registry value; if this succeeds, return the obtained value
if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then
Result := InstallPath
end; end;
function IsLegacyLayout(): Boolean;
begin
Result := not IsRecommendedLayout();
end;
function GetUninstallIconPath(Param: string): string;
begin
if IsRecommendedLayout() then
Result := ExpandConstant('{app}\advanced-scene-switcher\data\res\images\logo.ico')
else
Result := ExpandConstant('{app}\data\obs-plugins\advanced-scene-switcher\res\images\logo.ico');
end;
function GetDirName(Value: string): string;
var
PrevPath: string;
UninstallKey: string;
begin
Result := ExpandConstant('{commonappdata}\obs-studio\plugins');
UninstallKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + '{' + '{#MyAppId}' + '}_is1';
if RegQueryStringValue(HKLM, UninstallKey, 'InstallLocation', PrevPath) and (PrevPath <> '') then
Result := PrevPath;
end;
// Remove files placed by the portable installer (obs-plugins/64bit and
// data/obs-plugins layout) so the plugin is not loaded twice by OBS.
// Only runs when upgrading from a portable location to the recommended one.
procedure RemoveLegacyFiles();
var
OldInstallPath: string;
UninstallKey: string;
begin
if not IsRecommendedLayout() then
Exit;
UninstallKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + '{' + '{#MyAppId}' + '}_is1';
if not RegQueryStringValue(HKLM, UninstallKey, 'InstallLocation', OldInstallPath) then
Exit;
// Skip if the previous install was already in the recommended location
if Pos(LowerCase(ExpandConstant('{commonappdata}')), LowerCase(OldInstallPath)) = 1 then
Exit;
DeleteFile(OldInstallPath + '\obs-plugins\64bit\advanced-scene-switcher.dll');
DeleteFile(OldInstallPath + '\obs-plugins\64bit\advanced-scene-switcher.pdb');
DeleteFile(OldInstallPath + '\obs-plugins\64bit\advanced-scene-switcher-lib.dll');
DeleteFile(OldInstallPath + '\obs-plugins\64bit\advanced-scene-switcher-lib.pdb');
DelTree(OldInstallPath + '\obs-plugins\64bit\advanced-scene-switcher-plugins', True, True, True);
DelTree(OldInstallPath + '\data\obs-plugins\advanced-scene-switcher', True, True, True);
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssInstall then
RemoveLegacyFiles();
end;

View File

@@ -0,0 +1,86 @@
# ---------------------------------------------------------------------------
# Detects OpenSSL installed via winget or other common Windows locations,
# without requiring the user to manually set OPENSSL_ROOT_DIR.
# ---------------------------------------------------------------------------
if(WIN32 AND (NOT OpenSSL_FOUND))
set(_openssl_roots
"$ENV{ProgramFiles}/OpenSSL-Win64" "$ENV{ProgramFiles}/OpenSSL"
"$ENV{ProgramW6432}/OpenSSL-Win64")
set(_openssl_lib_suffixes "lib/VC/x64/MD" "lib/VC/x64/MDd" "lib/VC/x64/MT"
"lib/VC/x64/MTd" "lib")
# Determine which configuration we're building
if(CMAKE_BUILD_TYPE MATCHES "Debug")
set(_is_debug TRUE)
else()
set(_is_debug FALSE)
endif()
# Determine which runtime we use Default to /MD (shared CRT)
set(_crt_kind "MD")
if(MSVC)
if(CMAKE_MSVC_RUNTIME_LIBRARY MATCHES "MultiThreaded")
if(CMAKE_MSVC_RUNTIME_LIBRARY MATCHES "Debug")
set(_crt_kind "MTd")
else()
set(_crt_kind "MT")
endif()
else()
if(_is_debug)
set(_crt_kind "MDd")
else()
set(_crt_kind "MD")
endif()
endif()
endif()
message(STATUS "Looking for OpenSSL built with CRT variant: ${_crt_kind}")
# Try to find the root and corresponding lib path
if(OPENSSL_USE_STATIC_LIBS)
set(_crypto_lib_name "libcrypto_static.lib")
set(_ssl_lib_name "libssl_static.lib")
else()
set(_crypto_lib_name "libcrypto.lib")
set(_ssl_lib_name "libssl.lib")
endif()
foreach(_root ${_openssl_roots})
if(EXISTS "${_root}/include/openssl/ssl.h")
foreach(_suffix ${_openssl_lib_suffixes})
if(_suffix MATCHES "${_crt_kind}$"
AND EXISTS "${_root}/${_suffix}/${_crypto_lib_name}")
set(OPENSSL_ROOT_DIR
"${_root}"
CACHE PATH "Path to OpenSSL root")
set(OPENSSL_CRYPTO_LIBRARY
"${_root}/${_suffix}/${_crypto_lib_name}"
CACHE FILEPATH "OpenSSL crypto lib")
set(OPENSSL_SSL_LIBRARY
"${_root}/${_suffix}/${_ssl_lib_name}"
CACHE FILEPATH "OpenSSL ssl lib")
set(OPENSSL_INCLUDE_DIR
"${_root}/include"
CACHE PATH "OpenSSL include dir")
set(OpenSSL_FOUND
TRUE
CACHE BOOL "Whether OpenSSL was found")
message(STATUS "Found OpenSSL at: ${_root}/${_suffix}")
return()
endif()
endforeach()
endif()
if(OpenSSL_FOUND)
break()
endif()
endforeach()
if(NOT OpenSSL_FOUND)
message(WARNING "Could not auto-detect OpenSSL under Program Files. "
"Might have to set OPENSSL_ROOT_DIR manually.")
endif()
endif()

View File

@@ -76,7 +76,6 @@ AdvSceneSwitcher.macroTab.name="Name:"
AdvSceneSwitcher.macroTab.run="Makro ausführen" AdvSceneSwitcher.macroTab.run="Makro ausführen"
AdvSceneSwitcher.macroTab.runFail="Ausführen von \"%1\" fehlgeschlagen!\nEntweder ist eine der Aktionen fehlgeschlagen oder das Makro wird bereits ausgeführt.\nSoll die aktuelle Ausführung gestoppt werden?" AdvSceneSwitcher.macroTab.runFail="Ausführen von \"%1\" fehlgeschlagen!\nEntweder ist eine der Aktionen fehlgeschlagen oder das Makro wird bereits ausgeführt.\nSoll die aktuelle Ausführung gestoppt werden?"
AdvSceneSwitcher.macroTab.runInParallel="Parallel zu anderen Makros ausführen" AdvSceneSwitcher.macroTab.runInParallel="Parallel zu anderen Makros ausführen"
AdvSceneSwitcher.macroTab.onChange="Nur bei Änderung ausführen"
AdvSceneSwitcher.macroTab.defaultname="Makro %1" AdvSceneSwitcher.macroTab.defaultname="Makro %1"
AdvSceneSwitcher.macroTab.defaultGroupName="Gruppe %1" AdvSceneSwitcher.macroTab.defaultGroupName="Gruppe %1"
AdvSceneSwitcher.macroTab.removeGroupPopup.text="Sicher, dass \"%1\" und alle zugehörigen Elemente gelöscht werden?" AdvSceneSwitcher.macroTab.removeGroupPopup.text="Sicher, dass \"%1\" und alle zugehörigen Elemente gelöscht werden?"
@@ -100,7 +99,6 @@ AdvSceneSwitcher.macroList.deleted="gelöscht"
AdvSceneSwitcher.macroList.duplicate="\"%1\" ist bereits ausgewählt!" AdvSceneSwitcher.macroList.duplicate="\"%1\" ist bereits ausgewählt!"
; Macro Logic ; Macro Logic
AdvSceneSwitcher.logic.none="Eintrag ignorieren"
AdvSceneSwitcher.logic.and="Und" AdvSceneSwitcher.logic.and="Und"
AdvSceneSwitcher.logic.or="Oder" AdvSceneSwitcher.logic.or="Oder"
AdvSceneSwitcher.logic.andNot="Und nicht" AdvSceneSwitcher.logic.andNot="Und nicht"
@@ -146,11 +144,7 @@ AdvSceneSwitcher.condition.file="Datei"
AdvSceneSwitcher.condition.file.type.match="entspricht" AdvSceneSwitcher.condition.file.type.match="entspricht"
AdvSceneSwitcher.condition.file.type.contentChange="Inhalt geändert" AdvSceneSwitcher.condition.file.type.contentChange="Inhalt geändert"
AdvSceneSwitcher.condition.file.type.dateChange="Änderungsdatum geändert" AdvSceneSwitcher.condition.file.type.dateChange="Änderungsdatum geändert"
AdvSceneSwitcher.condition.file.remote="Entfernte Datei" AdvSceneSwitcher.condition.file.layout="{{filePath}}{{conditions}}{{regex}}"
AdvSceneSwitcher.condition.file.local="Lokale Datei"
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
AdvSceneSwitcher.condition.media="Medien" AdvSceneSwitcher.condition.media="Medien"
AdvSceneSwitcher.condition.media.source="Quelle" AdvSceneSwitcher.condition.media.source="Quelle"
AdvSceneSwitcher.condition.media.anyOnScene="Beliebige Medienquelle in" AdvSceneSwitcher.condition.media.anyOnScene="Beliebige Medienquelle in"
@@ -216,8 +210,8 @@ AdvSceneSwitcher.condition.record.state.start="Aufnahme läuft"
AdvSceneSwitcher.condition.record.state.pause="Aufnahme pausiert" AdvSceneSwitcher.condition.record.state.pause="Aufnahme pausiert"
AdvSceneSwitcher.condition.record.state.stop="Aufnahme gestoppt" AdvSceneSwitcher.condition.record.state.stop="Aufnahme gestoppt"
AdvSceneSwitcher.condition.process="Prozess" AdvSceneSwitcher.condition.process="Prozess"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}läuft{{focused}}und ist fokusiert" AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}läuft{{focused}}und ist fokusiert"
AdvSceneSwitcher.condition.process.entry.focus="Aktueller Vordergrundprozess: {{focusProcess}}" AdvSceneSwitcher.condition.process.layout.focus="Aktueller Vordergrundprozess: {{focusProcess}}"
AdvSceneSwitcher.condition.idle="Leerlauf" AdvSceneSwitcher.condition.idle="Leerlauf"
AdvSceneSwitcher.condition.idle.entry="Keine Tastatur- oder Mauseingaben für {{duration}}" AdvSceneSwitcher.condition.idle.entry="Keine Tastatur- oder Mauseingaben für {{duration}}"
AdvSceneSwitcher.condition.pluginState="Plugin-Status" AdvSceneSwitcher.condition.pluginState="Plugin-Status"
@@ -291,14 +285,14 @@ AdvSceneSwitcher.condition.replay.state.started="Replay Buffer gestartet"
AdvSceneSwitcher.condition.replay.state.saved="Replay Buffer gespeichert" AdvSceneSwitcher.condition.replay.state.saved="Replay Buffer gespeichert"
AdvSceneSwitcher.condition.replay.entry="{{state}}" AdvSceneSwitcher.condition.replay.entry="{{state}}"
AdvSceneSwitcher.condition.date="Datum" AdvSceneSwitcher.condition.date="Datum"
AdvSceneSwitcher.condition.date.anyDay="Beliebiger Tag" AdvSceneSwitcher.day.any="Beliebiger Tag"
AdvSceneSwitcher.condition.date.monday="Montag" AdvSceneSwitcher.day.monday="Montag"
AdvSceneSwitcher.condition.date.tuesday="Dienstag" AdvSceneSwitcher.day.tuesday="Dienstag"
AdvSceneSwitcher.condition.date.wednesday="Mittwoch" AdvSceneSwitcher.day.wednesday="Mittwoch"
AdvSceneSwitcher.condition.date.thursday="Donnerstag" AdvSceneSwitcher.day.thursday="Donnerstag"
AdvSceneSwitcher.condition.date.friday="Freitag" AdvSceneSwitcher.day.friday="Freitag"
AdvSceneSwitcher.condition.date.saturday="Samstag" AdvSceneSwitcher.day.saturday="Samstag"
AdvSceneSwitcher.condition.date.sunday="Sonntag" AdvSceneSwitcher.day.sunday="Sonntag"
AdvSceneSwitcher.condition.date.state.at="Am" AdvSceneSwitcher.condition.date.state.at="Am"
AdvSceneSwitcher.condition.date.state.after="Nach" AdvSceneSwitcher.condition.date.state.after="Nach"
AdvSceneSwitcher.condition.date.state.before="Vor" AdvSceneSwitcher.condition.date.state.before="Vor"
@@ -309,12 +303,13 @@ AdvSceneSwitcher.condition.date.ignoreDate="Wenn diese Option nicht aktiviert is
AdvSceneSwitcher.condition.date.ignoreTime="Wenn diese Option nicht aktiviert ist, wird die Zeitkomponente ignoriert" AdvSceneSwitcher.condition.date.ignoreTime="Wenn diese Option nicht aktiviert ist, wird die Zeitkomponente ignoriert"
AdvSceneSwitcher.condition.date.showAdvancedSettings="Erweiterte Einstellungen anzeigen" AdvSceneSwitcher.condition.date.showAdvancedSettings="Erweiterte Einstellungen anzeigen"
AdvSceneSwitcher.condition.date.showSimpleSettings="Einfache Einstellungen anzeigen" AdvSceneSwitcher.condition.date.showSimpleSettings="Einfache Einstellungen anzeigen"
AdvSceneSwitcher.condition.date.entry.simple="Am {{dayOfWeek}} {{weekCondition}} {{ignoreWeekTime}}{{weekTime}}" AdvSceneSwitcher.condition.date.layout.simple.day="Am{{dayOfWeek}}"
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}} {{ignoreDate}}{{date}} {{ignoreTime}}{{time}} {{separator}} {{date2}} {{time2}}" AdvSceneSwitcher.condition.date.layout.simple.time="{{ignoreWeekTime}}{{weekCondition}}{{weekTime}}"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}} Wiederholen alle {{duration}} bei Datumsübereinstimmung" AdvSceneSwitcher.condition.date.layout.advanced="{{condition}} {{ignoreDate}}{{date}} {{ignoreTime}}{{time}} {{separator}} {{date2}} {{time2}}"
AdvSceneSwitcher.condition.date.entry.pattern="Aktuelles Datum \"{{currentDate}}\" entspricht dem Muster {{pattern}}" AdvSceneSwitcher.condition.date.layout.repeat="{{repeat}} Wiederholen alle {{duration}} bei Datumsübereinstimmung"
AdvSceneSwitcher.condition.date.entry.nextMatchDate="Nächster Treffer bei: %1" AdvSceneSwitcher.condition.date.layout.pattern="Aktuelles Datum \"{{currentDate}}\" entspricht dem Muster {{pattern}}"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} Bei Wiederholung ausgewähltes Datum auf Wiederholungsdatum aktualisieren" AdvSceneSwitcher.condition.date.layout.nextMatchDate="Nächster Treffer bei: %1"
AdvSceneSwitcher.condition.date.layout.updateOnRepeat="{{updateOnRepeat}} Bei Wiederholung ausgewähltes Datum auf Wiederholungsdatum aktualisieren"
AdvSceneSwitcher.condition.sceneTransform="Szenenelement transformieren" AdvSceneSwitcher.condition.sceneTransform="Szenenelement transformieren"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Transformation erhalten" AdvSceneSwitcher.condition.sceneTransform.getTransform="Transformation erhalten"
AdvSceneSwitcher.condition.sceneTransform.condition.match="entspricht Transformation" AdvSceneSwitcher.condition.sceneTransform.condition.match="entspricht Transformation"
@@ -387,8 +382,8 @@ AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Warten, bis der Übergan
AdvSceneSwitcher.action.wait="Warten" AdvSceneSwitcher.action.wait="Warten"
AdvSceneSwitcher.action.wait.type.fixed="fixe" AdvSceneSwitcher.action.wait.type.fixed="fixe"
AdvSceneSwitcher.action.wait.type.random="zufällig" AdvSceneSwitcher.action.wait.type.random="zufällig"
AdvSceneSwitcher.action.wait.entry.fixed="Warte für {{waitType}} Dauer von {{duration}}" AdvSceneSwitcher.action.wait.layout.fixed="Warte für {{waitType}} Dauer von {{duration}}"
AdvSceneSwitcher.action.wait.entry.random="Warte für {{waitType}} Dauer von {{duration}} bis {{duration2}}" AdvSceneSwitcher.action.wait.layout.random="Warte für {{waitType}} Dauer von {{duration}} bis {{duration2}}"
AdvSceneSwitcher.action.audio="Audio" AdvSceneSwitcher.action.audio="Audio"
AdvSceneSwitcher.action.audio.type.mute="Stummgeschaltet" AdvSceneSwitcher.action.audio.type.mute="Stummgeschaltet"
AdvSceneSwitcher.action.audio.type.unmute="Nicht stummgeschaltet" AdvSceneSwitcher.action.audio.type.unmute="Nicht stummgeschaltet"
@@ -412,7 +407,6 @@ AdvSceneSwitcher.action.recording.type.unpause="Aufnahme nicht mehr pausieren"
AdvSceneSwitcher.action.recording.type.split="Aufnahme-Datei teilen" AdvSceneSwitcher.action.recording.type.split="Aufnahme-Datei teilen"
AdvSceneSwitcher.action.recording.pause.hint="Bitte beachten, dass je nach Aufnahmeeinstellung die Aufnahme möglicherweise nicht unterbrochen werden kann" AdvSceneSwitcher.action.recording.pause.hint="Bitte beachten, dass je nach Aufnahmeeinstellung die Aufnahme möglicherweise nicht unterbrochen werden kann"
AdvSceneSwitcher.action.recording.split.hint="Vergewissern, dass die automatische Dateiaufteilung in den OBS-Einstellungen aktiviert ist!" AdvSceneSwitcher.action.recording.split.hint="Vergewissern, dass die automatische Dateiaufteilung in den OBS-Einstellungen aktiviert ist!"
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
AdvSceneSwitcher.action.replay="Replay Buffer" AdvSceneSwitcher.action.replay="Replay Buffer"
AdvSceneSwitcher.action.replay.saveWarn="Warnung: Ein zu häufiges Speichern kann dazu führen, dass der Replay Buffer nicht gespeichert wird!" AdvSceneSwitcher.action.replay.saveWarn="Warnung: Ein zu häufiges Speichern kann dazu führen, dass der Replay Buffer nicht gespeichert wird!"
AdvSceneSwitcher.action.replay.type.stop="Replay Buffer stoppen" AdvSceneSwitcher.action.replay.type.stop="Replay Buffer stoppen"
@@ -421,7 +415,6 @@ AdvSceneSwitcher.action.replay.type.save="Replay Buffer speichern"
AdvSceneSwitcher.action.streaming="Stream" AdvSceneSwitcher.action.streaming="Stream"
AdvSceneSwitcher.action.streaming.type.stop="Stream stoppen" AdvSceneSwitcher.action.streaming.type.stop="Stream stoppen"
AdvSceneSwitcher.action.streaming.type.start="Stream starten" AdvSceneSwitcher.action.streaming.type.start="Stream starten"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="Ausführen" AdvSceneSwitcher.action.run="Ausführen"
AdvSceneSwitcher.action.sceneVisibility="Sichtbarkeit von Szenenelementen" AdvSceneSwitcher.action.sceneVisibility="Sichtbarkeit von Szenenelementen"
AdvSceneSwitcher.action.sceneVisibility.type.show="Anzeigen" AdvSceneSwitcher.action.sceneVisibility.type.show="Anzeigen"
@@ -429,7 +422,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="Verstecken"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="Umschalten" AdvSceneSwitcher.action.sceneVisibility.type.toggle="Umschalten"
AdvSceneSwitcher.action.sceneVisibility.type.source="Quelle" AdvSceneSwitcher.action.sceneVisibility.type.source="Quelle"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Beliebig" AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Beliebig"
AdvSceneSwitcher.action.sceneVisibility.entry="Auf{{scenes}}{{actions}}{{sources}}" AdvSceneSwitcher.action.sceneVisibility.layout="Auf{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="Filter" AdvSceneSwitcher.action.filter="Filter"
AdvSceneSwitcher.action.filter.type.enable="Aktivieren" AdvSceneSwitcher.action.filter.type.enable="Aktivieren"
AdvSceneSwitcher.action.filter.type.disable="Deaktivieren" AdvSceneSwitcher.action.filter.type.disable="Deaktivieren"
@@ -457,7 +450,7 @@ AdvSceneSwitcher.action.macro="Makro"
AdvSceneSwitcher.action.macro.type.pause="Pausieren" AdvSceneSwitcher.action.macro.type.pause="Pausieren"
AdvSceneSwitcher.action.macro.type.unpause="Nicht mehr pausieren" AdvSceneSwitcher.action.macro.type.unpause="Nicht mehr pausieren"
AdvSceneSwitcher.action.macro.type.resetCounter="Zähler zurücksetzen" AdvSceneSwitcher.action.macro.type.resetCounter="Zähler zurücksetzen"
AdvSceneSwitcher.action.macro.type.run="Aktionen ausführen" AdvSceneSwitcher.action.macro.type.runActions="Aktionen ausführen"
AdvSceneSwitcher.action.macro.type.stop="Aktionen stoppen" AdvSceneSwitcher.action.macro.type.stop="Aktionen stoppen"
AdvSceneSwitcher.action.pluginState="Plugin-Status" AdvSceneSwitcher.action.pluginState="Plugin-Status"
AdvSceneSwitcher.action.pluginState.type.stop="Erweiterten Automatischen Szenenwechsler stoppen" AdvSceneSwitcher.action.pluginState.type.stop="Erweiterten Automatischen Szenenwechsler stoppen"
@@ -505,9 +498,9 @@ AdvSceneSwitcher.action.transition.type.scene="Szenenübergang"
AdvSceneSwitcher.action.transition.type.sceneOverride="Szenenübergang überschreiben" AdvSceneSwitcher.action.transition.type.sceneOverride="Szenenübergang überschreiben"
AdvSceneSwitcher.action.transition.type.sourceShow="Übergang der Quelle anzeigen" AdvSceneSwitcher.action.transition.type.sourceShow="Übergang der Quelle anzeigen"
AdvSceneSwitcher.action.transition.type.sourceHide="Übergang der Quelle verstecken" AdvSceneSwitcher.action.transition.type.sourceHide="Übergang der Quelle verstecken"
AdvSceneSwitcher.action.transition.entry.line1="Anpassen {{type}}{{scenes}}{{sources}}" AdvSceneSwitcher.action.transition.layout.type="Anpassen {{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Übergangstyp festlegen auf {{transitions}}" AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Übergangstyp festlegen auf {{transitions}}"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Übergangsdauer festlegen auf {{duration}}Sekunden" AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Übergangsdauer festlegen auf {{duration}}Sekunden"
AdvSceneSwitcher.action.timer="Timer" AdvSceneSwitcher.action.timer="Timer"
AdvSceneSwitcher.action.timer.type.pause="Pausieren" AdvSceneSwitcher.action.timer.type.pause="Pausieren"
AdvSceneSwitcher.action.timer.type.continue="Fortsetzen" AdvSceneSwitcher.action.timer.type.continue="Fortsetzen"
@@ -834,8 +827,6 @@ AdvSceneSwitcher.status.inactive="Inaktiv"
AdvSceneSwitcher.running="Plugin läuft" AdvSceneSwitcher.running="Plugin läuft"
AdvSceneSwitcher.stopped="Plugin gestoppt" AdvSceneSwitcher.stopped="Plugin gestoppt"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Dies scheint das erste Mal zu sein, dass der Erweiterte Szenenwechsler gestartet wurde.<br>Bitte schaue ins <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> für eine Liste von Anleitungen und Beispielen.<br>Nicht zögern und Fragen im <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">Thread</span></a> des Plugins im OBS-Forum stellen!!</p></body></html>"
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.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.milliseconds="Millisekunden"

File diff suppressed because it is too large Load Diff

View File

@@ -72,7 +72,6 @@ AdvSceneSwitcher.macroTab.add="Agregar nueva macro"
AdvSceneSwitcher.macroTab.name="Nombre:" AdvSceneSwitcher.macroTab.name="Nombre:"
AdvSceneSwitcher.macroTab.run="Ejecutar macro" AdvSceneSwitcher.macroTab.run="Ejecutar macro"
AdvSceneSwitcher.macroTab.runInParallel="Ejecutar macro en paralelo a otras macros" AdvSceneSwitcher.macroTab.runInParallel="Ejecutar macro en paralelo a otras macros"
AdvSceneSwitcher.macroTab.onChange="Realizar acciones solo en el cambio de condición"
AdvSceneSwitcher.macroTab.defaultname="Macro %1" AdvSceneSwitcher.macroTab.defaultname="Macro %1"
AdvSceneSwitcher.macroTab.copy="Crear copia" AdvSceneSwitcher.macroTab.copy="Crear copia"
AdvSceneSwitcher.macroTab.expandAll="Expandir todo" AdvSceneSwitcher.macroTab.expandAll="Expandir todo"
@@ -84,7 +83,6 @@ AdvSceneSwitcher.macroTab.highlightTrueConditions="Resaltar condiciones de la ma
AdvSceneSwitcher.macroTab.highlightPerformedActions="Resaltar acciones realizadas recientemente de la macro seleccionada actualmente" AdvSceneSwitcher.macroTab.highlightPerformedActions="Resaltar acciones realizadas recientemente de la macro seleccionada actualmente"
; Lógica de macros ; Lógica de macros
AdvSceneSwitcher.logic.none="Omitir entrada"
AdvSceneSwitcher.logic.and="Y" AdvSceneSwitcher.logic.and="Y"
AdvSceneSwitcher.logic.or="O" AdvSceneSwitcher.logic.or="O"
AdvSceneSwitcher.logic.andNot="Y no" AdvSceneSwitcher.logic.andNot="Y no"
@@ -118,9 +116,7 @@ AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour="Durante la tra
AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="Durante la transición, verifique la escena de origen de la transición" AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="Durante la transición, verifique la escena de origen de la transición"
AdvSceneSwitcher.condition.window="Ventana" AdvSceneSwitcher.condition.window="Ventana"
AdvSceneSwitcher.condition.file="Archivo" AdvSceneSwitcher.condition.file="Archivo"
AdvSceneSwitcher.condition.file.entry.line1="Contenido de{{fileType}}{{filePath}}{{conditions}}{{useRegex}}" AdvSceneSwitcher.condition.file.layout="Contenido de{{filePath}}{{conditions}}{{regex}}"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
AdvSceneSwitcher.condition.media="Medios" AdvSceneSwitcher.condition.media="Medios"
AdvSceneSwitcher.condition.media.anyOnScene="Cualquier fuente multimedia activada" AdvSceneSwitcher.condition.media.anyOnScene="Cualquier fuente multimedia activada"
AdvSceneSwitcher.condition.media.allOnScene="Todas las fuentes de medios activadas" AdvSceneSwitcher.condition.media.allOnScene="Todas las fuentes de medios activadas"
@@ -176,7 +172,7 @@ AdvSceneSwitcher.condition.record.state.start="Grabación en ejecución"
AdvSceneSwitcher.condition.record.state.pause="Grabación en pausa" AdvSceneSwitcher.condition.record.state.pause="Grabación en pausa"
AdvSceneSwitcher.condition.record.state.stop="Grabación detenida" AdvSceneSwitcher.condition.record.state.stop="Grabación detenida"
AdvSceneSwitcher.condition.process="Proceso" AdvSceneSwitcher.condition.process="Proceso"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}se está ejecutando{{focused}}y está enfocado" AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}se está ejecutando{{focused}}y está enfocado"
AdvSceneSwitcher.condition.idle="Inactivo" AdvSceneSwitcher.condition.idle="Inactivo"
AdvSceneSwitcher.condition.idle.entry="No hay entradas de teclado o ratón durante {{duration}}" AdvSceneSwitcher.condition.idle.entry="No hay entradas de teclado o ratón durante {{duration}}"
AdvSceneSwitcher.condition.pluginState="Estado del complemento" AdvSceneSwitcher.condition.pluginState="Estado del complemento"
@@ -241,14 +237,14 @@ AdvSceneSwitcher.condition.replay.state.started="Búfer de reproducción iniciad
AdvSceneSwitcher.condition.replay.state.saved="Búfer de reproducción guardado" AdvSceneSwitcher.condition.replay.state.saved="Búfer de reproducción guardado"
AdvSceneSwitcher.condition.replay.entry="{{state}}" AdvSceneSwitcher.condition.replay.entry="{{state}}"
AdvSceneSwitcher.condition.date="Fecha" AdvSceneSwitcher.condition.date="Fecha"
AdvSceneSwitcher.condition.date.anyDay="Cualquier día" AdvSceneSwitcher.day.any="Cualquier día"
AdvSceneSwitcher.condition.date.monday="Lunes" AdvSceneSwitcher.day.monday="Lunes"
AdvSceneSwitcher.condition.date.tuesday="Martes" AdvSceneSwitcher.day.tuesday="Martes"
AdvSceneSwitcher.condition.date.wednesday="Miércoles" AdvSceneSwitcher.day.wednesday="Miércoles"
AdvSceneSwitcher.condition.date.thursday="Jueves" AdvSceneSwitcher.day.thursday="Jueves"
AdvSceneSwitcher.condition.date.friday="Viernes" AdvSceneSwitcher.day.friday="Viernes"
AdvSceneSwitcher.condition.date.saturday="Sábado" AdvSceneSwitcher.day.saturday="Sábado"
AdvSceneSwitcher.condition.date.sunday="Domingo" AdvSceneSwitcher.day.sunday="Domingo"
AdvSceneSwitcher.condition.date.state.at="A las" AdvSceneSwitcher.condition.date.state.at="A las"
AdvSceneSwitcher.condition.date.state.after="Después" AdvSceneSwitcher.condition.date.state.after="Después"
AdvSceneSwitcher.condition.date.state.before="Antes" AdvSceneSwitcher.condition.date.state.before="Antes"
@@ -258,11 +254,12 @@ AdvSceneSwitcher.condition.date.ignoreDate="Si no se marca, se ignorará el comp
AdvSceneSwitcher.condition.date.ignoreTime="Si no se marca, se ignorará el componente de tiempo" AdvSceneSwitcher.condition.date.ignoreTime="Si no se marca, se ignorará el componente de tiempo"
AdvSceneSwitcher.condition.date.showAdvancedSettings="Mostrar configuración avanzada" AdvSceneSwitcher.condition.date.showAdvancedSettings="Mostrar configuración avanzada"
AdvSceneSwitcher.condition.date.showSimpleSettings="Mostrar configuración simple" AdvSceneSwitcher.condition.date.showSimpleSettings="Mostrar configuración simple"
AdvSceneSwitcher.condition.date.entry.simple="El {{dayOfWeek}} {{weekCondition}} {{ignoreWeekTime}}{{weekTime}}" AdvSceneSwitcher.condition.date.layout.simple.day="El{{dayOfWeek}}"
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}} {{ignoreDate}}{{date}} {{ignoreTime}}{{time}} {{separator}} {{date2}} {{time2}}" AdvSceneSwitcher.condition.date.layout.simple.time="{{ignoreWeekTime}}{{weekCondition}}{{weekTime}}"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}} Repetir cada {{duration}} en la coincidencia de fechas" AdvSceneSwitcher.condition.date.layout.advanced="{{condition}} {{ignoreDate}}{{date}} {{ignoreTime}}{{time}} {{separator}} {{date2}} {{time2}}"
AdvSceneSwitcher.condition.date.entry.nextMatchDate="Próxima coincidencia en: %1" AdvSceneSwitcher.condition.date.layout.repeat="{{repeat}} Repetir cada {{duration}} en la coincidencia de fechas"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} Al repetir actualizar la fecha seleccionada para repetir la fecha" AdvSceneSwitcher.condition.date.layout.nextMatchDate="Próxima coincidencia en: %1"
AdvSceneSwitcher.condition.date.layout.updateOnRepeat="{{updateOnRepeat}} Al repetir actualizar la fecha seleccionada para repetir la fecha"
AdvSceneSwitcher.condition.sceneTransform="Transformar elemento de escena" AdvSceneSwitcher.condition.sceneTransform="Transformar elemento de escena"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Obtener transformación" AdvSceneSwitcher.condition.sceneTransform.getTransform="Obtener transformación"
AdvSceneSwitcher.condition.sceneTransform.condition.match="coincide con la transformación" AdvSceneSwitcher.condition.sceneTransform.condition.match="coincide con la transformación"
@@ -315,8 +312,8 @@ AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Espere hasta que se comp
AdvSceneSwitcher.action.wait="Esperar" AdvSceneSwitcher.action.wait="Esperar"
AdvSceneSwitcher.action.wait.type.fixed="fijo" AdvSceneSwitcher.action.wait.type.fixed="fijo"
AdvSceneSwitcher.action.wait.type.random="aleatorio" AdvSceneSwitcher.action.wait.type.random="aleatorio"
AdvSceneSwitcher.action.wait.entry.fixed="Espere {{waitType}} de {{duration}}" AdvSceneSwitcher.action.wait.layout.fixed="Espere {{waitType}} de {{duration}}"
AdvSceneSwitcher.action.wait.entry.random="Espere {{waitType}} de {{duration}} a {{duration2}}" AdvSceneSwitcher.action.wait.layout.random="Espere {{waitType}} de {{duration}} a {{duration2}}"
AdvSceneSwitcher.action.audio="Audio" AdvSceneSwitcher.action.audio="Audio"
AdvSceneSwitcher.action.audio.type.mute="Silencio" AdvSceneSwitcher.action.audio.type.mute="Silencio"
AdvSceneSwitcher.action.audio.type.unmute="Activar silencio" AdvSceneSwitcher.action.audio.type.unmute="Activar silencio"
@@ -334,7 +331,6 @@ AdvSceneSwitcher.action.recording.type.start="Iniciar grabación"
AdvSceneSwitcher.action.recording.type.pause="Pausar grabación" AdvSceneSwitcher.action.recording.type.pause="Pausar grabación"
AdvSceneSwitcher.action.recording.type.unpause="Reanudar grabación" AdvSceneSwitcher.action.recording.type.unpause="Reanudar grabación"
AdvSceneSwitcher.action.recording.pause.hint="Tenga en cuenta que, dependiendo de la configuración de grabación, es posible que no pueda pausar la grabación" AdvSceneSwitcher.action.recording.pause.hint="Tenga en cuenta que, dependiendo de la configuración de grabación, es posible que no pueda pausar la grabación"
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
AdvSceneSwitcher.action.replay="Búfer de reproducción" AdvSceneSwitcher.action.replay="Búfer de reproducción"
AdvSceneSwitcher.action.replay.saveWarn="Advertencia: ¡Guardar con demasiada frecuencia puede hacer que el búfer de reproducción no se guarde realmente!" AdvSceneSwitcher.action.replay.saveWarn="Advertencia: ¡Guardar con demasiada frecuencia puede hacer que el búfer de reproducción no se guarde realmente!"
AdvSceneSwitcher.action.replay.type.stop="Detener el búfer de reproducción" AdvSceneSwitcher.action.replay.type.stop="Detener el búfer de reproducción"
@@ -343,14 +339,13 @@ AdvSceneSwitcher.action.replay.type.save="Guardar búfer de reproducción"
AdvSceneSwitcher.action.streaming="Transmisión" AdvSceneSwitcher.action.streaming="Transmisión"
AdvSceneSwitcher.action.streaming.type.stop="Detener transmisión" AdvSceneSwitcher.action.streaming.type.stop="Detener transmisión"
AdvSceneSwitcher.action.streaming.type.start="Iniciar transmisión" AdvSceneSwitcher.action.streaming.type.start="Iniciar transmisión"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="Ejecutar" AdvSceneSwitcher.action.run="Ejecutar"
AdvSceneSwitcher.action.sceneVisibility="Visibilidad del elemento de escena" AdvSceneSwitcher.action.sceneVisibility="Visibilidad del elemento de escena"
AdvSceneSwitcher.action.sceneVisibility.type.show="Mostrar" AdvSceneSwitcher.action.sceneVisibility.type.show="Mostrar"
AdvSceneSwitcher.action.sceneVisibility.type.hide="Ocultar" AdvSceneSwitcher.action.sceneVisibility.type.hide="Ocultar"
AdvSceneSwitcher.action.sceneVisibility.type.source="Fuente" AdvSceneSwitcher.action.sceneVisibility.type.source="Fuente"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Cualquiera" AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Cualquiera"
AdvSceneSwitcher.action.sceneVisibility.entry="En{{scenes}}{{actions}}{{sources}}" AdvSceneSwitcher.action.sceneVisibility.layout="En{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="Filtro" AdvSceneSwitcher.action.filter="Filtro"
AdvSceneSwitcher.action.filter.type.enable="Habilitar" AdvSceneSwitcher.action.filter.type.enable="Habilitar"
AdvSceneSwitcher.action.filter.type.disable="Deshabilitar" AdvSceneSwitcher.action.filter.type.disable="Deshabilitar"
@@ -375,7 +370,7 @@ AdvSceneSwitcher.action.macro="Macro"
AdvSceneSwitcher.action.macro.type.pause="Pausa" AdvSceneSwitcher.action.macro.type.pause="Pausa"
AdvSceneSwitcher.action.macro.type.unpause="Reanudar" AdvSceneSwitcher.action.macro.type.unpause="Reanudar"
AdvSceneSwitcher.action.macro.type.resetCounter="Reiniciar contador" AdvSceneSwitcher.action.macro.type.resetCounter="Reiniciar contador"
AdvSceneSwitcher.action.macro.type.run="Ejecutar" AdvSceneSwitcher.action.macro.type.runActions="Ejecutar"
AdvSceneSwitcher.action.macro.type.stop="Detener" AdvSceneSwitcher.action.macro.type.stop="Detener"
AdvSceneSwitcher.action.pluginState="Estado del complemento" AdvSceneSwitcher.action.pluginState="Estado del complemento"
AdvSceneSwitcher.action.pluginState.type.stop="Detener el complemento Advanced Scene Switcher" AdvSceneSwitcher.action.pluginState.type.stop="Detener el complemento Advanced Scene Switcher"
@@ -423,9 +418,9 @@ AdvSceneSwitcher.action.transition.type.scene="transición de escena"
AdvSceneSwitcher.action.transition.type.sceneOverride="anulación de transición de escena" AdvSceneSwitcher.action.transition.type.sceneOverride="anulación de transición de escena"
AdvSceneSwitcher.action.transition.type.sourceShow="transición del programa de origen" AdvSceneSwitcher.action.transition.type.sourceShow="transición del programa de origen"
AdvSceneSwitcher.action.transition.type.sourceHide="fuente ocultar transición" AdvSceneSwitcher.action.transition.type.sourceHide="fuente ocultar transición"
AdvSceneSwitcher.action.transition.entry.line1="Modificar {{type}}{{scenes}}{{sources}}" AdvSceneSwitcher.action.transition.layout.type="Modificar {{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Establecer el tipo de transición en {{transitions}}" AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Establecer el tipo de transición en {{transitions}}"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Establecer la duración de la transición en {{duration}}segundos" AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Establecer la duración de la transición en {{duration}}segundos"
AdvSceneSwitcher.action.timer="Temporizador" AdvSceneSwitcher.action.timer="Temporizador"
AdvSceneSwitcher.action.timer.type.pause="Pausa" AdvSceneSwitcher.action.timer.type.pause="Pausa"
AdvSceneSwitcher.action.timer.type.continue="Continuar" AdvSceneSwitcher.action.timer.type.continue="Continuar"
@@ -680,8 +675,6 @@ AdvSceneSwitcher.status.inactive="Inactivo"
AdvSceneSwitcher.running="Iniciar complemento" AdvSceneSwitcher.running="Iniciar complemento"
AdvSceneSwitcher.stopped="Complemento de detención" AdvSceneSwitcher.stopped="Complemento de detención"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Esta parece ser la primera vez que se inicia el conmutador de escena avanzado.<br>Por favor, eche un vistazo a <a href=\"https:/ /github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> para obtener una lista de guías y ejemplos.<br>No dude en hacer preguntas en el complemento <a href=\"https://obsproject.com /forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">hilo</span></a> en los foros de OBS!</p></body></html>"
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.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.milliseconds="milisegundos"

View File

@@ -78,7 +78,6 @@ AdvSceneSwitcher.macroTab.add="Ajouter une nouvelle macro"
AdvSceneSwitcher.macroTab.name="Nom :" AdvSceneSwitcher.macroTab.name="Nom :"
AdvSceneSwitcher.macroTab.run="Exécuter la macro" AdvSceneSwitcher.macroTab.run="Exécuter la macro"
AdvSceneSwitcher.macroTab.runInParallel="Exécuter la macro en parallèle avec d'autres macros" AdvSceneSwitcher.macroTab.runInParallel="Exécuter la macro en parallèle avec d'autres macros"
AdvSceneSwitcher.macroTab.onChange="Exécuter des actions uniquement en cas de changement de condition"
AdvSceneSwitcher.macroTab.defaultname="Macro %1" AdvSceneSwitcher.macroTab.defaultname="Macro %1"
AdvSceneSwitcher.macroTab.defaultGroupName="Groupe %1" AdvSceneSwitcher.macroTab.defaultGroupName="Groupe %1"
AdvSceneSwitcher.macroTab.removeSingleMacroPopup.text="Êtes-vous sûr de vouloir supprimer \"%1\" ?" AdvSceneSwitcher.macroTab.removeSingleMacroPopup.text="Êtes-vous sûr de vouloir supprimer \"%1\" ?"
@@ -132,7 +131,6 @@ AdvSceneSwitcher.macroList.deleted="supprimé"
AdvSceneSwitcher.macroList.duplicate="\"%1\" est déjà sélectionné !" AdvSceneSwitcher.macroList.duplicate="\"%1\" est déjà sélectionné !"
; Macro Logic ; Macro Logic
AdvSceneSwitcher.logic.none="Ignorer l'entrée"
AdvSceneSwitcher.logic.and="Et" AdvSceneSwitcher.logic.and="Et"
AdvSceneSwitcher.logic.or="Ou" AdvSceneSwitcher.logic.or="Ou"
AdvSceneSwitcher.logic.andNot="Et pas" AdvSceneSwitcher.logic.andNot="Et pas"
@@ -188,8 +186,6 @@ AdvSceneSwitcher.condition.file="Fichier"
AdvSceneSwitcher.condition.file.type.match="correspond à" AdvSceneSwitcher.condition.file.type.match="correspond à"
AdvSceneSwitcher.condition.file.type.contentChange="a changé de contenu" AdvSceneSwitcher.condition.file.type.contentChange="a changé de contenu"
AdvSceneSwitcher.condition.file.type.dateChange="a changé de date de modification" AdvSceneSwitcher.condition.file.type.dateChange="a changé de date de modification"
AdvSceneSwitcher.condition.file.remote="Fichier distant"
AdvSceneSwitcher.condition.file.local="Fichier local"
AdvSceneSwitcher.condition.media="Média" AdvSceneSwitcher.condition.media="Média"
AdvSceneSwitcher.condition.media.source="Source" AdvSceneSwitcher.condition.media.source="Source"
AdvSceneSwitcher.condition.media.anyOnScene="Toute source média sur la scène" AdvSceneSwitcher.condition.media.anyOnScene="Toute source média sur la scène"
@@ -261,10 +257,10 @@ AdvSceneSwitcher.condition.video.layout.modelPath="Données du modèle (classifi
AdvSceneSwitcher.condition.video.layout.minNeighbor="Nombre minimum de voisins :{{minNeighbors}}" AdvSceneSwitcher.condition.video.layout.minNeighbor="Nombre minimum de voisins :{{minNeighbors}}"
AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Réduire la charge CPU en effectuant la vérification uniquement toutes les{{throttleCount}}millisecondes" AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Réduire la charge CPU en effectuant la vérification uniquement toutes les{{throttleCount}}millisecondes"
AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Effectuer la vérification uniquement dans la zone" AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Effectuer la vérification uniquement dans la zone"
AdvSceneSwitcher.condition.video.layout.ocrColorPick="Vérifier la couleur du texte :{{textColor}}{{selectColor}}" AdvSceneSwitcher.condition.video.layout.ocrColorPick="Vérifier la couleur du texte :{{color}}"
AdvSceneSwitcher.condition.video.layout.ocrTextType="Vérifier le type de texte :{{textType}}" AdvSceneSwitcher.condition.video.layout.ocrTextType="Vérifier le type de texte :{{textType}}"
AdvSceneSwitcher.condition.video.layout.ocrLanguage="Vérifier la langue :{{languageCode}}" AdvSceneSwitcher.condition.video.layout.ocrLanguage="Vérifier la langue :{{languageCode}}"
AdvSceneSwitcher.condition.video.layout.color="Vérifier la couleur :{{color}}{{selectColor}}" AdvSceneSwitcher.condition.video.layout.color="Vérifier la couleur :{{color}}"
AdvSceneSwitcher.condition.video.minSize="Taille minimale :" AdvSceneSwitcher.condition.video.minSize="Taille minimale :"
AdvSceneSwitcher.condition.video.maxSize="Taille maximale :" AdvSceneSwitcher.condition.video.maxSize="Taille maximale :"
AdvSceneSwitcher.condition.video.selectArea="Sélectionner la zone" AdvSceneSwitcher.condition.video.selectArea="Sélectionner la zone"
@@ -282,8 +278,8 @@ AdvSceneSwitcher.condition.record.state.start="Enregistrement en cours"
AdvSceneSwitcher.condition.record.state.pause="Enregistrement en pause" AdvSceneSwitcher.condition.record.state.pause="Enregistrement en pause"
AdvSceneSwitcher.condition.record.state.stop="Arrêt de l'enregistrement" AdvSceneSwitcher.condition.record.state.stop="Arrêt de l'enregistrement"
AdvSceneSwitcher.condition.process="Processus" AdvSceneSwitcher.condition.process="Processus"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}en cours d'exécution{{focused}}et est au premier plan" AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}en cours d'exécution{{focused}}et est au premier plan"
AdvSceneSwitcher.condition.process.entry.focus="Processus au premier plan actuel :{{focusProcess}}" AdvSceneSwitcher.condition.process.layout.focus="Processus au premier plan actuel :{{focusProcess}}"
AdvSceneSwitcher.condition.idle="Inactif" AdvSceneSwitcher.condition.idle="Inactif"
AdvSceneSwitcher.condition.idle.entry="Aucune entrée de clavier ou de souris pendant{{duration}}" AdvSceneSwitcher.condition.idle.entry="Aucune entrée de clavier ou de souris pendant{{duration}}"
AdvSceneSwitcher.condition.pluginState="État du plugin" AdvSceneSwitcher.condition.pluginState="État du plugin"
@@ -356,14 +352,14 @@ AdvSceneSwitcher.condition.replay.state.stopped="Tampon de répétition arrêté
AdvSceneSwitcher.condition.replay.state.started="Tampon de répétition démarré" AdvSceneSwitcher.condition.replay.state.started="Tampon de répétition démarré"
AdvSceneSwitcher.condition.replay.state.saved="Tampon de répétition enregistré" AdvSceneSwitcher.condition.replay.state.saved="Tampon de répétition enregistré"
AdvSceneSwitcher.condition.date="Date" AdvSceneSwitcher.condition.date="Date"
AdvSceneSwitcher.condition.date.anyDay="N'importe quel jour" AdvSceneSwitcher.day.any="N'importe quel jour"
AdvSceneSwitcher.condition.date.monday="Lundi" AdvSceneSwitcher.day.monday="Lundi"
AdvSceneSwitcher.condition.date.tuesday="Mardi" AdvSceneSwitcher.day.tuesday="Mardi"
AdvSceneSwitcher.condition.date.wednesday="Mercredi" AdvSceneSwitcher.day.wednesday="Mercredi"
AdvSceneSwitcher.condition.date.thursday="Jeudi" AdvSceneSwitcher.day.thursday="Jeudi"
AdvSceneSwitcher.condition.date.friday="Vendredi" AdvSceneSwitcher.day.friday="Vendredi"
AdvSceneSwitcher.condition.date.saturday="Samedi" AdvSceneSwitcher.day.saturday="Samedi"
AdvSceneSwitcher.condition.date.sunday="Dimanche" AdvSceneSwitcher.day.sunday="Dimanche"
AdvSceneSwitcher.condition.date.state.at="À" AdvSceneSwitcher.condition.date.state.at="À"
AdvSceneSwitcher.condition.date.state.after="Après" AdvSceneSwitcher.condition.date.state.after="Après"
AdvSceneSwitcher.condition.date.state.before="Avant" AdvSceneSwitcher.condition.date.state.before="Avant"
@@ -374,11 +370,12 @@ AdvSceneSwitcher.condition.date.ignoreDate="Si non cochée, la composante date s
AdvSceneSwitcher.condition.date.ignoreTime="Si non cochée, la composante heure sera ignorée" AdvSceneSwitcher.condition.date.ignoreTime="Si non cochée, la composante heure sera ignorée"
AdvSceneSwitcher.condition.date.showAdvancedSettings="Afficher les paramètres avancés" AdvSceneSwitcher.condition.date.showAdvancedSettings="Afficher les paramètres avancés"
AdvSceneSwitcher.condition.date.showSimpleSettings="Afficher les paramètres simples" AdvSceneSwitcher.condition.date.showSimpleSettings="Afficher les paramètres simples"
AdvSceneSwitcher.condition.date.entry.simple="Le{{dayOfWeek}}{{weekCondition}}{{ignoreWeekTime}}{{weekTime}}" AdvSceneSwitcher.condition.date.layout.simple.day="Le{{dayOfWeek}}"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}}Répéter toutes les{{duration}}lorsque la date correspond" AdvSceneSwitcher.condition.date.layout.simple.time="{{ignoreWeekTime}}{{weekCondition}}{{weekTime}}"
AdvSceneSwitcher.condition.date.entry.pattern="La date actuelle \"{{currentDate}}\" correspond au motif{{pattern}}" AdvSceneSwitcher.condition.date.layout.repeat="{{repeat}}Répéter toutes les{{duration}}lorsque la date correspond"
AdvSceneSwitcher.condition.date.entry.nextMatchDate="Prochaine correspondance à : %1" AdvSceneSwitcher.condition.date.layout.pattern="La date actuelle \"{{currentDate}}\" correspond au motif{{pattern}}"
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.date.layout.nextMatchDate="Prochaine correspondance à : %1"
AdvSceneSwitcher.condition.date.layout.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="Transformation de l'élément de la scène"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Obtenir la transformation" AdvSceneSwitcher.condition.sceneTransform.getTransform="Obtenir la transformation"
AdvSceneSwitcher.condition.sceneTransform.condition.match="correspond à la transformation" AdvSceneSwitcher.condition.sceneTransform.condition.match="correspond à la transformation"
@@ -463,8 +460,8 @@ AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Attendre que la transiti
AdvSceneSwitcher.action.wait="Attendre" AdvSceneSwitcher.action.wait="Attendre"
AdvSceneSwitcher.action.wait.type.fixed="fixe" AdvSceneSwitcher.action.wait.type.fixed="fixe"
AdvSceneSwitcher.action.wait.type.random="aléatoire" AdvSceneSwitcher.action.wait.type.random="aléatoire"
AdvSceneSwitcher.action.wait.entry.fixed="Attendre pour{{waitType}}une durée de{{duration}}" AdvSceneSwitcher.action.wait.layout.fixed="Attendre pour{{waitType}}une durée de{{duration}}"
AdvSceneSwitcher.action.wait.entry.random="Attendre pour{{waitType}}une durée de{{duration}}à{{duration2}}" AdvSceneSwitcher.action.wait.layout.random="Attendre pour{{waitType}}une durée de{{duration}}à{{duration2}}"
AdvSceneSwitcher.action.audio="Audio" AdvSceneSwitcher.action.audio="Audio"
AdvSceneSwitcher.action.audio.type.mute="Muet" AdvSceneSwitcher.action.audio.type.mute="Muet"
AdvSceneSwitcher.action.audio.type.unmute="Activer le son" AdvSceneSwitcher.action.audio.type.unmute="Activer le son"
@@ -510,7 +507,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="Masquer"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="Basculer" AdvSceneSwitcher.action.sceneVisibility.type.toggle="Basculer"
AdvSceneSwitcher.action.sceneVisibility.type.source="Source" AdvSceneSwitcher.action.sceneVisibility.type.source="Source"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="N'importe" AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="N'importe"
AdvSceneSwitcher.action.sceneVisibility.entry="Sur{{scenes}}{{actions}}{{sources}}" AdvSceneSwitcher.action.sceneVisibility.layout="Sur{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="Filtre" AdvSceneSwitcher.action.filter="Filtre"
AdvSceneSwitcher.action.filter.type.enable="Activer" AdvSceneSwitcher.action.filter.type.enable="Activer"
AdvSceneSwitcher.action.filter.type.disable="Désactiver" AdvSceneSwitcher.action.filter.type.disable="Désactiver"
@@ -553,7 +550,7 @@ AdvSceneSwitcher.action.macro="Macro"
AdvSceneSwitcher.action.macro.type.pause="Pause" AdvSceneSwitcher.action.macro.type.pause="Pause"
AdvSceneSwitcher.action.macro.type.unpause="Reprendre" AdvSceneSwitcher.action.macro.type.unpause="Reprendre"
AdvSceneSwitcher.action.macro.type.resetCounter="Réinitialiser le compteur" AdvSceneSwitcher.action.macro.type.resetCounter="Réinitialiser le compteur"
AdvSceneSwitcher.action.macro.type.run="Exécuter les actions" AdvSceneSwitcher.action.macro.type.runActions="Exécuter les actions"
AdvSceneSwitcher.action.macro.type.stop="Arrêter les actions" AdvSceneSwitcher.action.macro.type.stop="Arrêter les actions"
AdvSceneSwitcher.action.macro.type.disableAction="Désactiver l'action" AdvSceneSwitcher.action.macro.type.disableAction="Désactiver l'action"
AdvSceneSwitcher.action.macro.type.enableAction="Activer l'action" AdvSceneSwitcher.action.macro.type.enableAction="Activer l'action"
@@ -622,9 +619,9 @@ AdvSceneSwitcher.action.transition.type.scene="Transition de scène"
AdvSceneSwitcher.action.transition.type.sceneOverride="Remplacement de la transition de scène" AdvSceneSwitcher.action.transition.type.sceneOverride="Remplacement de la transition de scène"
AdvSceneSwitcher.action.transition.type.sourceShow="Transition d'affichage de la source" AdvSceneSwitcher.action.transition.type.sourceShow="Transition d'affichage de la source"
AdvSceneSwitcher.action.transition.type.sourceHide="Transition de masquage de la source" AdvSceneSwitcher.action.transition.type.sourceHide="Transition de masquage de la source"
AdvSceneSwitcher.action.transition.entry.line1="Modifier{{type}}{{scenes}}{{sources}}" AdvSceneSwitcher.action.transition.layout.type="Modifier{{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Définir le type de transition sur{{transitions}}" AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Définir le type de transition sur{{transitions}}"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Définir la durée de la transition à{{duration}}secondes" AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Définir la durée de la transition à{{duration}}secondes"
AdvSceneSwitcher.action.timer="Minuterie" AdvSceneSwitcher.action.timer="Minuterie"
AdvSceneSwitcher.action.timer.type.pause="Mettre en pause" AdvSceneSwitcher.action.timer.type.pause="Mettre en pause"
AdvSceneSwitcher.action.timer.type.continue="Continuer" AdvSceneSwitcher.action.timer.type.continue="Continuer"
@@ -701,8 +698,8 @@ 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.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.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.currentSegmentValue="Valeur actuelle :"
AdvSceneSwitcher.action.variable.layout.substringIndex="Début de la sous-chaîne :{{subStringStart}}Taille de la sous-chaîne :{{subStringSize}}" AdvSceneSwitcher.action.variable.layout.substringIndex="Début de la sous-chaîne :{{subStringStart}}Taille de la sous-chaîne :{{subStringSize}}{{subStringRegex}}"
AdvSceneSwitcher.action.variable.layout.substringRegex="Attribuer la valeur du match{{regexMatchIdx}}en utilisant une expression régulière :" AdvSceneSwitcher.action.variable.layout.substringRegex="Attribuer la valeur du match{{regexMatchIdx}}en utilisant une expression régulière:{{subStringRegex}}"
AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}Utiliser un message personnalisé{{inputPrompt}}" 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.variable.layout.userInput.placeholder="{{useInputPlaceholder}}Remplir avec un indicateur de position{{inputPlaceholder}}"
AdvSceneSwitcher.action.projector="Projecteur" AdvSceneSwitcher.action.projector="Projecteur"
@@ -1122,8 +1119,6 @@ AdvSceneSwitcher.status.inactive="Inactif"
AdvSceneSwitcher.running="Plugin en cours d'exécution" AdvSceneSwitcher.running="Plugin en cours d'exécution"
AdvSceneSwitcher.stopped="Plugin arrêté" AdvSceneSwitcher.stopped="Plugin arrêté"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Il semble que ce soit la première fois que l'Advanced Scene Switcher est démarré.<br>Veuillez consulter le <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> pour obtenir une liste de guides et d'exemples.<br>N'hésitez pas à poser des questions dans le <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">fil de discussion</span></a> du plugin sur les forums OBS !</p></body></html>"
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.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.milliseconds="millisecondes"

View File

@@ -42,7 +42,6 @@ AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailure="プラグイ
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage="<html><body>次のプラグイン ライブラリの読み込みに失敗したため、一部の高機能シーンスイッチャーが利用できなくなる可能性があります:%1詳細については、OBS ログを確認してください。<br>このメッセージは、[全般] タブで無効にすることができます。 </body></html>" AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage="<html><body>次のプラグイン ライブラリの読み込みに失敗したため、一部の高機能シーンスイッチャーが利用できなくなる可能性があります:%1詳細については、OBS ログを確認してください。<br>このメッセージは、[全般] タブで無効にすることができます。 </body></html>"
AdvSceneSwitcher.generalTab.generalBehavior.warnCorruptedInstallMessage="プラグインが正しくインストールできておらずクラッシュする可能性があります!\nプラグインが正しくインストールされているか確認してください" AdvSceneSwitcher.generalTab.generalBehavior.warnCorruptedInstallMessage="プラグインが正しくインストールできておらずクラッシュする可能性があります!\nプラグインが正しくインストールされているか確認してください"
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="旧来型マクロ設定のタブを非表示にする(廃止予定タブ)" AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="旧来型マクロ設定のタブを非表示にする(廃止予定タブ)"
AdvSceneSwitcher.generalTab.generalBehavior.disableMacroWidgetCache="マクロウィジェットのキャッシングを無効にする"
AdvSceneSwitcher.generalTab.matchBehavior="マッチした場合の動作(廃止予定の設定項目)" AdvSceneSwitcher.generalTab.matchBehavior="マッチした場合の動作(廃止予定の設定項目)"
AdvSceneSwitcher.generalTab.priority="優先順位" AdvSceneSwitcher.generalTab.priority="優先順位"
AdvSceneSwitcher.generalTab.priority.description="切り替え方法の優先順位(優先順位が高いものが上)" AdvSceneSwitcher.generalTab.priority.description="切り替え方法の優先順位(優先順位が高いものが上)"
@@ -165,7 +164,6 @@ AdvSceneSwitcher.macroTab.run.tooltip="条件に関係なくすべてのマク
AdvSceneSwitcher.macroTab.runElse="マクロ実行else" AdvSceneSwitcher.macroTab.runElse="マクロ実行else"
AdvSceneSwitcher.macroTab.runFail="\"%1\" の実行に失敗しました!\nいずれかのアクションが失敗したか、マクロがすでに実行されています。\n停止しますか" AdvSceneSwitcher.macroTab.runFail="\"%1\" の実行に失敗しました!\nいずれかのアクションが失敗したか、マクロがすでに実行されています。\n停止しますか"
AdvSceneSwitcher.macroTab.runInParallel="他のマクロと並行してマクロを実行する" AdvSceneSwitcher.macroTab.runInParallel="他のマクロと並行してマクロを実行する"
AdvSceneSwitcher.macroTab.onChange="条件変更時のみアクションを実行"
AdvSceneSwitcher.macroTab.defaultname="マクロ %1" AdvSceneSwitcher.macroTab.defaultname="マクロ %1"
AdvSceneSwitcher.macroTab.defaultGroupName="グループ %1" AdvSceneSwitcher.macroTab.defaultGroupName="グループ %1"
AdvSceneSwitcher.macroTab.macroNameExists="名前 \"%1\" は既にマクロで使用されています。" AdvSceneSwitcher.macroTab.macroNameExists="名前 \"%1\" は既にマクロで使用されています。"
@@ -277,7 +275,6 @@ AdvSceneSwitcher.macroList.deleted="削除"
AdvSceneSwitcher.macroList.duplicate="\"%1\" はすでに選択されています!" AdvSceneSwitcher.macroList.duplicate="\"%1\" はすでに選択されています!"
# Macro Logic # Macro Logic
AdvSceneSwitcher.logic.none="入力無視"
; AdvSceneSwitcher.logic.and="And" ; AdvSceneSwitcher.logic.and="And"
; AdvSceneSwitcher.logic.or="Or" ; AdvSceneSwitcher.logic.or="Or"
; AdvSceneSwitcher.logic.andNot="And not" ; AdvSceneSwitcher.logic.andNot="And not"
@@ -333,14 +330,6 @@ AdvSceneSwitcher.condition.window.entry.text="{{checkText}}ウィンドウにテ
AdvSceneSwitcher.condition.window.entry.text.note="このオプションは、ウィンドウに表示されているすべてのテキストに対して機能するとは限りません。\nその場合は、代わりにビデオ条件 OCR チェックの使用を検討してください。" AdvSceneSwitcher.condition.window.entry.text.note="このオプションは、ウィンドウに表示されているすべてのテキストに対して機能するとは限りません。\nその場合は、代わりにビデオ条件 OCR チェックの使用を検討してください。"
AdvSceneSwitcher.condition.window.entry.currentFocus="現在のフォーカスウィンドウ:{{focusWindow}}" AdvSceneSwitcher.condition.window.entry.currentFocus="現在のフォーカスウィンドウ:{{focusWindow}}"
AdvSceneSwitcher.condition.file="ファイル" AdvSceneSwitcher.condition.file="ファイル"
; AdvSceneSwitcher.condition.file.type.match="matches"
AdvSceneSwitcher.condition.file.type.contentChange="内容変更しました"
AdvSceneSwitcher.condition.file.type.dateChange="更新日変更"
AdvSceneSwitcher.condition.file.remote="リモートファイル"
AdvSceneSwitcher.condition.file.local="ローカルファイル"
; AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
; AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
; AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
; AdvSceneSwitcher.condition.media="Media" ; AdvSceneSwitcher.condition.media="Media"
AdvSceneSwitcher.condition.media.checkType.state="状態が一致" AdvSceneSwitcher.condition.media.checkType.state="状態が一致"
AdvSceneSwitcher.condition.media.checkType.time="時間が一致" AdvSceneSwitcher.condition.media.checkType.time="時間が一致"
@@ -425,12 +414,12 @@ AdvSceneSwitcher.condition.video.layout.minNeighbor="最小近傍数:{{minNeighb
AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}:{{throttleCount}}ミリ秒ごとにのみパフォーマンスチェックを実行することでCPU負荷を軽減します" AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}:{{throttleCount}}ミリ秒ごとにのみパフォーマンスチェックを実行することでCPU負荷を軽減します"
AdvSceneSwitcher.condition.video.layout.checkAreaEnable="エリア内のみチェックを実施" AdvSceneSwitcher.condition.video.layout.checkAreaEnable="エリア内のみチェックを実施"
; AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" ; AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
AdvSceneSwitcher.condition.video.layout.ocrColorPick="テキストカラーの確認:{{textColor}}{{selectColor}}" AdvSceneSwitcher.condition.video.layout.ocrColorPick="テキストカラーの確認:{{color}}"
AdvSceneSwitcher.condition.video.layout.ocrTextType="テキストタイプの確認:{{textType}}" AdvSceneSwitcher.condition.video.layout.ocrTextType="テキストタイプの確認:{{textType}}"
AdvSceneSwitcher.condition.video.layout.ocrBaseDir="Tesseractベースディレクトリ:{{tesseractBaseDir}}" AdvSceneSwitcher.condition.video.layout.ocrBaseDir="Tesseractベースディレクトリ:{{tesseractBaseDir}}"
AdvSceneSwitcher.condition.video.layout.ocrLanguage="言語の確認:{{languageCode}}" AdvSceneSwitcher.condition.video.layout.ocrLanguage="言語の確認:{{languageCode}}"
AdvSceneSwitcher.condition.video.layout.ocrConfig="設定ファイル:{{configFile}}{{openConfigFile}}{{reloadConfig}}{{configFileHint}}" AdvSceneSwitcher.condition.video.layout.ocrConfig="設定ファイル:{{configFile}}{{openConfigFile}}{{reloadConfig}}{{configFileHint}}"
AdvSceneSwitcher.condition.video.layout.color="カラーを確認してください:{{color}}{{selectColor}}" AdvSceneSwitcher.condition.video.layout.color="カラーを確認してください:{{color}}"
AdvSceneSwitcher.condition.video.minSize="最小サイズ:" AdvSceneSwitcher.condition.video.minSize="最小サイズ:"
AdvSceneSwitcher.condition.video.maxSize="最大サイズ:" AdvSceneSwitcher.condition.video.maxSize="最大サイズ:"
AdvSceneSwitcher.condition.video.selectArea="範囲指定" AdvSceneSwitcher.condition.video.selectArea="範囲指定"
@@ -455,8 +444,8 @@ AdvSceneSwitcher.condition.stream.service.tooltip="現在のサービス名: %1"
AdvSceneSwitcher.condition.record.state.duration="録音時間が長くなっています。" AdvSceneSwitcher.condition.record.state.duration="録音時間が長くなっています。"
; AdvSceneSwitcher.condition.record.entry="{{condition}}{{duration}}" ; AdvSceneSwitcher.condition.record.entry="{{condition}}{{duration}}"
AdvSceneSwitcher.condition.process="プロセス" AdvSceneSwitcher.condition.process="プロセス"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}が実行中{{focused}}に集中しています" AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}が実行中{{focused}}に集中しています"
AdvSceneSwitcher.condition.process.entry.focus="現在のフォアグラウンドプロセス:{{focusProcess}}" AdvSceneSwitcher.condition.process.layout.focus="現在のフォアグラウンドプロセス:{{focusProcess}}"
AdvSceneSwitcher.condition.idle="アイドル" AdvSceneSwitcher.condition.idle="アイドル"
AdvSceneSwitcher.condition.idle.entry="{{duration}}の間、キーボードまたはマウスの入力がありません" AdvSceneSwitcher.condition.idle.entry="{{duration}}の間、キーボードまたはマウスの入力がありません"
AdvSceneSwitcher.condition.pluginState="プラグインの状態" AdvSceneSwitcher.condition.pluginState="プラグインの状態"
@@ -556,14 +545,14 @@ AdvSceneSwitcher.condition.replay.state.started="リプレイバッファを開
AdvSceneSwitcher.condition.replay.state.saved="リプレイバッファを保存" AdvSceneSwitcher.condition.replay.state.saved="リプレイバッファを保存"
; AdvSceneSwitcher.condition.replay.entry="{{state}}" ; AdvSceneSwitcher.condition.replay.entry="{{state}}"
AdvSceneSwitcher.condition.date="日付条件" AdvSceneSwitcher.condition.date="日付条件"
AdvSceneSwitcher.condition.date.anyDay="いつでも" AdvSceneSwitcher.day.any="いつでも"
AdvSceneSwitcher.condition.date.monday="月曜日" AdvSceneSwitcher.day.monday="月曜日"
AdvSceneSwitcher.condition.date.tuesday="火曜日" AdvSceneSwitcher.day.tuesday="火曜日"
AdvSceneSwitcher.condition.date.wednesday="水曜日" AdvSceneSwitcher.day.wednesday="水曜日"
AdvSceneSwitcher.condition.date.thursday="木曜日" AdvSceneSwitcher.day.thursday="木曜日"
AdvSceneSwitcher.condition.date.friday="金曜日" AdvSceneSwitcher.day.friday="金曜日"
AdvSceneSwitcher.condition.date.saturday="土曜日" AdvSceneSwitcher.day.saturday="土曜日"
AdvSceneSwitcher.condition.date.sunday="日曜日" AdvSceneSwitcher.day.sunday="日曜日"
; AdvSceneSwitcher.condition.date.state.at="At" ; AdvSceneSwitcher.condition.date.state.at="At"
AdvSceneSwitcher.condition.date.state.after="アフター" AdvSceneSwitcher.condition.date.state.after="アフター"
AdvSceneSwitcher.condition.date.state.before="前" AdvSceneSwitcher.condition.date.state.before="前"
@@ -574,12 +563,9 @@ AdvSceneSwitcher.condition.date.ignoreDate="チェックを外すと日付部分
AdvSceneSwitcher.condition.date.ignoreTime="チェックを外すと時間要素は無視されます" AdvSceneSwitcher.condition.date.ignoreTime="チェックを外すと時間要素は無視されます"
AdvSceneSwitcher.condition.date.showAdvancedSettings="詳細設定を表示" AdvSceneSwitcher.condition.date.showAdvancedSettings="詳細設定を表示"
AdvSceneSwitcher.condition.date.showSimpleSettings="簡単設定を表示" AdvSceneSwitcher.condition.date.showSimpleSettings="簡単設定を表示"
; AdvSceneSwitcher.condition.date.entry.simple="On{{dayOfWeek}}{{weekCondition}}{{ignoreWeekTime}}{{weekTime}}" AdvSceneSwitcher.condition.date.layout.repeat="{{repeat}}日付の一致ごとに{{duration}}繰り返します"
; AdvSceneSwitcher.condition.date.entry.advanced="{{condition}}{{ignoreDate}}{{date}}{{ignoreTime}}{{time}}{{separator}}{{date2}}{{time2}}" AdvSceneSwitcher.condition.date.layout.pattern="現在の日付「{{currentDate}}」はパターン{{pattern}}と一致します"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}}日付の一致ごとに{{duration}}繰り返します" AdvSceneSwitcher.condition.date.layout.updateOnRepeat="{{updateOnRepeat}}繰り返しの場合、選択した日付を繰り返しの日付に更新します"
AdvSceneSwitcher.condition.date.entry.pattern="現在の日付「{{currentDate}}」はパターン{{pattern}}と一致します"
; AdvSceneSwitcher.condition.date.entry.nextMatchDate="Next match at: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}}繰り返しの場合、選択した日付を繰り返しの日付に更新します"
; AdvSceneSwitcher.condition.sceneTransform="Scene item transform" ; AdvSceneSwitcher.condition.sceneTransform="Scene item transform"
; AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform" ; AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
AdvSceneSwitcher.condition.sceneTransform.getCurrentValue="現在の値を取得" AdvSceneSwitcher.condition.sceneTransform.getCurrentValue="現在の値を取得"
@@ -814,8 +800,8 @@ AdvSceneSwitcher.action.scene.blockUntilTransitionDone="目的のシーンへの
; AdvSceneSwitcher.action.wait="Wait" ; AdvSceneSwitcher.action.wait="Wait"
AdvSceneSwitcher.action.wait.type.fixed="指定" AdvSceneSwitcher.action.wait.type.fixed="指定"
AdvSceneSwitcher.action.wait.type.random="ランダム" AdvSceneSwitcher.action.wait.type.random="ランダム"
AdvSceneSwitcher.action.wait.entry.fixed="{{waitType}}期間{{duration}}待機します" AdvSceneSwitcher.action.wait.layout.fixed="{{waitType}}期間{{duration}}待機します"
AdvSceneSwitcher.action.wait.entry.random="{{duration}}から{{duration2}}までの{{waitType}}期間待ちます" AdvSceneSwitcher.action.wait.layout.random="{{duration}}から{{duration2}}までの{{waitType}}期間待ちます"
AdvSceneSwitcher.action.audio="音声" AdvSceneSwitcher.action.audio="音声"
AdvSceneSwitcher.action.audio.type.mute="ミュート" AdvSceneSwitcher.action.audio.type.mute="ミュート"
AdvSceneSwitcher.action.audio.type.unmute="ミュート解除" AdvSceneSwitcher.action.audio.type.unmute="ミュート解除"
@@ -844,7 +830,6 @@ AdvSceneSwitcher.action.recording.type.changeOutputFolder="出力フォルダの
AdvSceneSwitcher.action.recording.type.changeOutputFileFormat="ファイル名の書式変更" AdvSceneSwitcher.action.recording.type.changeOutputFileFormat="ファイル名の書式変更"
AdvSceneSwitcher.action.recording.pause.hint="録画設定によっては録画を一時停止できない場合がありますのでご注意ください" AdvSceneSwitcher.action.recording.pause.hint="録画設定によっては録画を一時停止できない場合がありますのでご注意ください"
AdvSceneSwitcher.action.recording.split.hint="まずOBS設定で自動ファイル分割を有効にしてください" AdvSceneSwitcher.action.recording.split.hint="まずOBS設定で自動ファイル分割を有効にしてください"
; AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
AdvSceneSwitcher.action.replay="リプレイバッファー" AdvSceneSwitcher.action.replay="リプレイバッファー"
AdvSceneSwitcher.action.replay.saveWarn="警告: 頻繁に保存しすぎると、リプレイ バッファーが実際には保存されなくなる可能性があります。" AdvSceneSwitcher.action.replay.saveWarn="警告: 頻繁に保存しすぎると、リプレイ バッファーが実際には保存されなくなる可能性があります。"
AdvSceneSwitcher.action.replay.durationWarn="注意: 最大リプレイ時間の変更は、次回再生バッファを開始したときから適用されます!" AdvSceneSwitcher.action.replay.durationWarn="注意: 最大リプレイ時間の変更は、次回再生バッファを開始したときから適用されます!"
@@ -871,7 +856,6 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="非表示"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="切り替え" AdvSceneSwitcher.action.sceneVisibility.type.toggle="切り替え"
; AdvSceneSwitcher.action.sceneVisibility.type.source="Source" ; AdvSceneSwitcher.action.sceneVisibility.type.source="Source"
; AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Any" ; AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Any"
; AdvSceneSwitcher.action.sceneVisibility.entry="On{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="フィルタ" AdvSceneSwitcher.action.filter="フィルタ"
AdvSceneSwitcher.action.filter.type.enable="有効にする" AdvSceneSwitcher.action.filter.type.enable="有効にする"
AdvSceneSwitcher.action.filter.type.disable="無効化" AdvSceneSwitcher.action.filter.type.disable="無効化"
@@ -937,7 +921,7 @@ AdvSceneSwitcher.action.macro.type.pause="一時停止"
AdvSceneSwitcher.action.macro.type.unpause="一時停止解除" AdvSceneSwitcher.action.macro.type.unpause="一時停止解除"
AdvSceneSwitcher.action.macro.type.togglePause="一時停止を切り替え" AdvSceneSwitcher.action.macro.type.togglePause="一時停止を切り替え"
AdvSceneSwitcher.action.macro.type.resetCounter="カウンターリセット" AdvSceneSwitcher.action.macro.type.resetCounter="カウンターリセット"
AdvSceneSwitcher.action.macro.type.run="マクロを実行" AdvSceneSwitcher.action.macro.type.runActions="マクロを実行"
AdvSceneSwitcher.action.macro.type.run.conditions.ignore="条件の状態を考慮しない" AdvSceneSwitcher.action.macro.type.run.conditions.ignore="条件の状態を考慮しない"
AdvSceneSwitcher.action.macro.type.run.conditions.true="条件が true と評価された場合のみ" AdvSceneSwitcher.action.macro.type.run.conditions.true="条件が true と評価された場合のみ"
AdvSceneSwitcher.action.macro.type.run.conditions.false="条件が false と評価された場合のみ" AdvSceneSwitcher.action.macro.type.run.conditions.false="条件が false と評価された場合のみ"
@@ -1026,9 +1010,9 @@ AdvSceneSwitcher.action.transition="トランジション"
; AdvSceneSwitcher.action.transition.type.sceneOverride="シーントランジションオーバーライド" ; AdvSceneSwitcher.action.transition.type.sceneOverride="シーントランジションオーバーライド"
; AdvSceneSwitcher.action.transition.type.sourceShow="ソース表示トランジション" ; AdvSceneSwitcher.action.transition.type.sourceShow="ソース表示トランジション"
; AdvSceneSwitcher.action.transition.type.sourceHide="ソース非表示トランジション" ; AdvSceneSwitcher.action.transition.type.sourceHide="ソース非表示トランジション"
; AdvSceneSwitcher.action.transition.entry.line1="{{type}}{{scenes}}{{sources}} の変更" ; AdvSceneSwitcher.action.transition.layout.type="{{type}}{{scenes}}{{sources}} の変更"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}トランジションタイプを{{transitions}}に設定します" AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}トランジションタイプを{{transitions}}に設定します"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}移行時間を {{duration}} 秒に設定します" AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}移行時間を {{duration}} 秒に設定します"
AdvSceneSwitcher.action.timer="タイマー" AdvSceneSwitcher.action.timer="タイマー"
AdvSceneSwitcher.action.timer.type.pause="一時停止" AdvSceneSwitcher.action.timer.type.pause="一時停止"
AdvSceneSwitcher.action.timer.type.continue="続行" AdvSceneSwitcher.action.timer.type.continue="続行"
@@ -1150,8 +1134,8 @@ AdvSceneSwitcher.action.variable.currentSegmentValue="現在値:"
; AdvSceneSwitcher.action.variable.layout.other="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}{{tempVarsHelp}}{{sceneItemIndex}}{{direction}}{{stringLength}}{{paddingCharSelection}}" ; AdvSceneSwitcher.action.variable.layout.other="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}{{tempVarsHelp}}{{sceneItemIndex}}{{direction}}{{stringLength}}{{paddingCharSelection}}"
AdvSceneSwitcher.action.variable.layout.pad="{{actions}}の{{variables}}の長さを{{stringLength}}にするには、{{paddingCharSelection}}を{{direction}}に追加します。" AdvSceneSwitcher.action.variable.layout.pad="{{actions}}の{{variables}}の長さを{{stringLength}}にするには、{{paddingCharSelection}}を{{direction}}に追加します。"
AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}の{{variables}}の長さを{{stringLength}}にするには、{{direction}}から文字を削除します。" AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}の{{variables}}の長さを{{stringLength}}にするには、{{direction}}から文字を削除します。"
AdvSceneSwitcher.action.variable.layout.substringIndex="部分文字列の開始:{{subStringStart}}部分文字列のサイズ:{{subStringSize}}" AdvSceneSwitcher.action.variable.layout.substringIndex="部分文字列の開始:{{subStringStart}}部分文字列のサイズ:{{subStringSize}}{{subStringRegex}}"
AdvSceneSwitcher.action.variable.layout.substringRegex="正規表現を使用して{{regexMatchIdx}}一致の値を割り当てます:" AdvSceneSwitcher.action.variable.layout.substringRegex="正規表現を使用して{{regexMatchIdx}}一致の値を割り当てます:{{subStringRegex}}"
; AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}" ; AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}"
AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}カスタム プロンプトを使用する{{inputPrompt}}" AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}カスタム プロンプトを使用する{{inputPrompt}}"
AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}プレースホルダーを入力{{inputPlaceholder}}" AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}プレースホルダーを入力{{inputPlaceholder}}"
@@ -1194,10 +1178,17 @@ AdvSceneSwitcher.action.twitch.type.user.getInfo="ユーザー情報を取得"
AdvSceneSwitcher.action.twitch.type.reward.getInfo="チャンネルポイントリワード情報を取得" AdvSceneSwitcher.action.twitch.type.reward.getInfo="チャンネルポイントリワード情報を取得"
AdvSceneSwitcher.action.twitch.reward.toggleControl="リワード名/変数選択コントロールの切り替え" AdvSceneSwitcher.action.twitch.reward.toggleControl="リワード名/変数選択コントロールの切り替え"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Twitchアカウントを選択しないとカテゴリを選択できません" AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Twitchアカウントを選択しないとカテゴリを選択できません"
; AdvSceneSwitcher.action.twitch.entry.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}" ; AdvSceneSwitcher.action.twitch.layout.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}"
AdvSceneSwitcher.action.twitch.entry.chat="{{channel}}でアカウント{{account}}{{actions}}を使用しています" AdvSceneSwitcher.action.twitch.layout.chat="{{channel}}でアカウント{{account}}{{actions}}を使用しています"
AdvSceneSwitcher.action.twitch.entry.user.getInfo="{{userInfoQueryType}}{{userLogin}}{{userId}}でアカウント{{account}}{{actions}}を使用" AdvSceneSwitcher.action.twitch.layout.channel.getInfo="チャンネル{{channel}}でアカウント{{account}}{{actions}}を使用"
AdvSceneSwitcher.action.twitch.entry.reward.getInfo="チャンネル{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}でアカウント{{account}}{{actions}}を使用" AdvSceneSwitcher.action.twitch.layout.user.getInfo.row1="アカウント{{account}}{{actions}}を使用"
AdvSceneSwitcher.action.twitch.layout.user.getInfo.row2="{{userInfoQueryType}}{{userLogin}}{{userId}}"
AdvSceneSwitcher.action.twitch.layout.user.moderation.row1="アカウント{{account}}{{actions}}を使用"
AdvSceneSwitcher.action.twitch.layout.user.moderation.row2="チャンネル{{channel}}{{userInfoQueryType}}{{userLogin}}{{userId}}"
AdvSceneSwitcher.action.twitch.layout.user.ban.row1="アカウント{{account}}{{actions}}を使用 タイムアウト{{duration}}"
AdvSceneSwitcher.action.twitch.layout.user.ban.row2="チャンネル{{channel}}{{userInfoQueryType}}{{userLogin}}{{userId}} 理由{{banReason}}"
AdvSceneSwitcher.action.twitch.layout.reward.getInfo.row1="チャンネル{{channel}}でアカウント{{account}}{{actions}}を使用"
AdvSceneSwitcher.action.twitch.layout.reward.getInfo.row2="{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
AdvSceneSwitcher.action.twitch.title.title="タイトルを入力" AdvSceneSwitcher.action.twitch.title.title="タイトルを入力"
AdvSceneSwitcher.action.twitch.marker.description="マーカーの説明" AdvSceneSwitcher.action.twitch.marker.description="マーカーの説明"
AdvSceneSwitcher.action.twitch.clip.hasDelay="クリップをキャプチャする前にわずかな遅延を追加します" AdvSceneSwitcher.action.twitch.clip.hasDelay="クリップをキャプチャする前にわずかな遅延を追加します"
@@ -2257,8 +2248,6 @@ AdvSceneSwitcher.status.inactive="停止中"
AdvSceneSwitcher.running="プラグイン実行中" AdvSceneSwitcher.running="プラグイン実行中"
AdvSceneSwitcher.stopped="プラグイン停止しました" AdvSceneSwitcher.stopped="プラグイン停止しました"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>高機能シーンスイッチャーが初めて起動したようです。<br>ガイドと使用例については <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> をご覧ください。<br>質問は <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">OBSフォーラムのプラグインのスレッド</span></a> で遠慮なくどうぞ!</p></body></html>"
AdvSceneSwitcher.deprecatedTabWarning="このタブの開発は停止しました!\n代わりにマクロの使用に移行することを検討してください。\nこのヒントは [全般] タブで無効にすることができます。" AdvSceneSwitcher.deprecatedTabWarning="このタブの開発は停止しました!\n代わりにマクロの使用に移行することを検討してください。\nこのヒントは [全般] タブで無効にすることができます。"
AdvSceneSwitcher.unit.milliseconds="ミリ秒" AdvSceneSwitcher.unit.milliseconds="ミリ秒"

View File

@@ -147,7 +147,6 @@ AdvSceneSwitcher.macroTab.run.tooltip="Execute todas as ações da macro indepen
AdvSceneSwitcher.macroTab.runElse="Executar macro (alternativa)" AdvSceneSwitcher.macroTab.runElse="Executar macro (alternativa)"
AdvSceneSwitcher.macroTab.runFail="Falha ao executar \"%1\"!\nUma das ações falhou ou a macro já está em execução.\nDeseja interrompê-la?" AdvSceneSwitcher.macroTab.runFail="Falha ao executar \"%1\"!\nUma das ações falhou ou a macro já está em execução.\nDeseja interrompê-la?"
AdvSceneSwitcher.macroTab.runInParallel="Executar macro em paralelo com outras macros" AdvSceneSwitcher.macroTab.runInParallel="Executar macro em paralelo com outras macros"
AdvSceneSwitcher.macroTab.onChange="Executar ações apenas quando houver mudança na condição"
AdvSceneSwitcher.macroTab.defaultname="Macro %1" AdvSceneSwitcher.macroTab.defaultname="Macro %1"
AdvSceneSwitcher.macroTab.defaultGroupName="Grupo %1" AdvSceneSwitcher.macroTab.defaultGroupName="Grupo %1"
AdvSceneSwitcher.macroTab.macroNameExists="O nome \"%1\" já está em uso por uma macro." AdvSceneSwitcher.macroTab.macroNameExists="O nome \"%1\" já está em uso por uma macro."
@@ -240,7 +239,6 @@ AdvSceneSwitcher.macroList.deleted="excluída"
AdvSceneSwitcher.macroList.duplicate="\"%1\" já está selecionada!" AdvSceneSwitcher.macroList.duplicate="\"%1\" já está selecionada!"
; Macro Logic ; Macro Logic
AdvSceneSwitcher.logic.none="Ignorar entrada"
AdvSceneSwitcher.logic.and="E" AdvSceneSwitcher.logic.and="E"
AdvSceneSwitcher.logic.or="Ou" AdvSceneSwitcher.logic.or="Ou"
AdvSceneSwitcher.logic.andNot="E não" AdvSceneSwitcher.logic.andNot="E não"
@@ -296,11 +294,6 @@ AdvSceneSwitcher.condition.file="Arquivo"
AdvSceneSwitcher.condition.file.type.match="corresponde" AdvSceneSwitcher.condition.file.type.match="corresponde"
AdvSceneSwitcher.condition.file.type.contentChange="conteúdo mudou" AdvSceneSwitcher.condition.file.type.contentChange="conteúdo mudou"
AdvSceneSwitcher.condition.file.type.dateChange="data de modificação mudou" AdvSceneSwitcher.condition.file.type.dateChange="data de modificação mudou"
AdvSceneSwitcher.condition.file.remote="Arquivo remoto"
AdvSceneSwitcher.condition.file.local="Arquivo local"
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
AdvSceneSwitcher.condition.media="Mídia" AdvSceneSwitcher.condition.media="Mídia"
AdvSceneSwitcher.condition.media.checkType.state="Estado corresponde" AdvSceneSwitcher.condition.media.checkType.state="Estado corresponde"
AdvSceneSwitcher.condition.media.checkType.time="Restrição de tempo corresponde" AdvSceneSwitcher.condition.media.checkType.time="Restrição de tempo corresponde"
@@ -378,10 +371,10 @@ AdvSceneSwitcher.condition.video.layout.minNeighbor="Vizinhos mínimos:{{minNeig
AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Reduzir carga de CPU realizando a verificação apenas a cada {{throttleCount}} milissegundos" AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Reduzir carga de CPU realizando a verificação apenas a cada {{throttleCount}} milissegundos"
AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Realizar verificação apenas na área" AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Realizar verificação apenas na área"
AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
AdvSceneSwitcher.condition.video.layout.ocrColorPick="Verificar cor do texto:{{textColor}}{{selectColor}}" AdvSceneSwitcher.condition.video.layout.ocrColorPick="Verificar cor do texto:{{color}}"
AdvSceneSwitcher.condition.video.layout.ocrTextType="Verificar tipo de texto:{{textType}}" AdvSceneSwitcher.condition.video.layout.ocrTextType="Verificar tipo de texto:{{textType}}"
AdvSceneSwitcher.condition.video.layout.ocrLanguage="Verificar idioma:{{languageCode}}" AdvSceneSwitcher.condition.video.layout.ocrLanguage="Verificar idioma:{{languageCode}}"
AdvSceneSwitcher.condition.video.layout.color="Verificar cor:{{color}}{{selectColor}}" AdvSceneSwitcher.condition.video.layout.color="Verificar cor:{{color}}"
AdvSceneSwitcher.condition.video.minSize="Tamanho mínimo:" AdvSceneSwitcher.condition.video.minSize="Tamanho mínimo:"
AdvSceneSwitcher.condition.video.maxSize="Tamanho máximo:" AdvSceneSwitcher.condition.video.maxSize="Tamanho máximo:"
AdvSceneSwitcher.condition.video.selectArea="Selecionar área" AdvSceneSwitcher.condition.video.selectArea="Selecionar área"
@@ -401,8 +394,8 @@ AdvSceneSwitcher.condition.record.state.stop="Gravação parada"
AdvSceneSwitcher.condition.record.state.duration="Duração da gravação é maior que" AdvSceneSwitcher.condition.record.state.duration="Duração da gravação é maior que"
AdvSceneSwitcher.condition.record.entry="{{condition}}{{duration}}" AdvSceneSwitcher.condition.record.entry="{{condition}}{{duration}}"
AdvSceneSwitcher.condition.process="Processo" AdvSceneSwitcher.condition.process="Processo"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}está em execução{{focused}}e está em foco" AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}está em execução{{focused}}e está em foco"
AdvSceneSwitcher.condition.process.entry.focus="Processo atual em primeiro plano:{{focusProcess}}" AdvSceneSwitcher.condition.process.layout.focus="Processo atual em primeiro plano:{{focusProcess}}"
AdvSceneSwitcher.condition.idle="Inativo" AdvSceneSwitcher.condition.idle="Inativo"
AdvSceneSwitcher.condition.idle.entry="Sem entradas de teclado ou mouse por {{duration}}" AdvSceneSwitcher.condition.idle.entry="Sem entradas de teclado ou mouse por {{duration}}"
AdvSceneSwitcher.condition.pluginState="Estado do plugin" AdvSceneSwitcher.condition.pluginState="Estado do plugin"
@@ -502,14 +495,14 @@ AdvSceneSwitcher.condition.replay.state.started="Buffer de replay iniciado"
AdvSceneSwitcher.condition.replay.state.saved="Buffer de replay salvo" AdvSceneSwitcher.condition.replay.state.saved="Buffer de replay salvo"
AdvSceneSwitcher.condition.replay.entry="{{state}}" AdvSceneSwitcher.condition.replay.entry="{{state}}"
AdvSceneSwitcher.condition.date="Data" AdvSceneSwitcher.condition.date="Data"
AdvSceneSwitcher.condition.date.anyDay="Qualquer dia" AdvSceneSwitcher.day.any="Qualquer dia"
AdvSceneSwitcher.condition.date.monday="Segunda-feira" AdvSceneSwitcher.day.monday="Segunda-feira"
AdvSceneSwitcher.condition.date.tuesday="Terça-feira" AdvSceneSwitcher.day.tuesday="Terça-feira"
AdvSceneSwitcher.condition.date.wednesday="Quarta-feira" AdvSceneSwitcher.day.wednesday="Quarta-feira"
AdvSceneSwitcher.condition.date.thursday="Quinta-feira" AdvSceneSwitcher.day.thursday="Quinta-feira"
AdvSceneSwitcher.condition.date.friday="Sexta-feira" AdvSceneSwitcher.day.friday="Sexta-feira"
AdvSceneSwitcher.condition.date.saturday="Sábado" AdvSceneSwitcher.day.saturday="Sábado"
AdvSceneSwitcher.condition.date.sunday="Domingo" AdvSceneSwitcher.day.sunday="Domingo"
AdvSceneSwitcher.condition.date.state.at="Às" AdvSceneSwitcher.condition.date.state.at="Às"
AdvSceneSwitcher.condition.date.state.after="Após" AdvSceneSwitcher.condition.date.state.after="Após"
AdvSceneSwitcher.condition.date.state.before="Antes" AdvSceneSwitcher.condition.date.state.before="Antes"
@@ -520,12 +513,13 @@ AdvSceneSwitcher.condition.date.ignoreDate="Se desmarcado, o componente de data
AdvSceneSwitcher.condition.date.ignoreTime="Se desmarcado, o componente de tempo será ignorado" AdvSceneSwitcher.condition.date.ignoreTime="Se desmarcado, o componente de tempo será ignorado"
AdvSceneSwitcher.condition.date.showAdvancedSettings="Mostrar configurações avançadas" AdvSceneSwitcher.condition.date.showAdvancedSettings="Mostrar configurações avançadas"
AdvSceneSwitcher.condition.date.showSimpleSettings="Mostrar configurações simples" AdvSceneSwitcher.condition.date.showSimpleSettings="Mostrar configurações simples"
AdvSceneSwitcher.condition.date.entry.simple="Em{{dayOfWeek}}{{weekCondition}}{{ignoreWeekTime}}{{weekTime}}" AdvSceneSwitcher.condition.date.layout.simple.day="Em{{dayOfWeek}}"
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}}{{ignoreDate}}{{date}}{{ignoreTime}}{{time}}{{separator}}{{date2}}{{time2}}" AdvSceneSwitcher.condition.date.layout.simple.time="{{ignoreWeekTime}}{{weekCondition}}{{weekTime}}"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}}Repetir a cada{{duration}}em correspondência de data" AdvSceneSwitcher.condition.date.layout.advanced="{{condition}}{{ignoreDate}}{{date}}{{ignoreTime}}{{time}}{{separator}}{{date2}}{{time2}}"
AdvSceneSwitcher.condition.date.entry.pattern="Data atual \"{{currentDate}}\" corresponde ao padrão{{pattern}}" AdvSceneSwitcher.condition.date.layout.repeat="{{repeat}}Repetir a cada{{duration}}em correspondência de data"
AdvSceneSwitcher.condition.date.entry.nextMatchDate="Próxima correspondência em: %1" AdvSceneSwitcher.condition.date.layout.pattern="Data atual \"{{currentDate}}\" corresponde ao padrão{{pattern}}"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}}Ao repetir, atualize a data selecionada para a data de repetição" AdvSceneSwitcher.condition.date.layout.nextMatchDate="Próxima correspondência em: %1"
AdvSceneSwitcher.condition.date.layout.updateOnRepeat="{{updateOnRepeat}}Ao repetir, atualize a data selecionada para a data de repetição"
AdvSceneSwitcher.condition.sceneTransform="Transformação do item de cena" AdvSceneSwitcher.condition.sceneTransform="Transformação do item de cena"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Obter transformação" AdvSceneSwitcher.condition.sceneTransform.getTransform="Obter transformação"
AdvSceneSwitcher.condition.sceneTransform.getCurrentValue="Obter valor atual" AdvSceneSwitcher.condition.sceneTransform.getCurrentValue="Obter valor atual"
@@ -724,8 +718,8 @@ AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Aguardar até que a tran
AdvSceneSwitcher.action.wait="Aguardar" AdvSceneSwitcher.action.wait="Aguardar"
AdvSceneSwitcher.action.wait.type.fixed="fixo" AdvSceneSwitcher.action.wait.type.fixed="fixo"
AdvSceneSwitcher.action.wait.type.random="aleatório" AdvSceneSwitcher.action.wait.type.random="aleatório"
AdvSceneSwitcher.action.wait.entry.fixed="Aguardando por{{waitType}}duração de{{duration}}" AdvSceneSwitcher.action.wait.layout.fixed="Aguardando por{{waitType}}duração de{{duration}}"
AdvSceneSwitcher.action.wait.entry.random="Aguardando por{{waitType}}duração de{{duration}}para{{duration2}}" AdvSceneSwitcher.action.wait.layout.random="Aguardando por{{waitType}}duração de{{duration}}para{{duration2}}"
AdvSceneSwitcher.action.audio="Áudio" AdvSceneSwitcher.action.audio="Áudio"
AdvSceneSwitcher.action.audio.type.mute="Silenciar" AdvSceneSwitcher.action.audio.type.mute="Silenciar"
AdvSceneSwitcher.action.audio.type.unmute="Ativar som" AdvSceneSwitcher.action.audio.type.unmute="Ativar som"
@@ -754,7 +748,6 @@ AdvSceneSwitcher.action.recording.type.changeOutputFolder="Alterar pasta de saí
AdvSceneSwitcher.action.recording.type.changeOutputFileFormat="Alterar formatação do nome do arquivo" AdvSceneSwitcher.action.recording.type.changeOutputFileFormat="Alterar formatação do nome do arquivo"
AdvSceneSwitcher.action.recording.pause.hint="Observe que dependendo das suas configurações de gravação, você pode não conseguir pausar a gravação" AdvSceneSwitcher.action.recording.pause.hint="Observe que dependendo das suas configurações de gravação, você pode não conseguir pausar a gravação"
AdvSceneSwitcher.action.recording.split.hint="Certifique-se de habilitar a divisão automática de arquivos nas configurações do OBS primeiro!" AdvSceneSwitcher.action.recording.split.hint="Certifique-se de habilitar a divisão automática de arquivos nas configurações do OBS primeiro!"
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
AdvSceneSwitcher.action.replay="Buffer de replay" AdvSceneSwitcher.action.replay="Buffer de replay"
AdvSceneSwitcher.action.replay.saveWarn="Aviso: Salvar com muita frequência pode resultar em o buffer de replay não ser realmente salvo!" AdvSceneSwitcher.action.replay.saveWarn="Aviso: Salvar com muita frequência pode resultar em o buffer de replay não ser realmente salvo!"
AdvSceneSwitcher.action.replay.durationWarn="Aviso: Alterar o tempo máximo de replay só se aplicará na próxima vez que o buffer de replay for iniciado!" AdvSceneSwitcher.action.replay.durationWarn="Aviso: Alterar o tempo máximo de replay só se aplicará na próxima vez que o buffer de replay for iniciado!"
@@ -771,7 +764,6 @@ AdvSceneSwitcher.action.streaming.type.server="Definir URL do servidor"
AdvSceneSwitcher.action.streaming.type.streamKey="Definir chave de transmissão" AdvSceneSwitcher.action.streaming.type.streamKey="Definir chave de transmissão"
AdvSceneSwitcher.action.streaming.type.username="Definir nome de usuário" AdvSceneSwitcher.action.streaming.type.username="Definir nome de usuário"
AdvSceneSwitcher.action.streaming.type.password="Definir senha" AdvSceneSwitcher.action.streaming.type.password="Definir senha"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="Executar" AdvSceneSwitcher.action.run="Executar"
AdvSceneSwitcher.action.run.wait.entry="{{wait}}Aguardar a saída do processo ou no máximo {{timeout}}{{waitHelp}}" AdvSceneSwitcher.action.run.wait.entry="{{wait}}Aguardar a saída do processo ou no máximo {{timeout}}{{waitHelp}}"
AdvSceneSwitcher.action.run.wait.help.tooltip="Observe que as propriedades da macro não funcionarão se você deixar isso desmarcado, pois o processo é iniciado de forma independente do restante da lógica e não há controle sobre ele." AdvSceneSwitcher.action.run.wait.help.tooltip="Observe que as propriedades da macro não funcionarão se você deixar isso desmarcado, pois o processo é iniciado de forma independente do restante da lógica e não há controle sobre ele."
@@ -781,7 +773,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="Ocultar"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="Alternar" AdvSceneSwitcher.action.sceneVisibility.type.toggle="Alternar"
AdvSceneSwitcher.action.sceneVisibility.type.source="Fonte" AdvSceneSwitcher.action.sceneVisibility.type.source="Fonte"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Qualquer" AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Qualquer"
AdvSceneSwitcher.action.sceneVisibility.entry="Em{{scenes}}{{actions}}{{sources}}" AdvSceneSwitcher.action.sceneVisibility.layout="Em{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="Filtro" AdvSceneSwitcher.action.filter="Filtro"
AdvSceneSwitcher.action.filter.type.enable="Habilitar" AdvSceneSwitcher.action.filter.type.enable="Habilitar"
AdvSceneSwitcher.action.filter.type.disable="Desabilitar" AdvSceneSwitcher.action.filter.type.disable="Desabilitar"
@@ -846,7 +838,7 @@ AdvSceneSwitcher.action.macro="Macro"
AdvSceneSwitcher.action.macro.type.pause="Pausar" AdvSceneSwitcher.action.macro.type.pause="Pausar"
AdvSceneSwitcher.action.macro.type.unpause="Retomar" AdvSceneSwitcher.action.macro.type.unpause="Retomar"
AdvSceneSwitcher.action.macro.type.resetCounter="Reiniciar contador" AdvSceneSwitcher.action.macro.type.resetCounter="Reiniciar contador"
AdvSceneSwitcher.action.macro.type.run="Executar macro" AdvSceneSwitcher.action.macro.type.runActions="Executar macro"
AdvSceneSwitcher.action.macro.type.run.conditions.ignore="Não considerar o estado da condição" AdvSceneSwitcher.action.macro.type.run.conditions.ignore="Não considerar o estado da condição"
AdvSceneSwitcher.action.macro.type.run.conditions.true="Apenas se as condições forem verdadeiras" AdvSceneSwitcher.action.macro.type.run.conditions.true="Apenas se as condições forem verdadeiras"
AdvSceneSwitcher.action.macro.type.run.conditions.false="Apenas se as condições forem falsas" AdvSceneSwitcher.action.macro.type.run.conditions.false="Apenas se as condições forem falsas"
@@ -861,9 +853,9 @@ AdvSceneSwitcher.action.macro.type.stop="Parar ações"
AdvSceneSwitcher.action.macro.type.disableAction="Desabilitar ação" AdvSceneSwitcher.action.macro.type.disableAction="Desabilitar ação"
AdvSceneSwitcher.action.macro.type.enableAction="Habilitar ação" AdvSceneSwitcher.action.macro.type.enableAction="Habilitar ação"
AdvSceneSwitcher.action.macro.type.toggleAction="Alternar ação" AdvSceneSwitcher.action.macro.type.toggleAction="Alternar ação"
AdvSceneSwitcher.action.macro.entry.run="{{actions}}{{actionTypes}}de{{macros}}" AdvSceneSwitcher.action.macro.layout.run="{{actions}}{{actionSections}}de{{macros}}"
AdvSceneSwitcher.action.macro.entry.run.condition="{{conditionBehaviors}}de{{conditionMacros}}" AdvSceneSwitcher.action.macro.layout.run.condition="{{conditionBehaviors}}de{{conditionMacros}}"
AdvSceneSwitcher.action.macro.entry.other="{{actions}}{{actionIndex}}{{macros}}" AdvSceneSwitcher.action.macro.layout.other="{{actions}}{{macros}}"
AdvSceneSwitcher.action.pluginState="Estado do plugin" AdvSceneSwitcher.action.pluginState="Estado do plugin"
AdvSceneSwitcher.action.pluginState.type.stop="Parar o plugin Advanced Scene Switcher" AdvSceneSwitcher.action.pluginState.type.stop="Parar o plugin Advanced Scene Switcher"
AdvSceneSwitcher.action.pluginState.type.noMatch="Alterar o comportamento em caso de não correspondência:" AdvSceneSwitcher.action.pluginState.type.noMatch="Alterar o comportamento em caso de não correspondência:"
@@ -933,9 +925,9 @@ AdvSceneSwitcher.action.transition.type.scene="transição de cena"
AdvSceneSwitcher.action.transition.type.sceneOverride="substituição de transição de cena" AdvSceneSwitcher.action.transition.type.sceneOverride="substituição de transição de cena"
AdvSceneSwitcher.action.transition.type.sourceShow="transição de exibição de fonte" AdvSceneSwitcher.action.transition.type.sourceShow="transição de exibição de fonte"
AdvSceneSwitcher.action.transition.type.sourceHide="transição de ocultação de fonte" AdvSceneSwitcher.action.transition.type.sourceHide="transição de ocultação de fonte"
AdvSceneSwitcher.action.transition.entry.line1="Modificar{{type}}{{scenes}}{{sources}}" AdvSceneSwitcher.action.transition.layout.type="Modificar{{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Definir tipo de transição para{{transitions}}" AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Definir tipo de transição para{{transitions}}"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Definir duração da transição para{{duration}}segundos" AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Definir duração da transição para{{duration}}segundos"
AdvSceneSwitcher.action.timer="Temporizador" AdvSceneSwitcher.action.timer="Temporizador"
AdvSceneSwitcher.action.timer.type.pause="Pausar" AdvSceneSwitcher.action.timer.type.pause="Pausar"
AdvSceneSwitcher.action.timer.type.continue="Continuar" AdvSceneSwitcher.action.timer.type.continue="Continuar"
@@ -1030,8 +1022,8 @@ AdvSceneSwitcher.action.variable.conditionNoVariableSupport="Obter valores de va
AdvSceneSwitcher.action.variable.currentSegmentValue="Valor atual:" AdvSceneSwitcher.action.variable.currentSegmentValue="Valor atual:"
AdvSceneSwitcher.action.variable.layout.pad="{{actions}}de{{variables}}para comprimento{{stringLength}}adicionando{{paddingCharSelection}}ao{{direction}}" AdvSceneSwitcher.action.variable.layout.pad="{{actions}}de{{variables}}para comprimento{{stringLength}}adicionando{{paddingCharSelection}}ao{{direction}}"
AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}de{{variables}}para comprimento{{stringLength}}removendo caracteres da{{direction}}" AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}de{{variables}}para comprimento{{stringLength}}removendo caracteres da{{direction}}"
AdvSceneSwitcher.action.variable.layout.substringIndex="Início da substring:{{subStringStart}}Tamanho da substring:{{subStringSize}}" AdvSceneSwitcher.action.variable.layout.substringIndex="Início da substring:{{subStringStart}}Tamanho da substring:{{subStringSize}}{{subStringRegex}}"
AdvSceneSwitcher.action.variable.layout.substringRegex="Atribuir valor do{{regexMatchIdx}}correspondência usando expressão regular:" AdvSceneSwitcher.action.variable.layout.substringRegex="Atribuir valor do{{regexMatchIdx}}correspondência usando expressão regular:{{subStringRegex}}"
AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}" AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}"
AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}Usar prompt personalizado{{inputPrompt}}" AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}Usar prompt personalizado{{inputPrompt}}"
AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}Preencher com placeholder{{inputPlaceholder}}" AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}Preencher com placeholder{{inputPlaceholder}}"
@@ -1065,8 +1057,8 @@ AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable="Habilitar modo apenas
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="Desabilitar modo apenas emotes no chat" AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="Desabilitar modo apenas emotes no chat"
AdvSceneSwitcher.action.twitch.type.chat.sendMessage="Enviar mensagem de chat" AdvSceneSwitcher.action.twitch.type.chat.sendMessage="Enviar mensagem de chat"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Não é possível selecionar categoria sem selecionar uma conta Twitch primeiro!" AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Não é possível selecionar categoria sem selecionar uma conta Twitch primeiro!"
AdvSceneSwitcher.action.twitch.entry.default="Em{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}" AdvSceneSwitcher.action.twitch.layout.default="Em{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}{{nonModDelayDuration}}"
AdvSceneSwitcher.action.twitch.entry.chat="Usando conta{{account}}{{actions}}em{{channel}}" AdvSceneSwitcher.action.twitch.layout.chat="Usando conta{{account}}{{actions}}em{{channel}}"
AdvSceneSwitcher.action.twitch.title.title="Digite o título" AdvSceneSwitcher.action.twitch.title.title="Digite o título"
AdvSceneSwitcher.action.twitch.marker.description="Descreva o marcador" AdvSceneSwitcher.action.twitch.marker.description="Descreva o marcador"
AdvSceneSwitcher.action.twitch.clip.hasDelay="Adicionar um leve atraso antes de capturar o clipe" AdvSceneSwitcher.action.twitch.clip.hasDelay="Adicionar um leve atraso antes de capturar o clipe"
@@ -1871,8 +1863,6 @@ AdvSceneSwitcher.status.inactive="Inativo"
AdvSceneSwitcher.running="Plugin em execução" AdvSceneSwitcher.running="Plugin em execução"
AdvSceneSwitcher.stopped="Plugin parado" AdvSceneSwitcher.stopped="Plugin parado"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Parece que esta é a primeira vez que o Advanced Scene Switcher está sendo iniciado.<br>Por favor, consulte o <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> para uma lista de guias e exemplos.<br>Não hesite em fazer perguntas no <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">tópico</span></a> do plugin nos fóruns do OBS!</p></body></html>"
AdvSceneSwitcher.deprecatedTabWarning="Desenvolvimento para esta aba interrompido!\nPor favor, considere a transição para o uso de Macros em vez disso.\nEsta dica pode ser desativada na aba Geral." AdvSceneSwitcher.deprecatedTabWarning="Desenvolvimento para esta aba interrompido!\nPor favor, considere a transição para o uso de Macros em vez disso.\nEsta dica pode ser desativada na aba Geral."
AdvSceneSwitcher.unit.milliseconds="milissegundos" AdvSceneSwitcher.unit.milliseconds="milissegundos"

View File

@@ -70,7 +70,6 @@ AdvSceneSwitcher.macroTab.name="Имя:"
AdvSceneSwitcher.macroTab.defaultname="Макрос %1" AdvSceneSwitcher.macroTab.defaultname="Макрос %1"
AdvSceneSwitcher.macroTab.copy="Создать копию" AdvSceneSwitcher.macroTab.copy="Создать копию"
; Macro Logic ; Macro Logic
AdvSceneSwitcher.logic.none="Игнорировать вход"
AdvSceneSwitcher.logic.and="И" AdvSceneSwitcher.logic.and="И"
AdvSceneSwitcher.logic.or="Или" AdvSceneSwitcher.logic.or="Или"
AdvSceneSwitcher.logic.andNot="И не" AdvSceneSwitcher.logic.andNot="И не"
@@ -85,8 +84,6 @@ AdvSceneSwitcher.condition.scene.type.current="Текущий"
AdvSceneSwitcher.condition.scene.type.previous="Предыдущий" AdvSceneSwitcher.condition.scene.type.previous="Предыдущий"
AdvSceneSwitcher.condition.window="Окно" AdvSceneSwitcher.condition.window="Окно"
AdvSceneSwitcher.condition.file="Файл" AdvSceneSwitcher.condition.file="Файл"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
AdvSceneSwitcher.condition.media="Медиа" AdvSceneSwitcher.condition.media="Медиа"
AdvSceneSwitcher.condition.video="Видео" AdvSceneSwitcher.condition.video="Видео"
AdvSceneSwitcher.condition.video.condition.match="точно соответствует" AdvSceneSwitcher.condition.video.condition.match="точно соответствует"
@@ -105,7 +102,7 @@ AdvSceneSwitcher.condition.record.state.start="Запись запущена"
AdvSceneSwitcher.condition.record.state.pause="Запись приостановлена" AdvSceneSwitcher.condition.record.state.pause="Запись приостановлена"
AdvSceneSwitcher.condition.record.state.stop="Запись остановлена" AdvSceneSwitcher.condition.record.state.stop="Запись остановлена"
AdvSceneSwitcher.condition.process="Процесс" AdvSceneSwitcher.condition.process="Процесс"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}запущен{{focused}}и сфокусирован" AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}запущен{{focused}}и сфокусирован"
AdvSceneSwitcher.condition.idle="Простой" AdvSceneSwitcher.condition.idle="Простой"
AdvSceneSwitcher.condition.idle.entry="Не было ни клавиатуры, ни мыши в течении{{duration}}" AdvSceneSwitcher.condition.idle.entry="Не было ни клавиатуры, ни мыши в течении{{duration}}"
AdvSceneSwitcher.condition.pluginState="Состояние плагина" AdvSceneSwitcher.condition.pluginState="Состояние плагина"
@@ -117,8 +114,8 @@ AdvSceneSwitcher.action.scene="Переключить сцену"
AdvSceneSwitcher.action.wait="Подождать" AdvSceneSwitcher.action.wait="Подождать"
AdvSceneSwitcher.action.wait.type.fixed="фиксированный" AdvSceneSwitcher.action.wait.type.fixed="фиксированный"
AdvSceneSwitcher.action.wait.type.random="случайный" AdvSceneSwitcher.action.wait.type.random="случайный"
AdvSceneSwitcher.action.wait.entry.fixed="Ожидать {{waitType}} в течении {{duration}}" AdvSceneSwitcher.action.wait.layout.fixed="Ожидать {{waitType}} в течении {{duration}}"
AdvSceneSwitcher.action.wait.entry.random="Ожидание {{waitType}} длительностью от {{duration}} до {{duration2}}" AdvSceneSwitcher.action.wait.layout.random="Ожидание {{waitType}} длительностью от {{duration}} до {{duration2}}"
AdvSceneSwitcher.action.audio="Аудио" AdvSceneSwitcher.action.audio="Аудио"
AdvSceneSwitcher.action.audio.type.mute="Замьютить" AdvSceneSwitcher.action.audio.type.mute="Замьютить"
AdvSceneSwitcher.action.audio.type.unmute="Размьютить" AdvSceneSwitcher.action.audio.type.unmute="Размьютить"
@@ -130,7 +127,6 @@ AdvSceneSwitcher.action.recording.type.start="Начать запись"
AdvSceneSwitcher.action.recording.type.pause="Пауза записи" AdvSceneSwitcher.action.recording.type.pause="Пауза записи"
AdvSceneSwitcher.action.recording.type.unpause="Снять запись с паузы" AdvSceneSwitcher.action.recording.type.unpause="Снять запись с паузы"
AdvSceneSwitcher.action.recording.pause.hint="Обратите внимание, что в зависимости от настроек записи вы можете не иметь возможности приостановить запись" AdvSceneSwitcher.action.recording.pause.hint="Обратите внимание, что в зависимости от настроек записи вы можете не иметь возможности приостановить запись"
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
AdvSceneSwitcher.action.replay="Буфер воспроизведения" AdvSceneSwitcher.action.replay="Буфер воспроизведения"
AdvSceneSwitcher.action.replay.type.stop="Остановить буфер воспроизведения" AdvSceneSwitcher.action.replay.type.stop="Остановить буфер воспроизведения"
AdvSceneSwitcher.action.replay.type.start="Начать воспроизведение буфера" AdvSceneSwitcher.action.replay.type.start="Начать воспроизведение буфера"
@@ -138,7 +134,6 @@ AdvSceneSwitcher.action.replay.type.save="Сохранить буфер восп
AdvSceneSwitcher.action.streaming="Потоковое вещание" AdvSceneSwitcher.action.streaming="Потоковое вещание"
AdvSceneSwitcher.action.streaming.type.stop="Остановить потоковое вещание" AdvSceneSwitcher.action.streaming.type.stop="Остановить потоковое вещание"
AdvSceneSwitcher.action.streaming.type.start="Начать потоковое вещание" AdvSceneSwitcher.action.streaming.type.start="Начать потоковое вещание"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="Запустить" AdvSceneSwitcher.action.run="Запустить"

View File

@@ -72,14 +72,12 @@ AdvSceneSwitcher.macroTab.add="Yeni Makro ekle"
AdvSceneSwitcher.macroTab.name="İsim:" AdvSceneSwitcher.macroTab.name="İsim:"
AdvSceneSwitcher.macroTab.run="Makro Çalıştırma" AdvSceneSwitcher.macroTab.run="Makro Çalıştırma"
AdvSceneSwitcher.macroTab.runInParallel="Makroyu diğer makrolara paralel olarak çalıştırın" AdvSceneSwitcher.macroTab.runInParallel="Makroyu diğer makrolara paralel olarak çalıştırın"
AdvSceneSwitcher.macroTab.onChange="Eylemleri yalnızca koşul değişikliğinde gerçekleştirin"
AdvSceneSwitcher.macroTab.defaultname="Makro %1" AdvSceneSwitcher.macroTab.defaultname="Makro %1"
AdvSceneSwitcher.macroTab.copy="Kopya oluştur" AdvSceneSwitcher.macroTab.copy="Kopya oluştur"
AdvSceneSwitcher.macroTab.expandAll="Hepsini Genişlet" AdvSceneSwitcher.macroTab.expandAll="Hepsini Genişlet"
AdvSceneSwitcher.macroTab.collapseAll="Hepsini Küçült" AdvSceneSwitcher.macroTab.collapseAll="Hepsini Küçült"
; Macro Logic ; Macro Logic
AdvSceneSwitcher.logic.none="Girişi yoksay"
AdvSceneSwitcher.logic.and="Ve" AdvSceneSwitcher.logic.and="Ve"
AdvSceneSwitcher.logic.or="Ya da" AdvSceneSwitcher.logic.or="Ya da"
AdvSceneSwitcher.logic.andNot="ve değil" AdvSceneSwitcher.logic.andNot="ve değil"
@@ -108,9 +106,7 @@ 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.currentSceneTransitionBehaviour="Geçiş hedefi sahnesi için geçiş kontrolü sırasında"
AdvSceneSwitcher.condition.window="Pencere" AdvSceneSwitcher.condition.window="Pencere"
AdvSceneSwitcher.condition.file="Dosya" AdvSceneSwitcher.condition.file="Dosya"
AdvSceneSwitcher.condition.file.entry.line1="İçerik{{fileType}}{{filePath}}{{conditions}}{{useRegex}}" AdvSceneSwitcher.condition.file.layout="İçerik{{filePath}}{{conditions}}{{regex}}"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
AdvSceneSwitcher.condition.media="Medya" AdvSceneSwitcher.condition.media="Medya"
AdvSceneSwitcher.condition.media.anyOnScene="Herhangi bir medya kaynağı" AdvSceneSwitcher.condition.media.anyOnScene="Herhangi bir medya kaynağı"
AdvSceneSwitcher.condition.media.allOnScene="Tüm medya kaynakları " AdvSceneSwitcher.condition.media.allOnScene="Tüm medya kaynakları "
@@ -153,7 +149,7 @@ AdvSceneSwitcher.condition.record.state.start="Kayıt Çalışıyor"
AdvSceneSwitcher.condition.record.state.pause="Kayıt durakladı" AdvSceneSwitcher.condition.record.state.pause="Kayıt durakladı"
AdvSceneSwitcher.condition.record.state.stop="Kayıt durdu" AdvSceneSwitcher.condition.record.state.stop="Kayıt durdu"
AdvSceneSwitcher.condition.process="İşlem" AdvSceneSwitcher.condition.process="İşlem"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}çalışıyor{{focused}}ve odaklandı" AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}çalışıyor{{focused}}ve odaklandı"
AdvSceneSwitcher.condition.idle="Boşta" AdvSceneSwitcher.condition.idle="Boşta"
AdvSceneSwitcher.condition.idle.entry="...için klavye veya fare girişi yok {{duration}}" AdvSceneSwitcher.condition.idle.entry="...için klavye veya fare girişi yok {{duration}}"
AdvSceneSwitcher.condition.pluginState="Eklenti durumu" AdvSceneSwitcher.condition.pluginState="Eklenti durumu"
@@ -253,8 +249,8 @@ AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Hedef sahneye geçiş ta
AdvSceneSwitcher.action.wait="Bekle" AdvSceneSwitcher.action.wait="Bekle"
AdvSceneSwitcher.action.wait.type.fixed="sabit" AdvSceneSwitcher.action.wait.type.fixed="sabit"
AdvSceneSwitcher.action.wait.type.random="rastgele" AdvSceneSwitcher.action.wait.type.random="rastgele"
AdvSceneSwitcher.action.wait.entry.fixed="Şunu bekle {{waitType}} süre {{duration}}" AdvSceneSwitcher.action.wait.layout.fixed="Şunu bekle {{waitType}} süre {{duration}}"
AdvSceneSwitcher.action.wait.entry.random="Şunu bekle {{waitType}} bu aralıktan {{duration}} buna {{duration2}}" AdvSceneSwitcher.action.wait.layout.random="Şunu bekle {{waitType}} bu aralıktan {{duration}} buna {{duration2}}"
AdvSceneSwitcher.action.audio="Ses" AdvSceneSwitcher.action.audio="Ses"
AdvSceneSwitcher.action.audio.type.mute="Sessiz" AdvSceneSwitcher.action.audio.type.mute="Sessiz"
AdvSceneSwitcher.action.audio.type.unmute="Ses açmak" AdvSceneSwitcher.action.audio.type.unmute="Ses açmak"
@@ -266,7 +262,6 @@ AdvSceneSwitcher.action.recording.type.start="Kayıt Başlat"
AdvSceneSwitcher.action.recording.type.pause="Kayıt Duraklat" AdvSceneSwitcher.action.recording.type.pause="Kayıt Duraklat"
AdvSceneSwitcher.action.recording.type.unpause="Kayıt Duraklatma" AdvSceneSwitcher.action.recording.type.unpause="Kayıt Duraklatma"
AdvSceneSwitcher.action.recording.pause.hint="Kayıt ayarlarınıza bağlı olarak kaydı duraklatamayabileceğinizi unutmayın." AdvSceneSwitcher.action.recording.pause.hint="Kayıt ayarlarınıza bağlı olarak kaydı duraklatamayabileceğinizi unutmayın."
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
AdvSceneSwitcher.action.replay="Tekrar arabelleği" AdvSceneSwitcher.action.replay="Tekrar arabelleği"
AdvSceneSwitcher.action.replay.type.stop="Tekrar arabelleğini durdur" AdvSceneSwitcher.action.replay.type.stop="Tekrar arabelleğini durdur"
AdvSceneSwitcher.action.replay.type.start="Tekrar arabelleğini başlat" AdvSceneSwitcher.action.replay.type.start="Tekrar arabelleğini başlat"
@@ -274,14 +269,13 @@ AdvSceneSwitcher.action.replay.type.save="Tekrar arabelleğini kaydet"
AdvSceneSwitcher.action.streaming="Yayın" AdvSceneSwitcher.action.streaming="Yayın"
AdvSceneSwitcher.action.streaming.type.stop="Yayın durdur" AdvSceneSwitcher.action.streaming.type.stop="Yayın durdur"
AdvSceneSwitcher.action.streaming.type.start="Yayın başlat" AdvSceneSwitcher.action.streaming.type.start="Yayın başlat"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="Çalıştır" AdvSceneSwitcher.action.run="Çalıştır"
AdvSceneSwitcher.action.sceneVisibility="Sahne öğesi görünürlüğü" AdvSceneSwitcher.action.sceneVisibility="Sahne öğesi görünürlüğü"
AdvSceneSwitcher.action.sceneVisibility.type.show="Göster" AdvSceneSwitcher.action.sceneVisibility.type.show="Göster"
AdvSceneSwitcher.action.sceneVisibility.type.hide="Gizle" AdvSceneSwitcher.action.sceneVisibility.type.hide="Gizle"
AdvSceneSwitcher.action.sceneVisibility.type.source="Kayıt" AdvSceneSwitcher.action.sceneVisibility.type.source="Kayıt"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Herhangi" AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Herhangi"
AdvSceneSwitcher.action.sceneVisibility.entry="Açık{{scenes}}{{actions}}{{sources}}" AdvSceneSwitcher.action.sceneVisibility.layout="Açık{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="Filtrele" AdvSceneSwitcher.action.filter="Filtrele"
AdvSceneSwitcher.action.filter.type.enable="Etkin" AdvSceneSwitcher.action.filter.type.enable="Etkin"
AdvSceneSwitcher.action.filter.type.disable="Etkisiz" AdvSceneSwitcher.action.filter.type.disable="Etkisiz"
@@ -305,7 +299,7 @@ AdvSceneSwitcher.action.macro="Makro"
AdvSceneSwitcher.action.macro.type.pause="Duraklat" AdvSceneSwitcher.action.macro.type.pause="Duraklat"
AdvSceneSwitcher.action.macro.type.unpause="Duraklatma" AdvSceneSwitcher.action.macro.type.unpause="Duraklatma"
AdvSceneSwitcher.action.macro.type.resetCounter="Sayacı sıfırla" AdvSceneSwitcher.action.macro.type.resetCounter="Sayacı sıfırla"
AdvSceneSwitcher.action.macro.type.run="Çalıştır" AdvSceneSwitcher.action.macro.type.runActions="Çalıştır"
AdvSceneSwitcher.action.pluginState="Eklenti durumu" AdvSceneSwitcher.action.pluginState="Eklenti durumu"
AdvSceneSwitcher.action.pluginState.type.stop="Advanced Scene Switcher eklentisini durdurun" AdvSceneSwitcher.action.pluginState.type.stop="Advanced Scene Switcher eklentisini durdurun"
AdvSceneSwitcher.action.pluginState.type.noMatch="Eşleşmeme davranışını değiştirin:" AdvSceneSwitcher.action.pluginState.type.noMatch="Eşleşmeme davranışını değiştirin:"
@@ -342,8 +336,8 @@ AdvSceneSwitcher.action.file.type.write="Yaz"
AdvSceneSwitcher.action.file.type.append="Ekle" AdvSceneSwitcher.action.file.type.append="Ekle"
AdvSceneSwitcher.action.file.entry="{{actions}} {{filePath}}:" AdvSceneSwitcher.action.file.entry="{{actions}} {{filePath}}:"
AdvSceneSwitcher.action.transition="Geçiş" AdvSceneSwitcher.action.transition="Geçiş"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Geçiş türünü ayarla {{transitions}}" AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Geçiş türünü ayarla {{transitions}}"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Geçiş süresini şuna ayarla: {{duration}}saniyeler" AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Geçiş süresini şuna ayarla: {{duration}}saniyeler"
AdvSceneSwitcher.action.timer="Zamanlayıcı" AdvSceneSwitcher.action.timer="Zamanlayıcı"
AdvSceneSwitcher.action.timer.type.pause="Duraklat" AdvSceneSwitcher.action.timer.type.pause="Duraklat"
AdvSceneSwitcher.action.timer.type.continue="Devam et" AdvSceneSwitcher.action.timer.type.continue="Devam et"
@@ -587,8 +581,6 @@ AdvSceneSwitcher.status.inactive="İnaktif"
AdvSceneSwitcher.running="Eklenti çalışıyor" AdvSceneSwitcher.running="Eklenti çalışıyor"
AdvSceneSwitcher.stopped="Eklenti durdu" 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.milliseconds="millisaniye"
AdvSceneSwitcher.unit.seconds="saniye" AdvSceneSwitcher.unit.seconds="saniye"
AdvSceneSwitcher.unit.minutes="dakika" AdvSceneSwitcher.unit.minutes="dakika"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#fefefe" xmlns="http://www.w3.org/2000/svg"
width="800px" height="800px" viewBox="0 0 52 52" enable-background="new 0 0 52 52" xml:space="preserve">
<path d="M42.6,17.8c2.4,0,7.2-2,7.2-8.4c0-6.4-4.6-6.8-6.1-6.8c-2.8,0-5.6,2-8.1,6.3c-2.5,4.4-5.3,9.1-5.3,9.1
l-0.1,0c-0.6-3.1-1.1-5.6-1.3-6.7c-0.5-2.7-3.6-8.4-9.9-8.4c-6.4,0-12.2,3.7-12.2,3.7l0,0C5.8,7.3,5.1,8.5,5.1,9.9
c0,2.1,1.7,3.9,3.9,3.9c0.6,0,1.2-0.2,1.7-0.4l0,0c0,0,4.8-2.7,5.9,0c0.3,0.8,0.6,1.7,0.9,2.7c1.2,4.2,2.4,9.1,3.3,13.5l-4.2,6
c0,0-4.7-1.7-7.1-1.7s-7.2,2-7.2,8.4s4.6,6.8,6.1,6.8c2.8,0,5.6-2,8.1-6.3c2.5-4.4,5.3-9.1,5.3-9.1c0.8,4,1.5,7.1,1.9,8.5
c1.6,4.5,5.3,7.2,10.1,7.2c0,0,5,0,10.9-3.3c1.4-0.6,2.4-2,2.4-3.6c0-2.1-1.7-3.9-3.9-3.9c-0.6,0-1.2,0.2-1.7,0.4l0,0
c0,0-4.2,2.4-5.6,0.5c-1-2-1.9-4.6-2.6-7.8c-0.6-2.8-1.3-6.2-2-9.5l4.3-6.2C35.5,16.1,40.2,17.8,42.6,17.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 973 B

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#202020" xmlns="http://www.w3.org/2000/svg"
width="800px" height="800px" viewBox="0 0 52 52" enable-background="new 0 0 52 52" xml:space="preserve">
<path d="M42.6,17.8c2.4,0,7.2-2,7.2-8.4c0-6.4-4.6-6.8-6.1-6.8c-2.8,0-5.6,2-8.1,6.3c-2.5,4.4-5.3,9.1-5.3,9.1
l-0.1,0c-0.6-3.1-1.1-5.6-1.3-6.7c-0.5-2.7-3.6-8.4-9.9-8.4c-6.4,0-12.2,3.7-12.2,3.7l0,0C5.8,7.3,5.1,8.5,5.1,9.9
c0,2.1,1.7,3.9,3.9,3.9c0.6,0,1.2-0.2,1.7-0.4l0,0c0,0,4.8-2.7,5.9,0c0.3,0.8,0.6,1.7,0.9,2.7c1.2,4.2,2.4,9.1,3.3,13.5l-4.2,6
c0,0-4.7-1.7-7.1-1.7s-7.2,2-7.2,8.4s4.6,6.8,6.1,6.8c2.8,0,5.6-2,8.1-6.3c2.5-4.4,5.3-9.1,5.3-9.1c0.8,4,1.5,7.1,1.9,8.5
c1.6,4.5,5.3,7.2,10.1,7.2c0,0,5,0,10.9-3.3c1.4-0.6,2.4-2,2.4-3.6c0-2.1-1.7-3.9-3.9-3.9c-0.6,0-1.2,0.2-1.7,0.4l0,0
c0,0-4.2,2.4-5.6,0.5c-1-2-1.9-4.6-2.6-7.8c-0.6-2.8-1.3-6.2-2-9.5l4.3-6.2C35.5,16.1,40.2,17.8,42.6,17.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

1
deps/date vendored

Submodule deps/date deleted from 5bdb7e6f31

2
deps/leptonica vendored

2
deps/libusb vendored

2
deps/opencv vendored

1
deps/opencv_contrib vendored Submodule

Submodule deps/opencv_contrib added at 755e50675d

1
deps/whisper.cpp vendored Submodule

Submodule deps/whisper.cpp added at 306c88f4d1

View File

@@ -68,365 +68,162 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>962</width> <width>962</width>
<height>1057</height> <height>1190</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_19"> <layout class="QVBoxLayout" name="verticalLayout_19">
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_31"> <widget class="QGroupBox" name="statusBox">
<item> <property name="title">
<layout class="QVBoxLayout" name="verticalLayout_2"> <string>AdvSceneSwitcher.generalTab.status</string>
<item> </property>
<widget class="QGroupBox" name="statusBox"> <layout class="QGridLayout" name="gridLayout_4">
<property name="title"> <item row="0" column="0">
<string>AdvSceneSwitcher.generalTab.status</string> <layout class="QGridLayout" name="statusLayout">
</property> <item row="4" column="1">
<layout class="QGridLayout" name="gridLayout_4"> <widget class="QComboBox" name="autoStartEvent"/>
<item row="0" column="0"> </item>
<layout class="QGridLayout" name="statusLayout"> <item row="4" column="0">
<item row="4" column="1"> <widget class="QLabel" name="label_10">
<widget class="QComboBox" name="autoStartEvent"/> <property name="text">
</item> <string>AdvSceneSwitcher.generalTab.status.autoStart.startup</string>
<item row="4" column="0"> </property>
<widget class="QLabel" name="label_10"> </widget>
<property name="text"> </item>
<string>AdvSceneSwitcher.generalTab.status.autoStart.startup</string> <item row="0" column="0">
</property> <layout class="QVBoxLayout" name="verticalLayout_33">
</widget> <item>
</item> <widget class="QLabel" name="label_4">
<item row="0" column="0"> <property name="text">
<layout class="QVBoxLayout" name="verticalLayout_33"> <string>AdvSceneSwitcher.generalTab.status.checkInterval</string>
<item> </property>
<widget class="QLabel" name="label_4"> </widget>
<property name="text"> </item>
<string>AdvSceneSwitcher.generalTab.status.checkInterval</string> <item>
</property> <widget class="QLabel" name="checkIntervalTooLowWarning">
</widget> <property name="text">
</item> <string/>
<item> </property>
<widget class="QLabel" name="checkIntervalTooLowWarning"> </widget>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="startupBehavior"/>
</item>
<item row="1" column="1">
<widget class="QWidget" name="placeholder3" native="true"/>
</item>
<item row="1" column="0">
<widget class="QWidget" name="placeholder" native="true"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_64">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.onStartup</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QWidget" name="placeholder2" native="true"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.hotkeytips</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="checkInterval">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="suffix">
<string notr="true">ms</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>20000</number>
</property>
<property name="value">
<number>300</number>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="autoStartSceneEnable">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.autoStart.scene</string>
</property>
</widget>
</item>
<item row="5" column="1">
<layout class="QHBoxLayout" name="autoStartSceneLayout">
<item>
<widget class="advss::SceneSelectionWidget" name="autoStartScene" native="true"/>
</item>
<item>
<widget class="advss::VariableLineEdit" name="autoStartSceneName"/>
</item>
<item>
<widget class="advss::RegexConfigWidget" name="autoStartSceneNameRegex" native="true"/>
</item>
</layout>
</item>
</layout>
</item> </item>
</layout> </layout>
</widget> </item>
</item> <item row="3" column="1">
<item> <widget class="QComboBox" name="startupBehavior"/>
<widget class="QGroupBox" name="generalSettingsBox"> </item>
<property name="title"> <item row="1" column="1">
<string>AdvSceneSwitcher.generalTab.generalBehavior</string> <widget class="QWidget" name="placeholder3" native="true"/>
</property> </item>
<layout class="QVBoxLayout" name="verticalLayout_32"> <item row="1" column="0">
<widget class="QWidget" name="placeholder" native="true"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_64">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.onStartup</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QWidget" name="placeholder2" native="true"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.hotkeytips</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="checkInterval">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="suffix">
<string notr="true">ms</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>20000</number>
</property>
<property name="value">
<number>300</number>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="autoStartSceneEnable">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.autoStart.scene</string>
</property>
</widget>
</item>
<item row="5" column="1">
<layout class="QHBoxLayout" name="autoStartSceneLayout">
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_52"> <widget class="advss::SceneSelectionWidget" name="autoStartScene" native="true"/>
<item>
<widget class="QLabel" name="label_21">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.logLevel</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="logLevel"/>
</item>
<item>
<spacer name="horizontalSpacer_49">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_50"> <widget class="advss::VariableLineEdit" name="autoStartSceneName"/>
<item>
<widget class="QCheckBox" name="saveWindowGeo">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_55">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <widget class="advss::RegexConfigWidget" name="autoStartSceneNameRegex" native="true"/>
<item>
<widget class="QCheckBox" name="showTrayNotifications">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_48">
<item>
<widget class="QCheckBox" name="uiHintsDisable">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_52">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QCheckBox" name="disableComboBoxFilter">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.comboBoxFilterDisable</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_13">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QCheckBox" name="disableMacroWidgetCache">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.disableMacroWidgetCache</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_26">
<item>
<widget class="QCheckBox" name="warnPluginLoadFailure">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailure</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_19">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_24">
<item>
<widget class="QCheckBox" name="hideLegacyTabs">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_16">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item> </item>
</layout> </layout>
</widget> </item>
</item> </layout>
<item> </item>
<layout class="QVBoxLayout" name="verticalLayout_18"> </layout>
<item> </widget>
<widget class="QGroupBox" name="saveLoadBox">
<property name="title">
<string>AdvSceneSwitcher.generalTab.saveOrLoadsettings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_17">
<item>
<widget class="QPushButton" name="exportSettings">
<property name="text">
<string>AdvSceneSwitcher.generalTab.saveOrLoadsettings.export</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="importSettings">
<property name="text">
<string>AdvSceneSwitcher.generalTab.saveOrLoadsettings.import</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="groupBox_6"> <widget class="QGroupBox" name="saveLoadBox">
<property name="title"> <property name="title">
<string>AdvSceneSwitcher.generalTab.matchBehavior</string> <string>AdvSceneSwitcher.generalTab.saveOrLoadsettings</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_37"> <layout class="QVBoxLayout" name="verticalLayout_17">
<item> <item>
<layout class="QHBoxLayout" name="cooldownLayout"> <widget class="QPushButton" name="exportSettings">
<property name="text">
<string>AdvSceneSwitcher.generalTab.saveOrLoadsettings.export</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="importSettings">
<property name="text">
<string>AdvSceneSwitcher.generalTab.saveOrLoadsettings.import</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="generalSettingsBox">
<property name="title">
<string>AdvSceneSwitcher.generalTab.generalBehavior</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_32">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_52">
<item> <item>
<widget class="QCheckBox" name="enableCooldown"> <widget class="QLabel" name="label_21">
<property name="text"> <property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.cooldown</string> <string>AdvSceneSwitcher.generalTab.generalBehavior.logLevel</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="advss::DurationSelection" name="cooldownTime" native="true"/> <widget class="QComboBox" name="logLevel"/>
</item> </item>
<item> <item>
<spacer name="horizontalSpacer_21"> <spacer name="horizontalSpacer_49">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
@@ -441,80 +238,86 @@
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QGridLayout" name="gridLayout"> <widget class="QCheckBox" name="showTrayNotifications">
<item row="0" column="2"> <property name="text">
<layout class="QHBoxLayout" name="horizontalLayout_43"> <string>AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications</string>
<item> </property>
<widget class="QRadioButton" name="noMatchDontSwitch"> </widget>
<property name="text"> </item>
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.dontSwitch</string> <item>
</property> <widget class="QCheckBox" name="warnPluginLoadFailure">
<property name="checked"> <property name="text">
<bool>true</bool> <string>AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailure</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> <item>
</item> <widget class="QCheckBox" name="suppressCrashRecoveryDialog">
<item row="2" column="2"> <property name="text">
<layout class="QHBoxLayout" name="horizontalLayout_45"> <string>AdvSceneSwitcher.generalTab.generalBehavior.suppressCrashRecoveryDialog</string>
<item> </property>
<widget class="QRadioButton" name="noMatchRandomSwitch"> </widget>
<property name="text"> </item>
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchToRandom</string> <item>
</property> <widget class="QCheckBox" name="settingsLock">
</widget> <property name="text">
</item> <string>AdvSceneSwitcher.generalTab.generalBehavior.settingsLock</string>
</layout> </property>
</item> </widget>
<item row="1" column="2"> </item>
<layout class="QHBoxLayout" name="horizontalLayout"> </layout>
<property name="leftMargin"> </widget>
<number>0</number> </item>
</property> <item>
<property name="topMargin"> <widget class="QGroupBox" name="groupBox_6">
<number>0</number> <property name="title">
</property> <string>AdvSceneSwitcher.generalTab.uiBehavior</string>
<property name="rightMargin"> </property>
<number>0</number> <layout class="QVBoxLayout" name="verticalLayout_2">
</property> <item>
<property name="bottomMargin"> <widget class="QCheckBox" name="saveWindowGeo">
<number>0</number> <property name="text">
</property> <string>AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo</string>
<item> </property>
<widget class="QRadioButton" name="noMatchSwitch"> </widget>
<property name="text"> </item>
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchTo</string> <item>
</property> <widget class="QCheckBox" name="alwaysShowMacroSearch">
</widget> <property name="text">
</item> <string>AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowMacroSearch</string>
<item> </property>
<widget class="advss::SceneSelectionWidget" name="noMatchSwitchScene"> </widget>
</widget> </item>
</item> <item>
</layout> <widget class="QCheckBox" name="alwaysShowFeatureTabs">
</item> <property name="toolTip">
<item row="1" column="1"> <string>AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowFeatureTabs.tooltip</string>
<layout class="QHBoxLayout" name="noMatchLayout"> </property>
<item> <property name="text">
<widget class="QLabel" name="label_2"> <string>AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowFeatureTabs</string>
<property name="sizePolicy"> </property>
<sizepolicy hsizetype="Preferred" vsizetype="Preferred"> </widget>
<horstretch>0</horstretch> </item>
<verstretch>0</verstretch> <item>
</sizepolicy> <widget class="QCheckBox" name="disableComboBoxFilter">
</property> <property name="text">
<property name="text"> <string>AdvSceneSwitcher.generalTab.generalBehavior.comboBoxFilterDisable</string>
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch</string> </property>
</property> </widget>
<property name="alignment"> </item>
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> <item>
</property> <widget class="QCheckBox" name="uiHintsDisable">
</widget> <property name="text">
</item> <string>AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints</string>
</layout> </property>
</item> </widget>
</layout> </item>
<item>
<widget class="QCheckBox" name="hideLegacyTabs">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@@ -562,6 +365,109 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="matchBehaviorBox">
<property name="title">
<string>AdvSceneSwitcher.generalTab.matchBehavior</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_37">
<item>
<layout class="QHBoxLayout" name="cooldownLayout">
<item>
<widget class="QCheckBox" name="enableCooldown">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.cooldown</string>
</property>
</widget>
</item>
<item>
<widget class="advss::DurationSelection" name="cooldownTime" native="true"/>
</item>
<item>
<spacer name="horizontalSpacer_21">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<widget class="QRadioButton" name="noMatchDontSwitch">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.dontSwitch</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QRadioButton" name="noMatchRandomSwitch">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchToRandom</string>
</property>
</widget>
</item>
<item row="1" column="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="noMatchSwitch">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchTo</string>
</property>
</widget>
</item>
<item>
<widget class="advss::SceneSelectionWidget" name="noMatchSwitchScene" native="true"/>
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="noMatchLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="priorityBox"> <widget class="QGroupBox" name="priorityBox">
<property name="title"> <property name="title">
@@ -576,15 +482,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout_10"> <widget class="QListWidget" name="priorityList"/>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_41">
<item>
<widget class="QListWidget" name="priorityList"/>
</item>
</layout>
</item>
</layout>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_32"> <layout class="QHBoxLayout" name="horizontalLayout_32">
@@ -681,6 +579,13 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="openSetupWizard">
<property name="text">
<string>FirstRunWizard.openButton</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
@@ -706,7 +611,7 @@
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<widget class="QGroupBox" name="groupBox_5"> <widget class="QGroupBox" name="macroListBox">
<property name="title"> <property name="title">
<string>AdvSceneSwitcher.macroTab.macros</string> <string>AdvSceneSwitcher.macroTab.macros</string>
</property> </property>
@@ -721,6 +626,55 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<layout class="QHBoxLayout" name="macroSearchLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="macroSearchText">
<property name="placeholderText">
<string>AdvSceneSwitcher.macroTab.search.placeholder</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="macroSearchClear">
<property name="toolTip">
<string>AdvSceneSwitcher.variableTab.clear</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="macroSearchType"/>
</item>
<item>
<widget class="advss::RegexConfigWidget" name="macroSearchRegex" native="true"/>
</item>
<item>
<widget class="QPushButton" name="macroSearchShowSettings">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.search.showSettings</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<widget class="advss::MacroTree" name="macros"> <widget class="advss::MacroTree" name="macros">
<property name="showDropIndicator" stdset="0"> <property name="showDropIndicator" stdset="0">
@@ -853,25 +807,19 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="runMacroOnChange"> <widget class="QLabel" name="label_18">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.onChange</string>
</property>
<property name="text"> <property name="text">
<string/> <string>AdvSceneSwitcher.macroTab.actionTriggerMode.label</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="label_18"> <widget class="QComboBox" name="actionTriggerMode">
<property name="toolTip"> <property name="toolTip">
<string>AdvSceneSwitcher.macroTab.onChange</string> <string>AdvSceneSwitcher.macroTab.actionTriggerMode.tooltip</string>
</property>
<property name="text">
<string>AdvSceneSwitcher.macroTab.onChange</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
@@ -4164,12 +4112,7 @@
<tabstop>autoStartSceneName</tabstop> <tabstop>autoStartSceneName</tabstop>
<tabstop>autoStartSceneNameRegex</tabstop> <tabstop>autoStartSceneNameRegex</tabstop>
<tabstop>logLevel</tabstop> <tabstop>logLevel</tabstop>
<tabstop>saveWindowGeo</tabstop>
<tabstop>showTrayNotifications</tabstop>
<tabstop>uiHintsDisable</tabstop>
<tabstop>disableComboBoxFilter</tabstop>
<tabstop>warnPluginLoadFailure</tabstop> <tabstop>warnPluginLoadFailure</tabstop>
<tabstop>hideLegacyTabs</tabstop>
<tabstop>exportSettings</tabstop> <tabstop>exportSettings</tabstop>
<tabstop>importSettings</tabstop> <tabstop>importSettings</tabstop>
<tabstop>noMatchDontSwitch</tabstop> <tabstop>noMatchDontSwitch</tabstop>
@@ -4190,7 +4133,7 @@
<tabstop>macroName</tabstop> <tabstop>macroName</tabstop>
<tabstop>runMacro</tabstop> <tabstop>runMacro</tabstop>
<tabstop>runMacroInParallel</tabstop> <tabstop>runMacroInParallel</tabstop>
<tabstop>runMacroOnChange</tabstop> <tabstop>actionTriggerMode</tabstop>
<tabstop>macroSettings</tabstop> <tabstop>macroSettings</tabstop>
<tabstop>macroEdit</tabstop> <tabstop>macroEdit</tabstop>
<tabstop>sceneGroups</tabstop> <tabstop>sceneGroups</tabstop>

View File

@@ -1,5 +1,6 @@
#include "advanced-scene-switcher.hpp" #include "advanced-scene-switcher.hpp"
#include "backup.hpp" #include "backup.hpp"
#include "crash-handler.hpp"
#include "log-helper.hpp" #include "log-helper.hpp"
#include "macro-helpers.hpp" #include "macro-helpers.hpp"
#include "obs-module-helper.hpp" #include "obs-module-helper.hpp"
@@ -18,8 +19,10 @@
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <QAction> #include <QAction>
#include <QDirIterator> #include <QDirIterator>
#include <QInputDialog>
#include <QLibrary> #include <QLibrary>
#include <QMainWindow> #include <QMainWindow>
#include <QMessageBox>
#include <QTextStream> #include <QTextStream>
#include <regex> #include <regex>
@@ -40,9 +43,12 @@ AdvSceneSwitcher::AdvSceneSwitcher(QWidget *parent)
{ {
switcher->settingsWindowOpened = true; switcher->settingsWindowOpened = true;
ui->setupUi(this); ui->setupUi(this);
std::lock_guard<std::mutex> lock(switcher->m); {
switcher->Prune(); std::lock_guard<std::mutex> lock(switcher->m);
LoadUI(); switcher->Prune();
LoadUI();
}
CheckFirstTimeSetup();
} }
AdvSceneSwitcher::~AdvSceneSwitcher() AdvSceneSwitcher::~AdvSceneSwitcher()
@@ -138,7 +144,6 @@ void AdvSceneSwitcher::LoadUI()
SetTabOrder(ui->tabWidget); SetTabOrder(ui->tabWidget);
SetCurrentTab(ui->tabWidget); SetCurrentTab(ui->tabWidget);
RestoreWindowGeo(); RestoreWindowGeo();
CheckFirstTimeSetup();
loading = false; loading = false;
} }
@@ -191,25 +196,21 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
switcher->m.lock(); switcher->m.lock();
if (switcher->VersionChanged(data, g_GIT_SHA1)) { if (switcher->VersionChanged(data, g_GIT_SHA1)) {
auto json = obs_data_get_json(data); AskForBackup(data);
static QString jsonQString = json ? json : "";
std::thread t([]() {
obs_queue_task(
OBS_TASK_UI,
[](void *) {
AskForBackup(jsonQString);
},
nullptr, false);
});
t.detach();
} }
switcher->LoadSettings(data); switcher->LoadSettings(data);
switcher->m.unlock(); switcher->m.unlock();
if (!switcher->stop) { if (switcher->stop) {
switcher->Start(); return;
} }
if (ShouldSkipPluginStartOnUncleanShutdown()) {
return;
}
switcher->Start();
} }
} }
@@ -325,8 +326,7 @@ void SwitcherData::SetPreconditions()
{ {
// Window title // Window title
lastTitle = currentTitle; lastTitle = currentTitle;
std::string title; auto title = GetCurrentWindowTitle();
GetCurrentWindowTitle(title);
for (auto &window : ignoreWindowsSwitches) { for (auto &window : ignoreWindowsSwitches) {
bool equals = (title == window); bool equals = (title == window);
bool matches = false; bool matches = false;
@@ -346,7 +346,7 @@ void SwitcherData::SetPreconditions()
currentTitle = title; currentTitle = title;
// Process name // Process name
GetForegroundProcessName(currentForegroundProcess); currentForegroundProcess = GetForegroundProcessName();
// Macro // Macro
InvalidateMacroTempVarValues(); InvalidateMacroTempVarValues();
@@ -421,7 +421,7 @@ bool SwitcherData::CheckForMatch(OBSWeakSource &scene,
static void ResetMacros() static void ResetMacros()
{ {
for (auto &m : GetMacros()) { for (auto &m : GetTopLevelMacros()) {
ResetMacroRunCount(m.get()); ResetMacroRunCount(m.get());
ResetMacroConditionTimers(m.get()); ResetMacroConditionTimers(m.get());
} }
@@ -461,12 +461,13 @@ void SwitcherData::Stop()
SetMacroAbortWait(true); SetMacroAbortWait(true);
GetMacroWaitCV().notify_all(); GetMacroWaitCV().notify_all();
GetMacroTransitionCV().notify_all(); GetMacroTransitionCV().notify_all();
StopAllMacros(); SignalStopAllMacros();
StopAndClearAllActionQueues(); StopAndClearAllActionQueues();
CloseAllInputDialogs(); CloseAllInputDialogs();
th->wait(); th->wait();
delete th; delete th;
th = nullptr; th = nullptr;
WaitForAllMacros();
RunStopSteps(); RunStopSteps();
} }
@@ -510,6 +511,8 @@ bool SwitcherData::AnySceneTransitionStarted()
******************************************************************************/ ******************************************************************************/
extern "C" EXPORT void FreeSceneSwitcher() extern "C" EXPORT void FreeSceneSwitcher()
{ {
switcher->Stop();
PlatformCleanup(); PlatformCleanup();
RunPluginCleanupSteps(); RunPluginCleanupSteps();
@@ -631,13 +634,20 @@ static void handleSceneCollectionCleanup()
return; return;
} }
// OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP is also called on
// shutdown.
// Here we also don't want to clear the settings.
if (switcher->obsIsShuttingDown) {
return;
}
SaveSceneSwitcher(nullptr, false, nullptr); SaveSceneSwitcher(nullptr, false, nullptr);
} }
// Note to future self: // Note to future self:
// be careful using switcher->m here as there is potential for deadlocks when using // be careful using switcher->m here as there is potential for deadlocks when using
// frontend functions such as obs_frontend_set_current_scene() // frontend functions such as obs_frontend_set_current_scene()
static void OBSEvent(enum obs_frontend_event event, void *switcher) static void OBSEvent(enum obs_frontend_event event, void *)
{ {
if (!switcher) { if (!switcher) {
return; return;
@@ -711,19 +721,54 @@ static void LoadPlugins()
} }
} }
static bool settingsPasswordIsCorrect(QWidget *parent)
{
bool ok;
QString input = QInputDialog::getText(
parent,
obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title"),
obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.passwordLabel"),
QLineEdit::Password, QString(), &ok);
if (!ok) {
return false;
}
if (input.toStdString() == switcher->settingsLockPassword) {
return true;
}
QMessageBox::warning(
parent,
obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title"),
obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.wrongPassword"));
return false;
}
void OpenSettingsWindow() void OpenSettingsWindow()
{ {
if (switcher->settingsWindowOpened) { if (switcher->settingsWindowOpened) {
AdvSceneSwitcher::window->show(); AdvSceneSwitcher::window->show();
AdvSceneSwitcher::window->raise(); AdvSceneSwitcher::window->raise();
AdvSceneSwitcher::window->activateWindow(); AdvSceneSwitcher::window->activateWindow();
} else { return;
AdvSceneSwitcher::window =
new AdvSceneSwitcher(static_cast<QMainWindow *>(
obs_frontend_get_main_window()));
AdvSceneSwitcher::window->setAttribute(Qt::WA_DeleteOnClose);
AdvSceneSwitcher::window->show();
} }
auto parent =
static_cast<QMainWindow *>(obs_frontend_get_main_window());
if (switcher->settingsLockEnabled &&
!switcher->settingsLockPassword.empty() &&
!settingsPasswordIsCorrect(parent)) {
return;
}
AdvSceneSwitcher::window = new AdvSceneSwitcher(parent);
AdvSceneSwitcher::window->setAttribute(Qt::WA_DeleteOnClose);
AdvSceneSwitcher::window->show();
} }
void AdvSceneSwitcher::HighlightMacroSettingsButton(bool enable) void AdvSceneSwitcher::HighlightMacroSettingsButton(bool enable)
@@ -771,8 +816,6 @@ void HighlightMacroSettingsButton(bool enable)
enable); enable);
} }
void SetupActionQueues();
extern "C" EXPORT void InitSceneSwitcher(obs_module_t *module, extern "C" EXPORT void InitSceneSwitcher(obs_module_t *module,
translateFunc translate) translateFunc translate)
{ {
@@ -784,12 +827,11 @@ extern "C" EXPORT void InitSceneSwitcher(obs_module_t *module,
PlatformInit(); PlatformInit();
LoadPlugins(); LoadPlugins();
SetupDock(); SetupDock();
SetupActionQueues();
RunPluginInitSteps(); RunPluginInitSteps();
obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr); obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr);
obs_frontend_add_event_callback(OBSEvent, switcher); obs_frontend_add_event_callback(OBSEvent, nullptr);
QAction *action = (QAction *)obs_frontend_add_tools_menu_qaction( QAction *action = (QAction *)obs_frontend_add_tools_menu_qaction(
obs_module_text("AdvSceneSwitcher.pluginName")); obs_module_text("AdvSceneSwitcher.pluginName"));

View File

@@ -1,7 +1,4 @@
#pragma once #pragma once
#include "macro-segment-list.hpp"
#include "condition-logic.hpp"
#include "log-helper.hpp"
#include <ui_advanced-scene-switcher.h> #include <ui_advanced-scene-switcher.h>
@@ -65,12 +62,14 @@ public slots:
void on_showTrayNotifications_stateChanged(int state); void on_showTrayNotifications_stateChanged(int state);
void on_uiHintsDisable_stateChanged(int state); void on_uiHintsDisable_stateChanged(int state);
void on_disableComboBoxFilter_stateChanged(int state); void on_disableComboBoxFilter_stateChanged(int state);
void on_disableMacroWidgetCache_stateChanged(int state);
void on_warnPluginLoadFailure_stateChanged(int state); void on_warnPluginLoadFailure_stateChanged(int state);
void on_suppressCrashRecoveryDialog_stateChanged(int state);
void on_hideLegacyTabs_stateChanged(int state); void on_hideLegacyTabs_stateChanged(int state);
void on_settingsLock_stateChanged(int state);
void on_priorityUp_clicked(); void on_priorityUp_clicked();
void on_priorityDown_clicked(); void on_priorityDown_clicked();
void on_threadPriority_currentTextChanged(const QString &text); void on_threadPriority_currentTextChanged(const QString &text);
void on_openSetupWizard_clicked();
/* --- End of legacy tab section --- */ /* --- End of legacy tab section --- */
@@ -108,13 +107,15 @@ public slots:
void on_macroDown_clicked() const; void on_macroDown_clicked() const;
void on_macroName_editingFinished(); void on_macroName_editingFinished();
void on_runMacroInParallel_stateChanged(int value) const; void on_runMacroInParallel_stateChanged(int value) const;
void on_runMacroOnChange_stateChanged(int value) const; void on_actionTriggerMode_currentIndexChanged(int index) const;
void MacroSelectionChanged(); void MacroSelectionChanged();
void ShowMacroContextMenu(const QPoint &); void ShowMacroContextMenu(const QPoint &);
void CopyMacro(); void CopyMacro();
void RenameSelectedMacro(); void RenameSelectedMacro();
void ExportMacros() const; void ExportMacros() const;
void ImportMacros(); void ImportMacros();
void PauseSelectedMacros();
void UnpauseSelectedMacros();
void HighlightOnChange() const; void HighlightOnChange() const;
void on_macroSettings_clicked(); void on_macroSettings_clicked();

View File

@@ -1,23 +1,28 @@
#include "advanced-scene-switcher.hpp" #include "advanced-scene-switcher.hpp"
#include "crash-handler.hpp"
#include "file-selection.hpp" #include "file-selection.hpp"
#include "filter-combo-box.hpp" #include "filter-combo-box.hpp"
#include "first-run-wizard.hpp"
#include "layout-helpers.hpp" #include "layout-helpers.hpp"
#include "macro.hpp" #include "macro-helpers.hpp"
#include "macro-search.hpp"
#include "macro-settings.hpp" #include "macro-settings.hpp"
#include "macro.hpp"
#include "path-helpers.hpp" #include "path-helpers.hpp"
#include "selection-helpers.hpp"
#include "source-helpers.hpp" #include "source-helpers.hpp"
#include "splitter-helpers.hpp" #include "splitter-helpers.hpp"
#include "status-control.hpp" #include "status-control.hpp"
#include "switcher-data.hpp" #include "switcher-data.hpp"
#include "tab-helpers.hpp" #include "tab-helpers.hpp"
#include "ui-helpers.hpp" #include "ui-helpers.hpp"
#include "utility.hpp"
#include "variable.hpp" #include "variable.hpp"
#include "version.h" #include "version.h"
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <QFileDialog> #include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox>
namespace advss { namespace advss {
@@ -185,16 +190,6 @@ void AdvSceneSwitcher::on_disableComboBoxFilter_stateChanged(int state)
FilterComboBox::SetFilterBehaviourEnabled(!state); FilterComboBox::SetFilterBehaviourEnabled(!state);
} }
void AdvSceneSwitcher::on_disableMacroWidgetCache_stateChanged(int state)
{
if (loading) {
return;
}
switcher->disableMacroWidgetCache = state;
MacroSegmentList::SetCachingEnabled(!state);
}
void AdvSceneSwitcher::on_warnPluginLoadFailure_stateChanged(int state) void AdvSceneSwitcher::on_warnPluginLoadFailure_stateChanged(int state)
{ {
if (loading) { if (loading) {
@@ -204,6 +199,15 @@ void AdvSceneSwitcher::on_warnPluginLoadFailure_stateChanged(int state)
switcher->warnPluginLoadFailure = state; switcher->warnPluginLoadFailure = state;
} }
void AdvSceneSwitcher::on_suppressCrashRecoveryDialog_stateChanged(int state)
{
if (loading) {
return;
}
SetSuppressCrashDialog(state);
}
static bool isLegacyTab(const QString &name) static bool isLegacyTab(const QString &name)
{ {
return name == obs_module_text( return name == obs_module_text(
@@ -243,6 +247,64 @@ void AdvSceneSwitcher::on_hideLegacyTabs_stateChanged(int state)
ui->priorityBox->setVisible(!switcher->hideLegacyTabs); ui->priorityBox->setVisible(!switcher->hideLegacyTabs);
} }
void AdvSceneSwitcher::on_settingsLock_stateChanged(int state)
{
if (loading) {
return;
}
if (!state) {
switcher->settingsLockEnabled = false;
switcher->settingsLockPassword = "";
return;
}
bool ok;
QString newPassword = QInputDialog::getText(
this,
obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title"),
obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.newPasswordLabel"),
QLineEdit::Password, QString(), &ok);
const auto disablePassword = [this]() {
QSignalBlocker blocker(ui->settingsLock);
ui->settingsLock->setChecked(false);
ui->settingsLock->setCheckState(Qt::Unchecked);
switcher->settingsLockEnabled = false;
switcher->settingsLockPassword = "";
};
if (!ok || newPassword.isEmpty()) {
disablePassword();
return;
}
QString confirm = QInputDialog::getText(
this,
obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title"),
obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.confirmPasswordLabel"),
QLineEdit::Password, QString(), &ok);
if (!ok || newPassword != confirm) {
if (ok) {
QMessageBox::warning(
this,
obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.title"),
obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.settingsLock.passwordMismatch"));
}
disablePassword();
return;
}
switcher->settingsLockEnabled = true;
switcher->settingsLockPassword = newPassword.toStdString();
}
void AdvSceneSwitcher::SetDeprecationWarnings() void AdvSceneSwitcher::SetDeprecationWarnings()
{ {
QString toolTip = QString toolTip =
@@ -370,14 +432,50 @@ void AdvSceneSwitcher::RestoreWindowGeo()
} }
} }
static void renameMacroIfNecessary(const std::shared_ptr<Macro> &macro)
{
if (!GetMacroByName(macro->Name().c_str())) {
return;
}
auto name = macro->Name();
int i = 2;
while (GetMacroByName((name + " " + std::to_string(i)).c_str())) {
i++;
}
macro->SetName(name + " " + std::to_string(i));
}
void AdvSceneSwitcher::CheckFirstTimeSetup() void AdvSceneSwitcher::CheckFirstTimeSetup()
{ {
if (switcher->firstBoot && !switcher->disableHints) { if (!IsFirstRun() || !GetTopLevelMacros().empty()) {
switcher->firstBoot = false; return;
DisplayMessage(
obs_module_text("AdvSceneSwitcher.firstBootMessage"));
switcher->Start();
} }
bool wasSkipped = false;
auto macro = wiz::FirstRunWizard::ShowWizard(this, &wasSkipped);
if (macro) {
renameMacroIfNecessary(macro);
QTimer::singleShot(0, this,
[this, macro]() { ui->macros->Add(macro); });
}
if (wasSkipped) {
ui->alwaysShowFeatureTabs->setChecked(true);
}
switcher->Start();
}
void AdvSceneSwitcher::on_openSetupWizard_clicked()
{
auto macro = wiz::FirstRunWizard::ShowWizard(this);
if (!macro) {
return;
}
renameMacroIfNecessary(macro);
ui->macros->Add(macro);
ui->tabWidget->setCurrentWidget(ui->macroTab);
} }
void AdvSceneSwitcher::on_tabWidget_currentChanged(int) void AdvSceneSwitcher::on_tabWidget_currentChanged(int)
@@ -436,7 +534,6 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
// Needs to be loaded before any entries which might rely on scene group // Needs to be loaded before any entries which might rely on scene group
// selections to be available. // selections to be available.
loadSceneGroups(obj); loadSceneGroups(obj);
LoadVariables(obj);
RunLoadSteps(obj); RunLoadSteps(obj);
@@ -459,7 +556,7 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
LoadHotkeys(obj); LoadHotkeys(obj);
LoadUISettings(obj); LoadUISettings(obj);
RunPostLoadSteps(); RunAndClearPostLoadSteps();
// Reset on startup and scene collection change // Reset on startup and scene collection change
ResetLastOpenedTab(); ResetLastOpenedTab();
@@ -475,7 +572,6 @@ void SwitcherData::SaveSettings(obs_data_t *obj)
saveSceneGroups(obj); saveSceneGroups(obj);
SaveMacros(obj); SaveMacros(obj);
SaveGlobalMacroSettings(obj); SaveGlobalMacroSettings(obj);
SaveVariables(obj);
saveWindowTitleSwitches(obj); saveWindowTitleSwitches(obj);
saveScreenRegionSwitches(obj); saveScreenRegionSwitches(obj);
savePauseSwitches(obj); savePauseSwitches(obj);
@@ -531,10 +627,11 @@ void SwitcherData::SaveGeneralSettings(obs_data_t *obj)
obs_data_set_bool(obj, "disableHints", disableHints); obs_data_set_bool(obj, "disableHints", disableHints);
obs_data_set_bool(obj, "disableFilterComboboxFilter", obs_data_set_bool(obj, "disableFilterComboboxFilter",
disableFilterComboboxFilter); disableFilterComboboxFilter);
obs_data_set_bool(obj, "disableMacroWidgetCache",
disableMacroWidgetCache);
obs_data_set_bool(obj, "warnPluginLoadFailure", warnPluginLoadFailure); obs_data_set_bool(obj, "warnPluginLoadFailure", warnPluginLoadFailure);
obs_data_set_bool(obj, "hideLegacyTabs", hideLegacyTabs); obs_data_set_bool(obj, "hideLegacyTabs", hideLegacyTabs);
obs_data_set_bool(obj, "settingsLockEnabled", settingsLockEnabled);
obs_data_set_string(obj, "settingsLockPassword",
settingsLockPassword.c_str());
SaveFunctionPriorities(obj, functionNamesByPriority); SaveFunctionPriorities(obj, functionNamesByPriority);
@@ -602,12 +699,12 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
disableHints = obs_data_get_bool(obj, "disableHints"); disableHints = obs_data_get_bool(obj, "disableHints");
disableFilterComboboxFilter = disableFilterComboboxFilter =
obs_data_get_bool(obj, "disableFilterComboboxFilter"); obs_data_get_bool(obj, "disableFilterComboboxFilter");
disableMacroWidgetCache =
obs_data_get_bool(obj, "disableMacroWidgetCache");
obs_data_set_default_bool(obj, "warnPluginLoadFailure", true); obs_data_set_default_bool(obj, "warnPluginLoadFailure", true);
warnPluginLoadFailure = obs_data_get_bool(obj, "warnPluginLoadFailure"); warnPluginLoadFailure = obs_data_get_bool(obj, "warnPluginLoadFailure");
obs_data_set_default_bool(obj, "hideLegacyTabs", true); obs_data_set_default_bool(obj, "hideLegacyTabs", true);
hideLegacyTabs = obs_data_get_bool(obj, "hideLegacyTabs"); hideLegacyTabs = obs_data_get_bool(obj, "hideLegacyTabs");
settingsLockEnabled = obs_data_get_bool(obj, "settingsLockEnabled");
settingsLockPassword = obs_data_get_string(obj, "settingsLockPassword");
SetDefaultFunctionPriorities(obj); SetDefaultFunctionPriorities(obj);
LoadFunctionPriorities(obj, functionNamesByPriority); LoadFunctionPriorities(obj, functionNamesByPriority);
@@ -927,11 +1024,10 @@ void AdvSceneSwitcher::SetupGeneralTab()
switcher->disableFilterComboboxFilter); switcher->disableFilterComboboxFilter);
FilterComboBox::SetFilterBehaviourEnabled( FilterComboBox::SetFilterBehaviourEnabled(
!switcher->disableFilterComboboxFilter); !switcher->disableFilterComboboxFilter);
ui->disableMacroWidgetCache->setChecked(
switcher->disableMacroWidgetCache);
MacroSegmentList::SetCachingEnabled(!switcher->disableMacroWidgetCache);
ui->warnPluginLoadFailure->setChecked(switcher->warnPluginLoadFailure); ui->warnPluginLoadFailure->setChecked(switcher->warnPluginLoadFailure);
ui->suppressCrashRecoveryDialog->setChecked(GetSuppressCrashDialog());
ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs); ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs);
ui->settingsLock->setChecked(switcher->settingsLockEnabled);
populatePriorityFunctionList(ui->priorityList); populatePriorityFunctionList(ui->priorityList);
populateThreadPriorityList(ui->threadPriority); populateThreadPriorityList(ui->threadPriority);
@@ -1013,6 +1109,19 @@ void AdvSceneSwitcher::SetupGeneralTab()
switcher->CheckAutoStart(); switcher->CheckAutoStart();
}); });
ui->alwaysShowMacroSearch->setChecked(
GetMacroSearchSettings().showAlways);
connect(ui->alwaysShowMacroSearch, &QCheckBox::stateChanged, this,
[this](int enabled) {
GetMacroSearchSettings().showAlways = enabled;
if (loading) {
return;
}
CheckMacroSearchVisibility();
});
// Set up status control // Set up status control
auto statusControl = new StatusControl(this, true); auto statusControl = new StatusControl(this, true);
ui->statusLayout->addWidget(statusControl->StatusPrefixLabel(), 1, 0); ui->statusLayout->addWidget(statusControl->StatusPrefixLabel(), 1, 0);
@@ -1032,6 +1141,8 @@ void AdvSceneSwitcher::SetupGeneralTab()
setTabOrder(ui->importSettings, ui->cooldownTime); setTabOrder(ui->importSettings, ui->cooldownTime);
setTabOrder(ui->cooldownTime, ui->noMatchDontSwitch); setTabOrder(ui->cooldownTime, ui->noMatchDontSwitch);
SetupShowAllTabsCheckBox(ui->alwaysShowFeatureTabs, ui->tabWidget);
MinimizeSizeOfColumn(ui->statusLayout, 0); MinimizeSizeOfColumn(ui->statusLayout, 0);
setWindowTitle(windowTitle() + " - " + g_GIT_TAG); setWindowTitle(windowTitle() + " - " + g_GIT_TAG);
} }

View File

@@ -92,12 +92,11 @@ bool SwitcherData::checkExeSwitch(OBSWeakSource &scene,
} }
std::string title = switcher->currentTitle; std::string title = switcher->currentTitle;
QStringList runningProcesses;
bool ignored = false; bool ignored = false;
bool match = false; bool match = false;
// Check for match // Check for match
GetProcessList(runningProcesses); const auto runningProcesses = GetProcessList();
for (ExecutableSwitch &s : executableSwitches) { for (ExecutableSwitch &s : executableSwitches) {
if (!s.initialized()) { if (!s.initialized()) {
continue; continue;

View File

@@ -178,47 +178,19 @@ void AdvSceneSwitcher::on_ignoreWindows_currentRowChanged(int idx)
} }
} }
void checkWindowTitleSwitchDirect(WindowSwitch &s, static bool windowInfoMatchesSwitch(const WindowInfo &info,
std::string &currentWindowTitle, bool &match, const WindowSwitch &s)
OBSWeakSource &scene,
OBSWeakSource &transition)
{ {
bool focus = (!s.focus || s.window == currentWindowTitle); if (s.focus && !info.focused) {
bool fullscreen = (!s.fullscreen || IsFullscreen(s.window)); return false;
bool max = (!s.maximized || IsMaximized(s.window));
if (focus && fullscreen && max) {
match = true;
scene = s.getScene();
transition = s.transition;
} }
} if (s.fullscreen && !info.fullscreen) {
return false;
void checkWindowTitleSwitchRegex(WindowSwitch &s,
std::string &currentWindowTitle,
std::vector<std::string> windowList,
bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
for (auto &window : windowList) {
try {
std::regex expr(s.window);
if (!std::regex_match(window, expr)) {
continue;
}
} catch (const std::regex_error &) {
}
bool focus = (!s.focus || window == currentWindowTitle);
bool fullscreen = (!s.fullscreen || IsFullscreen(window));
bool max = (!s.maximized || IsMaximized(window));
if (focus && fullscreen && max) {
match = true;
scene = s.getScene();
transition = s.transition;
}
} }
if (s.maximized && !info.maximized) {
return false;
}
return true;
} }
bool SwitcherData::checkWindowTitleSwitch(OBSWeakSource &scene, bool SwitcherData::checkWindowTitleSwitch(OBSWeakSource &scene,
@@ -228,24 +200,37 @@ bool SwitcherData::checkWindowTitleSwitch(OBSWeakSource &scene,
return false; return false;
} }
std::string currentWindowTitle = switcher->currentTitle;
bool match = false; bool match = false;
std::vector<std::string> windowList;
GetWindowList(windowList); WindowQueryOptions options;
options.focus = true;
options.fullscreen = true;
options.maximized = true;
const auto windows = GetWindows(options);
for (WindowSwitch &s : windowSwitches) { for (WindowSwitch &s : windowSwitches) {
if (!s.initialized()) { if (!s.initialized()) {
continue; continue;
} }
if (std::find(windowList.begin(), windowList.end(), s.window) != for (const auto &info : windows) {
windowList.end()) { bool titleMatch = false;
checkWindowTitleSwitchDirect(s, currentWindowTitle, try {
match, scene, transition); std::regex expr(s.window);
} else { titleMatch = info.title == s.window ||
checkWindowTitleSwitchRegex(s, currentWindowTitle, std::regex_match(info.title, expr);
windowList, match, scene, } catch (const std::regex_error &) {
transition); titleMatch = info.title == s.window;
}
if (!titleMatch || !windowInfoMatchesSwitch(info, s)) {
continue;
}
match = true;
scene = s.getScene();
transition = s.transition;
break;
} }
if (match) { if (match) {

View File

@@ -32,6 +32,9 @@
#endif #endif
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <climits>
#include <dirent.h>
#include <unistd.h>
#include "kwin-helpers.h" #include "kwin-helpers.h"
namespace advss { namespace advss {
@@ -233,28 +236,89 @@ std::string getWindowName(Window window)
return windowTitle; return windowTitle;
} }
void GetWindowList(std::vector<std::string> &windows) std::vector<WindowInfo> GetWindows(const WindowQueryOptions &options)
{ {
windows.resize(0); const std::string foregroundTitle = GetCurrentWindowTitle();
for (auto window : getTopLevelWindows()) { const auto topLevel = getTopLevelWindows();
auto name = getWindowName(window); auto display = disp();
if (name.empty()) {
continue;
}
windows.emplace_back(name);
}
}
void GetWindowList(QStringList &windows) std::vector<WindowInfo> result;
{ result.reserve(topLevel.size());
windows.clear();
for (auto window : getTopLevelWindows()) { for (auto window : topLevel) {
auto name = getWindowName(window); auto name = getWindowName(window);
if (name.empty()) { if (name.empty()) {
continue; continue;
} }
windows << QString::fromStdString(name);
WindowInfo info;
info.title = name;
if (options.focus) {
info.focused = (name == foregroundTitle);
}
if (options.fullscreen || options.maximized) {
QStringList states = getStates(window);
if (options.fullscreen) {
info.fullscreen = states.contains(
"_NET_WM_STATE_FULLSCREEN");
}
if (options.maximized) {
info.maximized =
states.contains(
"_NET_WM_STATE_MAXIMIZED_VERT") &&
states.contains(
"_NET_WM_STATE_MAXIMIZED_HORZ");
}
}
if (options.geometry && display) {
XWindowAttributes attrs;
if (XGetWindowAttributes(display, window, &attrs)) {
int x = 0;
int y = 0;
Window child;
XTranslateCoordinates(
display, window,
DefaultRootWindow(display), 0, 0, &x,
&y, &child);
info.x = x;
info.y = y;
info.width = attrs.width;
info.height = attrs.height;
}
}
// windowClass and text not implemented on Linux
result.emplace_back(std::move(info));
} }
// When KWin compat is active, native Wayland windows are not tracked by
// X11 and therefore do not appear in the list above. Merge in any window
// reported by KWin that is not already present (matched by title) so that
// title and focus conditions work for native Wayland windows.
if (KWin) {
for (const auto &[pid, title] :
FocusNotifier::getWindowList()) {
bool found = false;
for (const auto &info : result) {
if (info.title == title) {
found = true;
break;
}
}
if (!found) {
WindowInfo waylandWindow;
waylandWindow.title = title;
waylandWindow.focused =
(title == foregroundTitle);
result.emplace_back(std::move(waylandWindow));
}
}
}
return result;
} }
int getActiveWindow(Window *&window) int getActiveWindow(Window *&window)
@@ -278,91 +342,24 @@ int getActiveWindow(Window *&window)
&bytes, (uint8_t **)&window); &bytes, (uint8_t **)&window);
} }
void GetCurrentWindowTitle(std::string &title) std::string GetCurrentWindowTitle()
{ {
if (KWin) { if (KWin) {
title = FocusNotifier::getActiveWindowTitle(); return FocusNotifier::getActiveWindowTitle();
return;
} }
Window *data = 0; Window *data = 0;
if (getActiveWindow(data) != Success || !data) { if (getActiveWindow(data) != Success || !data) {
return; return {};
} }
if (!data[0]) { if (!data[0]) {
XFree(data); XFree(data);
return; return {};
} }
auto name = getWindowName(data[0]); auto name = getWindowName(data[0]);
XFree(data); XFree(data);
return name;
if (name.empty()) {
return;
}
title = name;
}
bool windowStatesAreSet(const std::string &windowTitle,
std::vector<QString> &expectedStates)
{
if (!ewmhIsSupported()) {
return false;
}
std::vector<Window> windows = getTopLevelWindows();
for (auto &window : windows) {
auto name = getWindowName(window);
if (name.empty()) {
continue;
}
bool equals = windowTitle == name;
bool matches = QString::fromStdString(name).contains(
QRegularExpression(
QString::fromStdString(windowTitle)));
if (!(equals || matches)) {
continue;
}
QStringList states = getStates(window);
if (states.isEmpty()) {
if (expectedStates.empty()) {
return true;
}
return false;
}
for (const auto &state : expectedStates) {
if (!states.contains(state)) {
return false;
}
}
return true;
}
return false;
}
bool IsMaximized(const std::string &title)
{
std::vector<QString> states;
states.emplace_back("_NET_WM_STATE_MAXIMIZED_VERT");
states.emplace_back("_NET_WM_STATE_MAXIMIZED_HORZ");
return windowStatesAreSet(title, states);
}
bool IsFullscreen(const std::string &title)
{
std::vector<QString> states;
states.emplace_back("_NET_WM_STATE_FULLSCREEN");
return windowStatesAreSet(title, states);
}
std::optional<std::string> GetTextInWindow(const std::string &)
{
// Not implemented
return {};
} }
static void getProcessListProcps(QStringList &processes) static void getProcessListProcps(QStringList &processes)
@@ -409,16 +406,17 @@ static void getProcessListProcps2(QStringList &processes)
#endif #endif
} }
void GetProcessList(QStringList &processes) QStringList GetProcessList()
{ {
processes.clear(); QStringList processes;
if (libprocpsSupported) { if (libprocpsSupported) {
getProcessListProcps(processes); getProcessListProcps(processes);
return; return processes;
} }
if (libprocps2Supported) { if (libprocps2Supported) {
getProcessListProcps2(processes); getProcessListProcps2(processes);
} }
return processes;
} }
long getForegroundProcessPid() long getForegroundProcessPid()
@@ -469,24 +467,83 @@ std::string getProcNameFromPid(long pid)
return name; return name;
} }
void GetForegroundProcessName(QString &proc) std::string GetForegroundProcessName()
{ {
std::string temp; auto pid = getForegroundProcessPid();
GetForegroundProcessName(temp); return getProcNameFromPid(pid);
proc = QString::fromStdString(temp);
} }
void GetForegroundProcessName(std::string &proc) static std::string getProcessPathFromPid(long pid)
{
std::string linkPath = "/proc/" + std::to_string(pid) + "/exe";
char buf[PATH_MAX];
ssize_t len = readlink(linkPath.c_str(), buf, sizeof(buf) - 1);
if (len <= 0) {
return {};
}
buf[len] = '\0';
return buf;
}
std::string GetForegroundProcessPath()
{ {
proc.resize(0);
auto pid = getForegroundProcessPid(); auto pid = getForegroundProcessPid();
proc = getProcNameFromPid(pid); if (pid <= 0) {
return {};
}
return getProcessPathFromPid(pid);
}
QStringList GetProcessPathsFromName(const QString &name)
{
QStringList paths;
const std::string nameStr = name.toStdString();
DIR *procDir = opendir("/proc");
if (!procDir) {
return paths;
}
struct dirent *entry;
while ((entry = readdir(procDir)) != nullptr) {
bool isPid = (entry->d_name[0] != '\0');
for (const char *c = entry->d_name; *c; c++) {
if (!isdigit(*c)) {
isPid = false;
break;
}
}
if (!isPid) {
continue;
}
std::string pid = entry->d_name;
std::string commPath = "/proc/" + pid + "/comm";
std::ifstream commFile(commPath);
if (!commFile) {
continue;
}
std::string comm;
std::getline(commFile, comm);
if (comm != nameStr) {
continue;
}
std::string path = getProcessPathFromPid(std::stol(pid));
if (path.empty()) {
continue;
}
QString qPath = QString::fromStdString(path);
if (!paths.contains(qPath)) {
paths.append(qPath);
}
}
closedir(procDir);
return paths;
} }
bool IsInFocus(const QString &executable) bool IsInFocus(const QString &executable)
{ {
std::string current; const auto current = GetForegroundProcessName();
GetForegroundProcessName(current);
// True if executable switch equals current window // True if executable switch equals current window
bool equals = (executable.toStdString() == current); bool equals = (executable.toStdString() == current);

View File

@@ -12,35 +12,82 @@
namespace advss { namespace advss {
std::mutex FocusNotifier::_mutex;
int FocusNotifier::activePID = -1; int FocusNotifier::activePID = -1;
std::string FocusNotifier::activeTitle = {}; std::string FocusNotifier::activeTitle = {};
std::map<std::string, std::pair<int, std::string>> FocusNotifier::_windows;
int FocusNotifier::getActiveWindowPID() int FocusNotifier::getActiveWindowPID()
{ {
std::lock_guard<std::mutex> lock(_mutex);
return activePID; return activePID;
} }
std::string FocusNotifier::getActiveWindowTitle() std::string FocusNotifier::getActiveWindowTitle()
{ {
std::lock_guard<std::mutex> lock(_mutex);
return activeTitle; return activeTitle;
} }
std::vector<std::pair<int, std::string>> FocusNotifier::getWindowList()
{
std::lock_guard<std::mutex> lock(_mutex);
std::vector<std::pair<int, std::string>> result;
result.reserve(_windows.size());
for (const auto &entry : _windows) {
result.emplace_back(entry.second);
}
return result;
}
void FocusNotifier::focusChanged(const int pid) void FocusNotifier::focusChanged(const int pid)
{ {
std::lock_guard<std::mutex> lock(_mutex);
activePID = pid; activePID = pid;
} }
void FocusNotifier::focusTitle(const QString &title) void FocusNotifier::focusTitle(const QString &title)
{ {
std::lock_guard<std::mutex> lock(_mutex);
activeTitle = title.toStdString(); activeTitle = title.toStdString();
} }
void FocusNotifier::focusUpdate(const int pid, const QString &title)
{
std::lock_guard<std::mutex> lock(_mutex);
activePID = pid;
activeTitle = title.toStdString();
}
void FocusNotifier::windowAdded(const QString &id, const int pid,
const QString &title)
{
std::lock_guard<std::mutex> lock(_mutex);
_windows[id.toStdString()] = {pid, title.toStdString()};
}
void FocusNotifier::windowRemoved(const QString &id)
{
std::lock_guard<std::mutex> lock(_mutex);
_windows.erase(id.toStdString());
}
void FocusNotifier::windowTitleChanged(const QString &id, const QString &title)
{
std::lock_guard<std::mutex> lock(_mutex);
auto it = _windows.find(id.toStdString());
if (it != _windows.end()) {
it->second.second = title.toStdString();
}
}
bool isKWinAvailable() bool isKWinAvailable()
{ {
const QDBusConnectionInterface *interface = const QDBusConnectionInterface *interface =
QDBusConnection::sessionBus().interface(); QDBusConnection::sessionBus().interface();
if (!interface) if (!interface) {
return false; return false;
}
const QStringList services = const QStringList services =
interface->registeredServiceNames().value(); interface->registeredServiceNames().value();
@@ -53,25 +100,32 @@ bool startKWinScript(QString &scriptObjectPath)
"/tmp/AdvancedSceneSwitcher/KWinFocusNotifier.js"; "/tmp/AdvancedSceneSwitcher/KWinFocusNotifier.js";
const QString script = const QString script =
R"(workspace.windowActivated.connect(function(client) { R"(var adss = "com.github.AdvancedSceneSwitcher";
if (!client) return; var adssPath = "/com/github/AdvancedSceneSwitcher";
if (!client.pid) return;
if (!client.caption) return;
callDBus( function trackWindow(window) {
"com.github.AdvancedSceneSwitcher", var id = window.internalId.toString();
"/com/github/AdvancedSceneSwitcher", callDBus(adss, adssPath, adss, "windowAdded", id, window.pid, window.caption);
"com.github.AdvancedSceneSwitcher", window.captionChanged.connect(function() {
"focusChanged", callDBus(adss, adssPath, adss, "windowTitleChanged", id, window.caption);
client.pid });
); }
callDBus(
"com.github.AdvancedSceneSwitcher", var windows = workspace.windowList();
"/com/github/AdvancedSceneSwitcher", for (var i = 0; i < windows.length; i++) {
"com.github.AdvancedSceneSwitcher", trackWindow(windows[i]);
"focusTitle", }
client.caption
); workspace.windowAdded.connect(trackWindow);
workspace.windowRemoved.connect(function(window) {
callDBus(adss, adssPath, adss, "windowRemoved", window.internalId.toString());
});
workspace.windowActivated.connect(function(client) {
callDBus(adss, adssPath, adss, "focusUpdate",
client ? client.pid : 0,
client ? client.caption : "");
}))"; }))";
if (const QDir dir; !dir.mkpath(QFileInfo(scriptPath).absolutePath())) { if (const QDir dir; !dir.mkpath(QFileInfo(scriptPath).absolutePath())) {

View File

@@ -2,7 +2,11 @@
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <map>
#include <mutex>
#include <string> #include <string>
#include <utility>
#include <vector>
namespace advss { namespace advss {
@@ -10,18 +14,27 @@ class FocusNotifier final : public QObject {
Q_OBJECT Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "com.github.AdvancedSceneSwitcher") Q_CLASSINFO("D-Bus Interface", "com.github.AdvancedSceneSwitcher")
static std::mutex _mutex;
static int activePID; static int activePID;
static std::string activeTitle; static std::string activeTitle;
// Maps KWin internal window ID to {pid, title}
static std::map<std::string, std::pair<int, std::string>> _windows;
public: public:
using QObject::QObject; using QObject::QObject;
static int getActiveWindowPID(); static int getActiveWindowPID();
static std::string getActiveWindowTitle(); static std::string getActiveWindowTitle();
static std::vector<std::pair<int, std::string>> getWindowList();
public slots: public slots:
void focusChanged(const int pid); void focusChanged(const int pid);
void focusTitle(const QString &title); void focusTitle(const QString &title);
void focusUpdate(const int pid, const QString &title);
void windowAdded(const QString &id, const int pid,
const QString &title);
void windowRemoved(const QString &id);
void windowTitleChanged(const QString &id, const QString &title);
}; };
bool isKWinAvailable(); bool isKWinAvailable();

View File

@@ -1,4 +1,5 @@
#include "macro-action-edit.hpp" #include "macro-action-edit.hpp"
#include "macro-undo-redo.hpp"
#include "advanced-scene-switcher.hpp" #include "advanced-scene-switcher.hpp"
#include "macro-helpers.hpp" #include "macro-helpers.hpp"
#include "macro-settings.hpp" #include "macro-settings.hpp"
@@ -28,8 +29,7 @@ static inline void populateActionSelection(QComboBox *list)
} }
MacroActionEdit::MacroActionEdit(QWidget *parent, MacroActionEdit::MacroActionEdit(QWidget *parent,
std::shared_ptr<MacroAction> *entryData, std::shared_ptr<MacroAction> *entryData)
const std::string &id)
: MacroSegmentEdit(parent), : MacroSegmentEdit(parent),
_actionSelection(new FilterComboBox()), _actionSelection(new FilterComboBox()),
_enable(new SwitchButton()), _enable(new SwitchButton()),
@@ -50,10 +50,12 @@ MacroActionEdit::MacroActionEdit(QWidget *parent,
_section->AddHeaderWidget(_enable); _section->AddHeaderWidget(_enable);
_section->AddHeaderWidget(_actionSelection); _section->AddHeaderWidget(_actionSelection);
_section->AddHeaderWidget(_headerInfo); _section->AddHeaderWidget(_headerInfo);
_section->AddHeaderWidget(_varMappingToggle);
auto actionLayout = new QVBoxLayout; auto actionLayout = new QVBoxLayout;
actionLayout->setContentsMargins({7, 7, 7, 7}); actionLayout->setContentsMargins({7, 7, 7, 7});
actionLayout->addWidget(_section); actionLayout->addWidget(_section);
actionLayout->addWidget(_outputMappings);
_contentLayout->addLayout(actionLayout); _contentLayout->addLayout(actionLayout);
auto mainLayout = new QHBoxLayout; auto mainLayout = new QHBoxLayout;
@@ -62,7 +64,6 @@ MacroActionEdit::MacroActionEdit(QWidget *parent,
mainLayout->addWidget(_frame); mainLayout->addWidget(_frame);
setLayout(mainLayout); setLayout(mainLayout);
_entryData = entryData;
SetupWidgets(true); SetupWidgets(true);
actionStateTimer->start(300); actionStateTimer->start(300);
@@ -91,7 +92,10 @@ void MacroActionEdit::SetupWidgets(bool basicSetup)
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData); auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)), QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &))); this, SLOT(HeaderInfoChanged(const QString &)));
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
SLOT(ShowVariableMappings(bool)));
_section->SetContent(widget, (*_entryData)->GetCollapsed()); _section->SetContent(widget, (*_entryData)->GetCollapsed());
SetupVarMappings((*_entryData).get());
SetFocusPolicyOfWidgets(); SetFocusPolicyOfWidgets();
_allWidgetsAreSetup = true; _allWidgetsAreSetup = true;
@@ -108,21 +112,57 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
return; return;
} }
Macro *const parentMacro =
(*_entryData)->GetMacro()->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
}
const std::string oldId = (*_entryData)->GetId();
OBSDataAutoRelease oldData = obs_data_create();
(*_entryData)->Save(oldData);
HeaderInfoChanged(""); HeaderInfoChanged("");
auto idx = _entryData->get()->GetIndex(); auto idx = _entryData->get()->GetIndex();
auto macro = _entryData->get()->GetMacro(); auto macro = _entryData->get()->GetMacro();
// Determine whether this is an action or else-action before resetting.
const auto *rawPtr = (*_entryData).get();
const auto &elseActions = macro->ElseActions();
const bool isElse = std::any_of(elseActions.begin(), elseActions.end(),
[rawPtr](const auto &a) {
return a.get() == rawPtr;
});
const auto segmentType = isElse ? MacroEdit::SegmentType::ELSE_ACTION
: MacroEdit::SegmentType::ACTION;
{ {
auto lock = LockContext(); auto lock = LockContext();
_entryData->reset(); _entryData->reset();
*_entryData = MacroActionFactory::Create(id, macro); *_entryData = MacroActionFactory::Create(id, macro);
(*_entryData)->SetIndex(idx); (*_entryData)->SetIndex(idx);
(*_entryData)->PostLoad(); (*_entryData)->PostLoad();
RunPostLoadSteps(); RunAndClearPostLoadSteps();
}
OBSDataAutoRelease newData = obs_data_create();
(*_entryData)->Save(newData);
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
} else {
RegisterSegmentTypeChangeUndoRedo(macro, segmentType, idx,
oldId, oldData, 0, id,
newData, 0);
} }
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData); auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)), QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &))); this, SLOT(HeaderInfoChanged(const QString &)));
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
SLOT(ShowVariableMappings(bool)));
_section->SetContent(widget); _section->SetContent(widget);
SetupVarMappings((*_entryData).get());
SetFocusPolicyOfWidgets(); SetFocusPolicyOfWidgets();
} }
@@ -158,4 +198,9 @@ std::shared_ptr<MacroSegment> MacroActionEdit::Data() const
return *_entryData; return *_entryData;
} }
void MacroActionEdit::FocusTypeSelection()
{
_actionSelection->setFocus();
}
} // namespace advss } // namespace advss

View File

@@ -13,12 +13,11 @@ class MacroActionEdit : public MacroSegmentEdit {
Q_OBJECT Q_OBJECT
public: public:
MacroActionEdit( MacroActionEdit(QWidget *parent = nullptr,
QWidget *parent = nullptr, std::shared_ptr<MacroAction> * = nullptr);
std::shared_ptr<MacroAction> * = nullptr,
const std::string &id = MacroAction::GetDefaultID().data());
void SetupWidgets(bool basicSetup = false); void SetupWidgets(bool basicSetup = false);
void SetEntryData(std::shared_ptr<MacroAction> *); void SetEntryData(std::shared_ptr<MacroAction> *);
void FocusTypeSelection() override;
private slots: private slots:
void ActionSelectionChanged(const QString &text); void ActionSelectionChanged(const QString &text);

View File

@@ -0,0 +1,197 @@
#include "macro-action-loop.hpp"
#include "layout-helpers.hpp"
#include "macro-action-factory.hpp"
#include "macro.hpp"
namespace advss {
const std::string MacroActionLoop::id = "loop";
bool MacroActionLoop::_registered = MacroActionFactory::Register(
MacroActionLoop::id,
{MacroActionLoop::Create, MacroActionLoopEdit::Create,
"AdvSceneSwitcher.action.loop"});
bool MacroActionLoop::PerformAction()
{
int iterations = 0;
const int maxIterations = _maxIterations;
Macro *parentMacro = GetMacro();
if (maxIterations <= 0) {
blog(LOG_WARNING,
"loop action has invalid max iterations value of %d",
maxIterations);
return true;
}
while (_loopMacro->CheckConditions()) {
if (iterations >= maxIterations) {
blog(LOG_WARNING,
"loop action reached iteration limit of %d",
maxIterations);
break;
}
ablog(LOG_INFO, "loop iteration %d", iterations);
if (!_loopMacro->PerformActions(true, false, true)) {
break;
}
++iterations;
if (parentMacro && parentMacro->GetStop()) {
break;
}
}
ablog(LOG_INFO, "loop completed after %d iteration(s)", iterations);
SetTempVarValue("count", std::to_string(iterations));
return true;
}
void MacroActionLoop::LogAction() const
{
ablog(LOG_INFO, "running loop (max %d iterations)",
_maxIterations.GetValue());
}
bool MacroActionLoop::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
_maxIterations.Save(obj, "maxIterations");
OBSDataAutoRelease loopMacroData = obs_data_create();
_loopMacro->Save(loopMacroData);
obs_data_set_obj(obj, "loopMacro", loopMacroData);
obs_data_set_int(obj, "customWidgetHeight", _customWidgetHeight);
return true;
}
bool MacroActionLoop::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_maxIterations.Load(obj, "maxIterations");
if (obs_data_has_user_value(obj, "loopMacro")) {
OBSDataAutoRelease loopMacroData =
obs_data_get_obj(obj, "loopMacro");
_loopMacro = std::make_shared<Macro>();
_loopMacro->Load(loopMacroData);
}
_customWidgetHeight = obs_data_get_int(obj, "customWidgetHeight");
return true;
}
bool MacroActionLoop::PostLoad()
{
MacroAction::PostLoad();
_loopMacro->PostLoad();
_loopMacro->SetActionTriggerMode(Macro::ActionTriggerMode::ALWAYS);
_loopMacro->SetNestedParentMacro(GetMacro());
return true;
}
std::shared_ptr<MacroAction> MacroActionLoop::Create(Macro *m)
{
return std::make_shared<MacroActionLoop>(m);
}
std::shared_ptr<MacroAction> MacroActionLoop::Copy() const
{
auto copy = std::make_shared<MacroActionLoop>(*this);
OBSDataAutoRelease data = obs_data_create();
_loopMacro->Save(data);
copy->_loopMacro = std::make_shared<Macro>();
copy->_loopMacro->Load(data);
copy->_loopMacro->PostLoad();
return copy;
}
void MacroActionLoop::ResolveVariablesToFixedValues()
{
_maxIterations.ResolveVariables();
for (auto &action : _loopMacro->Actions()) {
action->ResolveVariablesToFixedValues();
}
}
void MacroActionLoop::SetupTempVars()
{
MacroAction::SetupTempVars();
AddTempvar("count",
obs_module_text("AdvSceneSwitcher.tempVar.loop.count"),
obs_module_text(
"AdvSceneSwitcher.tempVar.loop.count.description"));
}
MacroActionLoopEdit::MacroActionLoopEdit(
QWidget *parent, std::shared_ptr<MacroActionLoop> entryData)
: ResizableWidget(parent),
_macroEdit(new MacroEdit(
this, QStringList()
<< "AdvSceneSwitcher.action.loop.conditionHelp"
<< "AdvSceneSwitcher.action.loop.actionHelp"
<< "")),
_maxIterations(new VariableSpinBox())
{
_maxIterations->setMinimum(1);
_maxIterations->setMaximum(10000000);
QWidget::connect(
_maxIterations,
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this, SLOT(MaxIterationsChanged(const NumberVariable<int> &)));
auto controlsLayout = new QHBoxLayout();
controlsLayout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.action.loop.maxIterations")));
controlsLayout->addWidget(_maxIterations);
controlsLayout->addStretch();
auto layout = new QVBoxLayout();
layout->addLayout(controlsLayout);
layout->addWidget(_macroEdit);
setLayout(layout);
_macroEdit->HideElseSection();
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
MacroActionLoopEdit::~MacroActionLoopEdit()
{
if (!_entryData) {
return;
}
_entryData->_customWidgetHeight = GetCustomHeight();
_macroEdit->SetMacro({});
}
void MacroActionLoopEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_maxIterations->SetValue(_entryData->_maxIterations);
_macroEdit->SetMacro(_entryData->_loopMacro);
if (_macroEdit->IsEmpty()) {
_macroEdit->ShowAllMacroSections();
_entryData->_customWidgetHeight = 600;
}
SetResizingEnabled(true);
SetCustomHeight(_entryData->_customWidgetHeight);
adjustSize();
updateGeometry();
}
void MacroActionLoopEdit::MaxIterationsChanged(const NumberVariable<int> &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_maxIterations = value;
}
} // namespace advss

View File

@@ -0,0 +1,64 @@
#pragma once
#include "macro-action-edit.hpp"
#include "macro-edit.hpp"
#include "resizable-widget.hpp"
#include "variable-spinbox.hpp"
#include <QLabel>
#include <QVBoxLayout>
namespace advss {
class MacroActionLoop : public MacroAction {
public:
MacroActionLoop(Macro *m) : MacroAction(m) {}
bool PerformAction();
void LogAction() const;
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
bool PostLoad();
std::string GetId() const { return id; }
static std::shared_ptr<MacroAction> Create(Macro *m);
std::shared_ptr<MacroAction> Copy() const;
void ResolveVariablesToFixedValues();
std::shared_ptr<Macro> _loopMacro = std::make_shared<Macro>();
IntVariable _maxIterations = 100;
int _customWidgetHeight = 0;
private:
void SetupTempVars();
static bool _registered;
static const std::string id;
};
class MacroActionLoopEdit : public ResizableWidget {
Q_OBJECT
public:
MacroActionLoopEdit(
QWidget *parent,
std::shared_ptr<MacroActionLoop> entryData = nullptr);
~MacroActionLoopEdit();
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionLoopEdit(
parent,
std::dynamic_pointer_cast<MacroActionLoop>(action));
}
private slots:
void MaxIterationsChanged(const NumberVariable<int> &value);
private:
MacroEdit *_macroEdit;
VariableSpinBox *_maxIterations;
std::shared_ptr<MacroActionLoop> _entryData;
bool _loading = true;
};
} // namespace advss

View File

@@ -1,8 +1,14 @@
#include "macro-action-macro.hpp" #include "macro-action-macro.hpp"
#include "condition-logic.hpp"
#include "help-icon.hpp" #include "help-icon.hpp"
#include "layout-helpers.hpp" #include "layout-helpers.hpp"
#include "macro-action-factory.hpp"
#include "macro-helpers.hpp"
#include "macro.hpp" #include "macro.hpp"
#include <chrono>
namespace advss { namespace advss {
const std::string MacroActionMacro::id = "macro"; const std::string MacroActionMacro::id = "macro";
@@ -12,6 +18,74 @@ bool MacroActionMacro::_registered = MacroActionFactory::Register(
{MacroActionMacro::Create, MacroActionMacroEdit::Create, {MacroActionMacro::Create, MacroActionMacroEdit::Create,
"AdvSceneSwitcher.action.macro"}); "AdvSceneSwitcher.action.macro"});
void MacroActionMacro::AdjustActionState(Macro *macro) const
{
const auto &macroActions = _useElseSection ? macro->ElseActions()
: macro->Actions();
std::vector<std::shared_ptr<MacroAction>> actionsToModify;
switch (_actionSelectionType) {
case SelectionType::INDEX: {
const bool isValidAction =
(_useElseSection &&
IsValidElseActionIndex(macro, _actionIndex - 1)) ||
(!_useElseSection &&
IsValidActionIndex(macro, _actionIndex - 1));
if (isValidAction) {
actionsToModify.emplace_back(
macroActions.at(_actionIndex - 1));
}
break;
}
case SelectionType::LABEL:
for (const auto &action : macroActions) {
if (!action->GetUseCustomLabel()) {
continue;
}
const auto label = action->GetCustomLabel();
if (_regex.Enabled()) {
if (_regex.Matches(label, _label)) {
actionsToModify.emplace_back(action);
}
continue;
}
if (label == std::string(_label)) {
actionsToModify.emplace_back(action);
}
}
break;
case SelectionType::ID:
for (const auto &action : macroActions) {
if (action->GetId() == _actionId) {
actionsToModify.emplace_back(action);
}
}
break;
default:
break;
}
for (const auto &action : actionsToModify) {
switch (_action) {
case Action::DISABLE_ACTION:
action->SetEnabled(false);
break;
case Action::ENABLE_ACTION:
action->SetEnabled(true);
break;
case Action::TOGGLE_ACTION:
action->SetEnabled(!action->Enabled());
break;
default:
break;
}
}
}
bool MacroActionMacro::PerformAction() bool MacroActionMacro::PerformAction()
{ {
if (_action == Action::NESTED_MACRO) { if (_action == Action::NESTED_MACRO) {
@@ -24,47 +98,85 @@ bool MacroActionMacro::PerformAction()
return true; return true;
} }
switch (_action) { const auto performActionForMacro = [this](Macro *macro) {
case Action::PAUSE: switch (_action) {
macro->SetPaused(); case Action::PAUSE:
break; macro->SetPaused();
case Action::UNPAUSE: break;
macro->SetPaused(false); case Action::UNPAUSE:
break; macro->SetPaused(false);
case Action::TOGGLE_PAUSE: break;
macro->SetPaused(!macro->Paused()); case Action::TOGGLE_PAUSE:
break; macro->SetPaused(!macro->Paused());
case Action::RESET_COUNTER: break;
macro->ResetRunCount(); case Action::RESET_COUNTER:
break; macro->ResetRunCount();
case Action::RUN_ACTIONS: break;
RunActions(macro.get()); case Action::RUN_ACTIONS:
break; RunActions(macro);
case Action::STOP: break;
macro->Stop(); case Action::STOP:
break; macro->Stop();
case Action::DISABLE_ACTION: break;
if (IsValidMacroSegmentIndex(macro.get(), _actionIndex - 1, case Action::DISABLE_ACTION:
false)) { case Action::ENABLE_ACTION:
macro->Actions().at(_actionIndex - 1)->SetEnabled(false); case Action::TOGGLE_ACTION:
AdjustActionState(macro);
break;
case Action::RUN_MACRO: {
if (_runOptions.skipWhenPaused && macro->Paused()) {
break;
}
const bool conditionsMatched =
macro->CheckConditions(true);
macro->PerformActions(conditionsMatched, false, true);
break;
} }
break; case Action::GET_INFO: {
case Action::ENABLE_ACTION: SetTempVarValue(
if (IsValidMacroSegmentIndex(macro.get(), _actionIndex - 1, "conditionCount",
false)) { std::to_string(macro->Conditions().size()));
macro->Actions().at(_actionIndex - 1)->SetEnabled(true); SetTempVarValue(
"actionCount",
std::to_string(macro->Actions().size()));
SetTempVarValue(
"elseActionCount",
std::to_string(macro->ElseActions().size()));
SetTempVarValue("paused",
macro->Paused() ? "true" : "false");
SetTempVarValue("runCount",
std::to_string(macro->RunCount()));
const auto lastRun = macro->GetLastExecutionTime();
const bool hasRun =
lastRun.time_since_epoch().count() != 0;
const auto secondsSinceLastRun =
hasRun ? std::chrono::duration_cast<
std::chrono::seconds>(
std::chrono::
high_resolution_clock::
now() -
lastRun)
.count()
: -1;
SetTempVarValue("secondsSinceLastRun",
std::to_string(secondsSinceLastRun));
break;
} }
break; default:
case Action::TOGGLE_ACTION: break;
if (IsValidMacroSegmentIndex(macro.get(), _actionIndex - 1,
false)) {
auto action = macro->Actions().at(_actionIndex - 1);
action->SetEnabled(!action->Enabled());
} }
break; };
default:
break; if (!IsGroupMacro(macro.get())) {
performActionForMacro(macro.get());
return true;
} }
auto macros = GetGroupMacroEntries(macro.get());
for (const auto &macro : macros) {
performActionForMacro(macro.get());
}
return true; return true;
} }
@@ -107,6 +219,12 @@ void MacroActionMacro::LogAction() const
case Action::NESTED_MACRO: case Action::NESTED_MACRO:
ablog(LOG_INFO, "run nested macro"); ablog(LOG_INFO, "run nested macro");
break; break;
case Action::GET_INFO:
ablog(LOG_INFO, "get info for \"%s\"", macro->Name().c_str());
break;
case Action::RUN_MACRO:
ablog(LOG_INFO, "run macro \"%s\"", macro->Name().c_str());
break;
default: default:
break; break;
} }
@@ -115,9 +233,14 @@ void MacroActionMacro::LogAction() const
bool MacroActionMacro::Save(obs_data_t *obj) const bool MacroActionMacro::Save(obs_data_t *obj) const
{ {
MacroAction::Save(obj); MacroAction::Save(obj);
_macro.Save(obj);
_actionIndex.Save(obj, "actionIndex");
obs_data_set_int(obj, "action", static_cast<int>(_action)); obs_data_set_int(obj, "action", static_cast<int>(_action));
_macro.Save(obj);
obs_data_set_int(obj, "actionSelectionType",
static_cast<int>(_actionSelectionType));
_actionIndex.Save(obj, "actionIndex");
_label.Save(obj, "label");
obs_data_set_string(obj, "actionId", _actionId.c_str());
_regex.Save(obj);
_runOptions.Save(obj); _runOptions.Save(obj);
OBSDataAutoRelease nestedMacroData = obs_data_create(); OBSDataAutoRelease nestedMacroData = obs_data_create();
_nestedMacro->Save(nestedMacroData); _nestedMacro->Save(nestedMacroData);
@@ -129,15 +252,21 @@ bool MacroActionMacro::Save(obs_data_t *obj) const
bool MacroActionMacro::Load(obs_data_t *obj) bool MacroActionMacro::Load(obs_data_t *obj)
{ {
MacroAction::Load(obj); MacroAction::Load(obj);
SetAction(static_cast<MacroActionMacro::Action>(
obs_data_get_int(obj, "action")));
_macro.Load(obj); _macro.Load(obj);
_actionSelectionType = static_cast<SelectionType>(
obs_data_get_int(obj, "actionSelectionType"));
_actionIndex.Load(obj, "actionIndex"); _actionIndex.Load(obj, "actionIndex");
_action = static_cast<MacroActionMacro::Action>( _label.Load(obj, "label");
obs_data_get_int(obj, "action")); _actionId = obs_data_get_string(obj, "actionId");
_regex.Load(obj);
_runOptions.Load(obj); _runOptions.Load(obj);
if (obs_data_has_user_value(obj, "nestedMacro")) { if (obs_data_has_user_value(obj, "nestedMacro")) {
OBSDataAutoRelease nestedMacroData = OBSDataAutoRelease nestedMacroData =
obs_data_get_obj(obj, "nestedMacro"); obs_data_get_obj(obj, "nestedMacro");
_nestedMacro = std::make_shared<Macro>();
_nestedMacro->Load(nestedMacroData); _nestedMacro->Load(nestedMacroData);
} }
@@ -152,6 +281,7 @@ bool MacroActionMacro::PostLoad()
MacroAction::PostLoad(); MacroAction::PostLoad();
_runOptions.macro.PostLoad(); _runOptions.macro.PostLoad();
_nestedMacro->PostLoad(); _nestedMacro->PostLoad();
_nestedMacro->SetNestedParentMacro(GetMacro());
return true; return true;
} }
@@ -183,6 +313,16 @@ std::shared_ptr<MacroAction> MacroActionMacro::Copy() const
void MacroActionMacro::ResolveVariablesToFixedValues() void MacroActionMacro::ResolveVariablesToFixedValues()
{ {
_actionIndex.ResolveVariables(); _actionIndex.ResolveVariables();
_label.ResolveVariables();
if (_action == Action::NESTED_MACRO) {
for (auto &action : _nestedMacro->Actions()) {
action->ResolveVariablesToFixedValues();
}
for (auto &action : _nestedMacro->ElseActions()) {
action->ResolveVariablesToFixedValues();
}
}
} }
static void runActionsHelper(Macro *macro, bool runElseActions, bool setInputs, static void runActionsHelper(Macro *macro, bool runElseActions, bool setInputs,
@@ -224,6 +364,56 @@ void MacroActionMacro::RunActions(Macro *actionMacro) const
} }
} }
void MacroActionMacro::SetAction(Action action)
{
_action = action;
SetupTempVars();
}
void MacroActionMacro::SetupTempVars()
{
MacroAction::SetupTempVars();
if (_action != Action::GET_INFO) {
return;
}
AddTempvar(
"conditionCount",
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.info.conditionCount"),
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.info.conditionCount.description"));
AddTempvar(
"actionCount",
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.info.actionCount"),
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.info.actionCount.description"));
AddTempvar(
"elseActionCount",
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.info.elseActionCount"),
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.info.elseActionCount.description"));
AddTempvar(
"paused",
obs_module_text("AdvSceneSwitcher.tempVar.macro.info.paused"),
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.info.paused.description"));
AddTempvar(
"runCount",
obs_module_text("AdvSceneSwitcher.tempVar.macro.info.runCount"),
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.info.runCount.description"));
AddTempvar(
"secondsSinceLastRun",
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.info.secondsSinceLastRun"),
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.info.secondsSinceLastRun.description"));
}
static void populateActionSelection(QComboBox *list) static void populateActionSelection(QComboBox *list)
{ {
static const std::vector<std::pair<MacroActionMacro::Action, std::string>> static const std::vector<std::pair<MacroActionMacro::Action, std::string>>
@@ -239,7 +429,9 @@ static void populateActionSelection(QComboBox *list)
{MacroActionMacro::Action::NESTED_MACRO, {MacroActionMacro::Action::NESTED_MACRO,
"AdvSceneSwitcher.action.macro.type.nestedMacro"}, "AdvSceneSwitcher.action.macro.type.nestedMacro"},
{MacroActionMacro::Action::RUN_ACTIONS, {MacroActionMacro::Action::RUN_ACTIONS,
"AdvSceneSwitcher.action.macro.type.run"}, "AdvSceneSwitcher.action.macro.type.runActions"},
{MacroActionMacro::Action::RUN_MACRO,
"AdvSceneSwitcher.action.macro.type.runMacro"},
{MacroActionMacro::Action::STOP, {MacroActionMacro::Action::STOP,
"AdvSceneSwitcher.action.macro.type.stop"}, "AdvSceneSwitcher.action.macro.type.stop"},
{MacroActionMacro::Action::DISABLE_ACTION, {MacroActionMacro::Action::DISABLE_ACTION,
@@ -248,6 +440,8 @@ static void populateActionSelection(QComboBox *list)
"AdvSceneSwitcher.action.macro.type.enableAction"}, "AdvSceneSwitcher.action.macro.type.enableAction"},
{MacroActionMacro::Action::TOGGLE_ACTION, {MacroActionMacro::Action::TOGGLE_ACTION,
"AdvSceneSwitcher.action.macro.type.toggleAction"}, "AdvSceneSwitcher.action.macro.type.toggleAction"},
{MacroActionMacro::Action::GET_INFO,
"AdvSceneSwitcher.action.macro.type.getInfo"},
}; };
for (const auto &[value, name] : actions) { for (const auto &[value, name] : actions) {
@@ -266,7 +460,7 @@ static void populateConditionBehaviorSelection(QComboBox *list)
"AdvSceneSwitcher.action.macro.type.run.conditions.false")); "AdvSceneSwitcher.action.macro.type.run.conditions.false"));
} }
static void populateActionTypeSelection(QComboBox *list) static void populateActionSectionSelection(QComboBox *list)
{ {
list->addItem(obs_module_text( list->addItem(obs_module_text(
"AdvSceneSwitcher.action.macro.type.run.actionType.regular")); "AdvSceneSwitcher.action.macro.type.run.actionType.regular"));
@@ -274,21 +468,53 @@ static void populateActionTypeSelection(QComboBox *list)
"AdvSceneSwitcher.action.macro.type.run.actionType.else")); "AdvSceneSwitcher.action.macro.type.run.actionType.else"));
} }
static void populateActionTypes(QComboBox *list)
{
for (const auto &[id, info] : MacroActionFactory::GetActionTypes()) {
list->addItem(obs_module_text(info._name.c_str()),
QString::fromStdString(id));
}
}
static void populateActionSelectionTypes(QComboBox *list)
{
list->addItem(
obs_module_text(
"AdvSceneSwitcher.action.macro.actionSelectionType.index"),
static_cast<int>(MacroActionMacro::SelectionType::INDEX));
list->addItem(
obs_module_text(
"AdvSceneSwitcher.action.macro.actionSelectionType.label"),
static_cast<int>(MacroActionMacro::SelectionType::LABEL));
list->addItem(
obs_module_text(
"AdvSceneSwitcher.action.macro.actionSelectionType.id"),
static_cast<int>(MacroActionMacro::SelectionType::ID));
}
MacroActionMacroEdit::MacroActionMacroEdit( MacroActionMacroEdit::MacroActionMacroEdit(
QWidget *parent, std::shared_ptr<MacroActionMacro> entryData) QWidget *parent, std::shared_ptr<MacroActionMacro> entryData)
: ResizableWidget(parent), : ResizableWidget(parent),
_actions(new QComboBox()),
_macros(new MacroSelection(parent)), _macros(new MacroSelection(parent)),
_actionSelectionType(new QComboBox(this)),
_actionIndex(new MacroSegmentSelection( _actionIndex(new MacroSegmentSelection(
this, MacroSegmentSelection::Type::ACTION)), this, MacroSegmentSelection::Type::ACTION)),
_actions(new QComboBox()), _label(new VariableLineEdit(this)),
_actionTypes(new FilterComboBox(this)),
_regex(new RegexConfigWidget(this)),
_conditionMacros(new MacroSelection(parent)), _conditionMacros(new MacroSelection(parent)),
_conditionBehaviors(new QComboBox()), _conditionBehaviors(new QComboBox()),
_reevaluateConditionState(new QCheckBox( _reevaluateConditionState(new QCheckBox(
obs_module_text("AdvSceneSwitcher.action.macro.type.run." obs_module_text("AdvSceneSwitcher.action.macro.type.run."
"updateConditionMatchState"))), "updateConditionMatchState"))),
_actionTypes(new QComboBox()), _actionSections(new QComboBox(this)),
_skipWhenPaused(new QCheckBox(obs_module_text( _skipWhenPaused(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.action.macro.type.run.skipWhenPaused"))), "AdvSceneSwitcher.action.macro.type.run.skipWhenPaused"))),
_noConditionsWarning(new QLabel(obs_module_text(
"AdvSceneSwitcher.action.macro.type.runMacro.noConditionsWarning"))),
_runMacroHelp(new HelpIcon(obs_module_text(
"AdvSceneSwitcher.action.macro.type.runMacro.help"))),
_setInputs(new QCheckBox(obs_module_text( _setInputs(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.action.macro.type.run.setInputs"))), "AdvSceneSwitcher.action.macro.type.run.setInputs"))),
_inputs(new MacroInputEdit()), _inputs(new MacroInputEdit()),
@@ -305,24 +531,36 @@ MacroActionMacroEdit::MacroActionMacroEdit(
{ {
populateActionSelection(_actions); populateActionSelection(_actions);
populateConditionBehaviorSelection(_conditionBehaviors); populateConditionBehaviorSelection(_conditionBehaviors);
populateActionTypeSelection(_actionTypes); populateActionSectionSelection(_actionSections);
populateActionSelectionTypes(_actionSelectionType);
populateActionTypes(_actionTypes);
_conditionMacros->HideSelectedMacro(); _conditionMacros->HideSelectedMacro();
_conditionMacros->HideGroups();
QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)), QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(MacroChanged(const QString &))); this, SLOT(MacroChanged(const QString &)));
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int))); SLOT(ActionChanged(int)));
QWidget::connect(_actionSelectionType, SIGNAL(currentIndexChanged(int)),
this, SLOT(ActionSelectionTypeChanged(int)));
QWidget::connect(_actionIndex, QWidget::connect(_actionIndex,
SIGNAL(SelectionChanged(const IntVariable &)), this, SIGNAL(SelectionChanged(const IntVariable &)), this,
SLOT(ActionIndexChanged(const IntVariable &))); SLOT(ActionIndexChanged(const IntVariable &)));
QWidget::connect(_label, SIGNAL(editingFinished()), this,
SLOT(LabelChanged()));
QWidget::connect(_actionTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionTypeChanged(int)));
QWidget::connect(_regex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(RegexChanged(const RegexConfig &)));
QWidget::connect(_conditionMacros, QWidget::connect(_conditionMacros,
SIGNAL(currentTextChanged(const QString &)), this, SIGNAL(currentTextChanged(const QString &)), this,
SLOT(ConditionMacroChanged(const QString &))); SLOT(ConditionMacroChanged(const QString &)));
QWidget::connect(_conditionBehaviors, SIGNAL(currentIndexChanged(int)), QWidget::connect(_conditionBehaviors, SIGNAL(currentIndexChanged(int)),
this, SLOT(ConditionBehaviorChanged(int))); this, SLOT(ConditionBehaviorChanged(int)));
QWidget::connect(_actionTypes, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_actionSections, SIGNAL(currentIndexChanged(int)),
SLOT(ActionTypeChanged(int))); this, SLOT(ActionSectionChanged(int)));
QWidget::connect(_skipWhenPaused, SIGNAL(stateChanged(int)), this, QWidget::connect(_skipWhenPaused, SIGNAL(stateChanged(int)), this,
SLOT(SkipWhenPausedChanged(int))); SLOT(SkipWhenPausedChanged(int)));
QWidget::connect(_setInputs, SIGNAL(stateChanged(int)), this, QWidget::connect(_setInputs, SIGNAL(stateChanged(int)), this,
@@ -350,6 +588,7 @@ MacroActionMacroEdit::MacroActionMacroEdit(
layout->addLayout(_setInputsLayout); layout->addLayout(_setInputsLayout);
layout->addWidget(_inputs); layout->addWidget(_inputs);
layout->addWidget(_skipWhenPaused); layout->addWidget(_skipWhenPaused);
layout->addWidget(_noConditionsWarning);
layout->addWidget(_nestedMacro); layout->addWidget(_nestedMacro);
setLayout(layout); setLayout(layout);
_entryData = entryData; _entryData = entryData;
@@ -375,17 +614,24 @@ void MacroActionMacroEdit::UpdateEntryData()
if (!_entryData) { if (!_entryData) {
return; return;
} }
_actions->setCurrentIndex( _actions->setCurrentIndex(
_actions->findData(static_cast<int>(_entryData->_action))); _actions->findData(static_cast<int>(_entryData->GetAction())));
_actionSelectionType->setCurrentIndex(_actionSelectionType->findData(
static_cast<int>(_entryData->_actionSelectionType)));
_actionIndex->SetValue(_entryData->_actionIndex); _actionIndex->SetValue(_entryData->_actionIndex);
_actionIndex->SetMacro(_entryData->_macro.GetMacro()); _actionIndex->SetMacro(_entryData->_macro.GetMacro());
_label->setText(_entryData->_label);
_actionTypes->setCurrentIndex(_actionTypes->findData(
QString::fromStdString(_entryData->_actionId)));
_regex->SetRegexConfig(_entryData->_regex);
_macros->SetCurrentMacro(_entryData->_macro); _macros->SetCurrentMacro(_entryData->_macro);
_conditionMacros->SetCurrentMacro(_entryData->_runOptions.macro); _conditionMacros->SetCurrentMacro(_entryData->_runOptions.macro);
_conditionBehaviors->setCurrentIndex( _conditionBehaviors->setCurrentIndex(
static_cast<int>(_entryData->_runOptions.logic)); static_cast<int>(_entryData->_runOptions.logic));
_reevaluateConditionState->setChecked( _reevaluateConditionState->setChecked(
_entryData->_runOptions.reevaluateConditionState); _entryData->_runOptions.reevaluateConditionState);
_actionTypes->setCurrentIndex( _actionSections->setCurrentIndex(
_entryData->_runOptions.runElseActions ? 1 : 0); _entryData->_runOptions.runElseActions ? 1 : 0);
_skipWhenPaused->setChecked(_entryData->_runOptions.skipWhenPaused); _skipWhenPaused->setChecked(_entryData->_runOptions.skipWhenPaused);
_setInputs->setChecked(_entryData->_runOptions.setInputs); _setInputs->setChecked(_entryData->_runOptions.setInputs);
@@ -419,8 +665,17 @@ void MacroActionMacroEdit::MacroChanged(const QString &text)
void MacroActionMacroEdit::ActionChanged(int idx) void MacroActionMacroEdit::ActionChanged(int idx)
{ {
GUARD_LOADING_AND_LOCK(); GUARD_LOADING_AND_LOCK();
_entryData->_action = static_cast<MacroActionMacro::Action>( _entryData->SetAction(static_cast<MacroActionMacro::Action>(
_actions->itemData(idx).toInt()); _actions->itemData(idx).toInt()));
SetWidgetVisibility();
}
void MacroActionMacroEdit::ActionSelectionTypeChanged(int idx)
{
GUARD_LOADING_AND_LOCK();
_entryData->_actionSelectionType =
static_cast<MacroActionMacro::SelectionType>(
_actionSelectionType->itemData(idx).toInt());
SetWidgetVisibility(); SetWidgetVisibility();
} }
@@ -430,6 +685,25 @@ void MacroActionMacroEdit::ActionIndexChanged(const IntVariable &value)
_entryData->_actionIndex = value; _entryData->_actionIndex = value;
} }
void MacroActionMacroEdit::LabelChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_label = _label->text().toStdString();
}
void MacroActionMacroEdit::ActionTypeChanged(int idx)
{
GUARD_LOADING_AND_LOCK();
_entryData->_actionId =
_actionTypes->itemData(idx).toString().toStdString();
}
void MacroActionMacroEdit::RegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_regex = regex;
}
void MacroActionMacroEdit::ConditionMacroChanged(const QString &text) void MacroActionMacroEdit::ConditionMacroChanged(const QString &text)
{ {
GUARD_LOADING_AND_LOCK(); GUARD_LOADING_AND_LOCK();
@@ -451,10 +725,13 @@ void MacroActionMacroEdit::ReevaluateConditionStateChanged(int value)
SetWidgetVisibility(); SetWidgetVisibility();
} }
void MacroActionMacroEdit::ActionTypeChanged(int value) void MacroActionMacroEdit::ActionSectionChanged(int useElse)
{ {
GUARD_LOADING_AND_LOCK(); GUARD_LOADING_AND_LOCK();
_entryData->_runOptions.runElseActions = value; _entryData->_runOptions.runElseActions = useElse;
_entryData->_useElseSection = useElse;
_actionIndex->SetType(useElse ? MacroSegmentSelection::Type::ELSE_ACTION
: MacroSegmentSelection::Type::ACTION);
} }
void MacroActionMacroEdit::SkipWhenPausedChanged(int value) void MacroActionMacroEdit::SkipWhenPausedChanged(int value)
@@ -481,9 +758,14 @@ void MacroActionMacroEdit::InputsChanged(const StringList &inputs)
void MacroActionMacroEdit::SetWidgetVisibility() void MacroActionMacroEdit::SetWidgetVisibility()
{ {
_entryLayout->removeWidget(_actions); _entryLayout->removeWidget(_actions);
_entryLayout->removeWidget(_runMacroHelp);
_entryLayout->removeWidget(_actionIndex); _entryLayout->removeWidget(_actionIndex);
_entryLayout->removeWidget(_macros); _entryLayout->removeWidget(_macros);
_entryLayout->removeWidget(_actionSections);
_entryLayout->removeWidget(_label);
_entryLayout->removeWidget(_regex);
_entryLayout->removeWidget(_actionTypes); _entryLayout->removeWidget(_actionTypes);
_entryLayout->removeWidget(_actionSelectionType);
_conditionLayout->removeWidget(_conditionBehaviors); _conditionLayout->removeWidget(_conditionBehaviors);
_conditionLayout->removeWidget(_conditionMacros); _conditionLayout->removeWidget(_conditionMacros);
@@ -492,21 +774,47 @@ void MacroActionMacroEdit::SetWidgetVisibility()
const std::unordered_map<std::string, QWidget *> placeholders = { const std::unordered_map<std::string, QWidget *> placeholders = {
{"{{actions}}", _actions}, {"{{actions}}", _actions},
{"{{runMacroHelp}}", _runMacroHelp},
{"{{actionIndex}}", _actionIndex}, {"{{actionIndex}}", _actionIndex},
{"{{macros}}", _macros}, {"{{macros}}", _macros},
{"{{actionTypes}}", _actionTypes}, {"{{actionSections}}", _actionSections},
{"{{conditionBehaviors}}", _conditionBehaviors}, {"{{conditionBehaviors}}", _conditionBehaviors},
{"{{conditionMacros}}", _conditionMacros}, {"{{conditionMacros}}", _conditionMacros},
{"{{actionSelectionType}}", _actionSelectionType},
{"{{label}}", _label},
{"{{regex}}", _regex},
{"{{actionTypes}}", _actionTypes},
}; };
PlaceWidgets( const auto action = _entryData->GetAction();
obs_module_text( const char *layoutText = "";
_entryData->_action == switch (action) {
MacroActionMacro::Action::RUN_ACTIONS case MacroActionMacro::Action::PAUSE:
? "AdvSceneSwitcher.action.macro.entry.run" case MacroActionMacro::Action::UNPAUSE:
: "AdvSceneSwitcher.action.macro.entry.other"), case MacroActionMacro::Action::TOGGLE_PAUSE:
_entryLayout, placeholders); case MacroActionMacro::Action::RESET_COUNTER:
case MacroActionMacro::Action::STOP:
case MacroActionMacro::Action::NESTED_MACRO:
case MacroActionMacro::Action::GET_INFO:
layoutText = "AdvSceneSwitcher.action.macro.layout.other";
break;
case MacroActionMacro::Action::RUN_MACRO:
layoutText = "AdvSceneSwitcher.action.macro.layout.runMacro";
break;
case MacroActionMacro::Action::RUN_ACTIONS:
layoutText = "AdvSceneSwitcher.action.macro.layout.run";
break;
case MacroActionMacro::Action::DISABLE_ACTION:
case MacroActionMacro::Action::ENABLE_ACTION:
case MacroActionMacro::Action::TOGGLE_ACTION:
layoutText = "AdvSceneSwitcher.action.macro.layout.actionState";
break;
default:
break;
}
PlaceWidgets(obs_module_text(layoutText), _entryLayout, placeholders);
if (_entryData->_runOptions.logic == if (_entryData->_runOptions.logic ==
MacroActionMacro::RunOptions::Logic::IGNORE_CONDITIONS) { MacroActionMacro::RunOptions::Logic::IGNORE_CONDITIONS) {
@@ -515,56 +823,75 @@ void MacroActionMacroEdit::SetWidgetVisibility()
} else { } else {
PlaceWidgets( PlaceWidgets(
obs_module_text( obs_module_text(
"AdvSceneSwitcher.action.macro.entry.run.condition"), "AdvSceneSwitcher.action.macro.layout.run.condition"),
_conditionLayout, placeholders); _conditionLayout, placeholders);
} }
if (_entryData->_action == MacroActionMacro::Action::RUN_ACTIONS || if (action == MacroActionMacro::Action::RUN_ACTIONS ||
_entryData->_action == MacroActionMacro::Action::STOP) { action == MacroActionMacro::Action::STOP ||
action == MacroActionMacro::Action::RUN_MACRO) {
_macros->HideSelectedMacro(); _macros->HideSelectedMacro();
} else { } else {
_macros->ShowAllMacros(); _macros->ShowAllMacros();
} }
const auto actionSelectionType = _entryData->_actionSelectionType;
const bool isModifyingActionState = const bool isModifyingActionState =
_entryData->_action == action == MacroActionMacro::Action::DISABLE_ACTION ||
MacroActionMacro::Action::DISABLE_ACTION || action == MacroActionMacro::Action::ENABLE_ACTION ||
_entryData->_action == action == MacroActionMacro::Action::TOGGLE_ACTION;
MacroActionMacro::Action::ENABLE_ACTION || _actionSelectionType->setVisible(isModifyingActionState);
_entryData->_action == MacroActionMacro::Action::TOGGLE_ACTION; _actionIndex->setVisible(
_actionIndex->setVisible(isModifyingActionState); isModifyingActionState &&
actionSelectionType == MacroActionMacro::SelectionType::INDEX);
_label->setVisible(isModifyingActionState &&
actionSelectionType ==
MacroActionMacro::SelectionType::LABEL);
_regex->setVisible(isModifyingActionState &&
actionSelectionType ==
MacroActionMacro::SelectionType::LABEL);
_actionTypes->setVisible(isModifyingActionState &&
actionSelectionType ==
MacroActionMacro::SelectionType::ID);
SetLayoutVisible(_conditionLayout, SetLayoutVisible(_conditionLayout,
_entryData->_action == action == MacroActionMacro::Action::RUN_ACTIONS);
MacroActionMacro::Action::RUN_ACTIONS);
const bool needsAdditionalConditionWidgets = const bool needsAdditionalConditionWidgets =
_entryData->_action == MacroActionMacro::Action::RUN_ACTIONS && action == MacroActionMacro::Action::RUN_ACTIONS &&
_entryData->_runOptions.logic != _entryData->_runOptions.logic !=
MacroActionMacro::RunOptions::Logic::IGNORE_CONDITIONS; MacroActionMacro::RunOptions::Logic::IGNORE_CONDITIONS;
_conditionMacros->setVisible(needsAdditionalConditionWidgets); _conditionMacros->setVisible(needsAdditionalConditionWidgets);
SetLayoutVisible(_reevaluateConditionStateLayout, SetLayoutVisible(_reevaluateConditionStateLayout,
needsAdditionalConditionWidgets); needsAdditionalConditionWidgets);
SetLayoutVisible(_setInputsLayout, SetLayoutVisible(_setInputsLayout,
_entryData->_action == action == MacroActionMacro::Action::RUN_ACTIONS);
MacroActionMacro::Action::RUN_ACTIONS); _inputs->setVisible(action == MacroActionMacro::Action::RUN_ACTIONS &&
_inputs->setVisible(_entryData->_action ==
MacroActionMacro::Action::RUN_ACTIONS &&
_entryData->_runOptions.setInputs); _entryData->_runOptions.setInputs);
HighlightMacroSettingsButton( HighlightMacroSettingsButton(
_entryData->_action == MacroActionMacro::Action::RUN_ACTIONS && action == MacroActionMacro::Action::RUN_ACTIONS &&
_entryData->_runOptions.setInputs && _entryData->_runOptions.setInputs &&
!_inputs->HasInputsToSet()); !_inputs->HasInputsToSet());
_actionTypes->setVisible(_entryData->_action == _actionSections->setVisible(
MacroActionMacro::Action::RUN_ACTIONS); action == MacroActionMacro::Action::RUN_ACTIONS ||
_skipWhenPaused->setVisible(_entryData->_action == isModifyingActionState);
MacroActionMacro::Action::RUN_ACTIONS); _skipWhenPaused->setVisible(
action == MacroActionMacro::Action::RUN_ACTIONS ||
action == MacroActionMacro::Action::RUN_MACRO);
_nestedMacro->setVisible(_entryData->_action == if (action == MacroActionMacro::Action::RUN_MACRO) {
auto macro = _entryData->_macro.GetMacro();
_noConditionsWarning->setVisible(!macro ||
macro->Conditions().empty());
} else {
_noConditionsWarning->setVisible(false);
}
_runMacroHelp->setVisible(action ==
MacroActionMacro::Action::RUN_MACRO);
_nestedMacro->setVisible(action ==
MacroActionMacro::Action::NESTED_MACRO); MacroActionMacro::Action::NESTED_MACRO);
_macros->setVisible(_entryData->_action != _macros->setVisible(action != MacroActionMacro::Action::NESTED_MACRO);
MacroActionMacro::Action::NESTED_MACRO); SetResizingEnabled(action == MacroActionMacro::Action::NESTED_MACRO);
SetResizingEnabled(_entryData->_action ==
MacroActionMacro::Action::NESTED_MACRO);
if (_nestedMacro->IsEmpty()) { if (_nestedMacro->IsEmpty()) {
_nestedMacro->ShowAllMacroSections(); _nestedMacro->ShowAllMacroSections();

View File

@@ -5,10 +5,13 @@
#include "macro-input.hpp" #include "macro-input.hpp"
#include "macro-selection.hpp" #include "macro-selection.hpp"
#include "macro-segment-selection.hpp" #include "macro-segment-selection.hpp"
#include "regex-config.hpp"
#include "resizable-widget.hpp" #include "resizable-widget.hpp"
#include "variable-line-edit.hpp"
#include <QCheckBox> #include <QCheckBox>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel>
namespace advss { namespace advss {
@@ -54,15 +57,31 @@ public:
TOGGLE_ACTION, TOGGLE_ACTION,
TOGGLE_PAUSE, TOGGLE_PAUSE,
NESTED_MACRO, NESTED_MACRO,
GET_INFO,
RUN_MACRO,
}; };
Action _action = Action::NESTED_MACRO;
void SetAction(Action);
Action GetAction() const { return _action; }
enum class SelectionType { INDEX, LABEL, ID };
SelectionType _actionSelectionType = SelectionType::INDEX;
bool _useElseSection = false;
IntVariable _actionIndex = 1; IntVariable _actionIndex = 1;
StringVariable _label = "Custom label";
std::string _actionId;
RegexConfig _regex;
RunOptions _runOptions = {}; RunOptions _runOptions = {};
std::shared_ptr<Macro> _nestedMacro = std::make_shared<Macro>(); std::shared_ptr<Macro> _nestedMacro = std::make_shared<Macro>();
int _customWidgetHeight = 0; int _customWidgetHeight = 0;
private: private:
void SetupTempVars();
void RunActions(Macro *actionMacro) const; void RunActions(Macro *actionMacro) const;
void AdjustActionState(Macro *) const;
Action _action = Action::NESTED_MACRO;
static bool _registered; static bool _registered;
static const std::string id; static const std::string id;
@@ -82,11 +101,15 @@ public:
private slots: private slots:
void MacroChanged(const QString &text); void MacroChanged(const QString &text);
void ActionChanged(int value); void ActionChanged(int value);
void ActionSelectionTypeChanged(int value);
void ActionIndexChanged(const IntVariable &value); void ActionIndexChanged(const IntVariable &value);
void LabelChanged();
void ActionTypeChanged(int value);
void RegexChanged(const RegexConfig &);
void ConditionMacroChanged(const QString &text); void ConditionMacroChanged(const QString &text);
void ConditionBehaviorChanged(int value); void ConditionBehaviorChanged(int value);
void ReevaluateConditionStateChanged(int value); void ReevaluateConditionStateChanged(int value);
void ActionTypeChanged(int value); void ActionSectionChanged(int value);
void SkipWhenPausedChanged(int value); void SkipWhenPausedChanged(int value);
void SetInputsChanged(int value); void SetInputsChanged(int value);
void InputsChanged(const StringList &); void InputsChanged(const StringList &);
@@ -98,14 +121,20 @@ private:
void SetWidgetVisibility(); void SetWidgetVisibility();
void SetupMacroInput(Macro *) const; void SetupMacroInput(Macro *) const;
MacroSelection *_macros;
MacroSegmentSelection *_actionIndex;
QComboBox *_actions; QComboBox *_actions;
MacroSelection *_macros;
QComboBox *_actionSelectionType;
MacroSegmentSelection *_actionIndex;
VariableLineEdit *_label;
FilterComboBox *_actionTypes;
RegexConfigWidget *_regex;
MacroSelection *_conditionMacros; MacroSelection *_conditionMacros;
QComboBox *_conditionBehaviors; QComboBox *_conditionBehaviors;
QCheckBox *_reevaluateConditionState; QCheckBox *_reevaluateConditionState;
QComboBox *_actionTypes; QComboBox *_actionSections;
QCheckBox *_skipWhenPaused; QCheckBox *_skipWhenPaused;
QLabel *_noConditionsWarning;
HelpIcon *_runMacroHelp;
QCheckBox *_setInputs; QCheckBox *_setInputs;
MacroInputEdit *_inputs; MacroInputEdit *_inputs;
QHBoxLayout *_entryLayout; QHBoxLayout *_entryLayout;

View File

@@ -1,4 +1,5 @@
#include "macro-action-variable.hpp" #include "macro-action-variable.hpp"
#include "help-icon.hpp"
#include "json-helpers.hpp" #include "json-helpers.hpp"
#include "layout-helpers.hpp" #include "layout-helpers.hpp"
#include "macro-condition-edit.hpp" #include "macro-condition-edit.hpp"
@@ -16,6 +17,15 @@ namespace advss {
const std::string MacroActionVariable::id = "variable"; const std::string MacroActionVariable::id = "variable";
std::vector<TempVariableRef> MacroActionVariable::GetTempVarRefs() const
{
auto refs = MacroSegment::GetTempVarRefs();
if (_tempVar.HasValidID()) {
refs.push_back(_tempVar);
}
return refs;
}
bool MacroActionVariable::_registered = MacroActionFactory::Register( bool MacroActionVariable::_registered = MacroActionFactory::Register(
MacroActionVariable::id, MacroActionVariable::id,
{MacroActionVariable::Create, MacroActionVariableEdit::Create, {MacroActionVariable::Create, MacroActionVariableEdit::Create,
@@ -218,6 +228,43 @@ void MacroActionVariable::GenerateRandomNumber(Variable *var)
} }
} }
void MacroActionVariable::PickRandomValue(Variable *var)
{
static std::random_device rd;
static std::mt19937 gen(rd());
static const uint8_t maxIter = 255;
if (_randomValues.isEmpty()) {
return;
}
if (_randomValues.size() == 1) {
const auto &value = _randomValues.at(0);
var->SetValue(value);
_lastRandomValue = value;
}
std::uniform_int_distribution<int> dis(0, _randomValues.size() - 1);
StringVariable value = _randomValues.at(dis(gen));
uint8_t iter = 0;
for (; !_allowRepeatValues && iter < maxIter &&
_lastRandomValue.has_value() &&
std::string(value) == _lastRandomValue.value();
iter++) {
value = _randomValues.at(dis(gen));
}
if (iter == maxIter) {
blog(LOG_INFO,
"giving up picking non-repeat random value after %d tries",
maxIter);
}
var->SetValue(value);
_lastRandomValue = value;
}
struct AskForInputParams { struct AskForInputParams {
AskForInputParams(const QString &prompt_, const QString &placeholder_) AskForInputParams(const QString &prompt_, const QString &placeholder_)
: prompt(prompt_), : prompt(prompt_),
@@ -285,6 +332,14 @@ bool MacroActionVariable::PerformAction()
case Action::APPEND: case Action::APPEND:
apppend(*var, _strValue); apppend(*var, _strValue);
break; break;
case Action::COPY_VAR: {
auto var2 = _variable2.lock();
if (!var2) {
return true;
}
var->SetValue(var2->Value());
break;
}
case Action::APPEND_VAR: { case Action::APPEND_VAR: {
auto var2 = _variable2.lock(); auto var2 = _variable2.lock();
if (!var2) { if (!var2) {
@@ -392,7 +447,19 @@ bool MacroActionVariable::PerformAction()
if (!value.has_value()) { if (!value.has_value()) {
return true; return true;
} }
var->SetValue(*value);
if (!_jsonQueryExtractSingle) {
var->SetValue(*value);
return true;
}
auto extracted = ExtractSingleJsonArrayElement(*value);
if (extracted.has_value()) {
var->SetValue(*extracted);
} else {
var->SetValue(*value);
}
return true; return true;
} }
case Action::ARRAY_JSON: { case Action::ARRAY_JSON: {
@@ -449,6 +516,9 @@ bool MacroActionVariable::PerformAction()
case Action::RANDOM_NUMBER: case Action::RANDOM_NUMBER:
GenerateRandomNumber(var.get()); GenerateRandomNumber(var.get());
return true; return true;
case Action::RANDOM_LIST_VALUE:
PickRandomValue(var.get());
return true;
} }
return true; return true;
@@ -489,8 +559,12 @@ bool MacroActionVariable::Save(obs_data_t *obj) const
_randomNumberStart.Save(obj, "randomNumberStart"); _randomNumberStart.Save(obj, "randomNumberStart");
_randomNumberEnd.Save(obj, "randomNumberEnd"); _randomNumberEnd.Save(obj, "randomNumberEnd");
obs_data_set_bool(obj, "generateInteger", _generateInteger); obs_data_set_bool(obj, "generateInteger", _generateInteger);
_randomValues.Save(obj, "randomValues", "value");
obs_data_set_bool(obj, "allowRepeatValues", _allowRepeatValues);
_jsonQuery.Save(obj, "jsonQuery"); _jsonQuery.Save(obj, "jsonQuery");
_jsonIndex.Save(obj, "jsonIndex"); _jsonIndex.Save(obj, "jsonIndex");
obs_data_set_bool(obj, "jsonQueryExtractSingle",
_jsonQueryExtractSingle);
obs_data_set_int(obj, "version", 1); obs_data_set_int(obj, "version", 1);
@@ -547,8 +621,12 @@ bool MacroActionVariable::Load(obs_data_t *obj)
_randomNumberStart.Load(obj, "randomNumberStart"); _randomNumberStart.Load(obj, "randomNumberStart");
_randomNumberEnd.Load(obj, "randomNumberEnd"); _randomNumberEnd.Load(obj, "randomNumberEnd");
_generateInteger = obs_data_get_bool(obj, "generateInteger"); _generateInteger = obs_data_get_bool(obj, "generateInteger");
_randomValues.Load(obj, "randomValues", "value");
_allowRepeatValues = obs_data_get_bool(obj, "allowRepeatValues");
_jsonQuery.Load(obj, "jsonQuery"); _jsonQuery.Load(obj, "jsonQuery");
_jsonIndex.Load(obj, "jsonIndex"); _jsonIndex.Load(obj, "jsonIndex");
_jsonQueryExtractSingle =
obs_data_get_bool(obj, "jsonQueryExtractSingle");
return true; return true;
} }
@@ -669,6 +747,8 @@ static inline void populateActionSelection(QComboBox *list)
actions = { actions = {
{MacroActionVariable::Action::SET_VALUE, {MacroActionVariable::Action::SET_VALUE,
"AdvSceneSwitcher.action.variable.type.set"}, "AdvSceneSwitcher.action.variable.type.set"},
{MacroActionVariable::Action::COPY_VAR,
"AdvSceneSwitcher.action.variable.type.copy"},
{MacroActionVariable::Action::APPEND, {MacroActionVariable::Action::APPEND,
"AdvSceneSwitcher.action.variable.type.append"}, "AdvSceneSwitcher.action.variable.type.append"},
{MacroActionVariable::Action::PAD, {MacroActionVariable::Action::PAD,
@@ -713,6 +793,8 @@ static inline void populateActionSelection(QComboBox *list)
"AdvSceneSwitcher.action.variable.type.roundToInt"}, "AdvSceneSwitcher.action.variable.type.roundToInt"},
{MacroActionVariable::Action::RANDOM_NUMBER, {MacroActionVariable::Action::RANDOM_NUMBER,
"AdvSceneSwitcher.action.variable.type.randomNumber"}, "AdvSceneSwitcher.action.variable.type.randomNumber"},
{MacroActionVariable::Action::RANDOM_LIST_VALUE,
"AdvSceneSwitcher.action.variable.type.randomListValue"},
{true, ""}, // Separator {true, ""}, // Separator
{MacroActionVariable::Action::USER_INPUT, {MacroActionVariable::Action::USER_INPUT,
@@ -790,11 +872,10 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_segmentValueStatus(new QLabel()), _segmentValueStatus(new QLabel()),
_segmentValue(new ResizingPlainTextEdit(this, 10, 1, 1)), _segmentValue(new ResizingPlainTextEdit(this, 10, 1, 1)),
_substringLayout(new QVBoxLayout()), _substringLayout(new QVBoxLayout()),
_subStringIndexEntryLayout(new QHBoxLayout()), _subStringControlsLayout(new QHBoxLayout()),
_subStringRegexEntryLayout(new QHBoxLayout()),
_subStringStart(new VariableSpinBox(this)), _subStringStart(new VariableSpinBox(this)),
_subStringSize(new VariableSpinBox(this)), _subStringSize(new VariableSpinBox(this)),
_substringRegex(new RegexConfigWidget(parent)), _subStringRegex(new RegexConfigWidget(parent)),
_regexPattern(new ResizingPlainTextEdit(this, 10, 1, 1)), _regexPattern(new ResizingPlainTextEdit(this, 10, 1, 1)),
_regexMatchIdx(new VariableSpinBox(this)), _regexMatchIdx(new VariableSpinBox(this)),
_findReplaceLayout(new QHBoxLayout()), _findReplaceLayout(new QHBoxLayout()),
@@ -826,10 +907,28 @@ MacroActionVariableEdit::MacroActionVariableEdit(
obs_module_text( obs_module_text(
"AdvSceneSwitcher.action.variable.generateInteger"), "AdvSceneSwitcher.action.variable.generateInteger"),
this)), this)),
_randomLayout(new QVBoxLayout()), _randomNumberLayout(new QVBoxLayout()),
_randomValues(new StringListEdit(this)),
_allowRepeatValues(new QCheckBox(
obs_module_text(
"AdvSceneSwitcher.action.variable.type.allowRepeat"),
this)),
_randomValueLayout(new QVBoxLayout()),
_jsonQuery(new VariableLineEdit(this)), _jsonQuery(new VariableLineEdit(this)),
_jsonQueryHelp(new QLabel(this)), _jsonQueryHelp(new HelpIcon(
obs_module_text(
"AdvSceneSwitcher.action.variable.type.queryJson.info"),
this)),
_jsonIndex(new VariableSpinBox(this)), _jsonIndex(new VariableSpinBox(this)),
_jsonExtractSingle(new QCheckBox(
obs_module_text(
"AdvSceneSwitcher.action.variable.type.queryJson.extractSingle"),
this)),
_jsonExtractSingleHelp(new HelpIcon(
obs_module_text(
"AdvSceneSwitcher.action.variable.type.queryJson.extractSingle.info"),
this)),
_jsonQueryLayout(new QVBoxLayout()),
_entryLayout(new QHBoxLayout()) _entryLayout(new QHBoxLayout())
{ {
_numValue->setMinimum(-9999999999); _numValue->setMinimum(-9999999999);
@@ -859,15 +958,13 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_randomNumberStart->setMaximum(9999999999); _randomNumberStart->setMaximum(9999999999);
_randomNumberEnd->setMinimum(-9999999999); _randomNumberEnd->setMinimum(-9999999999);
_randomNumberEnd->setMaximum(9999999999); _randomNumberEnd->setMaximum(9999999999);
const QString path = GetThemeTypeName() == "Light" _randomValues->SetMaxStringSize(99999999);
? ":/res/images/help.svg"
: ":/res/images/help_light.svg";
const QIcon icon(path);
const QPixmap pixmap = icon.pixmap(QSize(16, 16));
_jsonQueryHelp->setPixmap(pixmap);
_jsonQueryHelp->setToolTip(obs_module_text(
"AdvSceneSwitcher.action.variable.type.queryJson.info"));
_jsonIndex->setMaximum(999); _jsonIndex->setMaximum(999);
auto jsonExtractSingleLayout = new QHBoxLayout();
jsonExtractSingleLayout->addWidget(_jsonExtractSingle);
jsonExtractSingleLayout->addWidget(_jsonExtractSingleHelp);
jsonExtractSingleLayout->addStretch();
_jsonQueryLayout->addLayout(jsonExtractSingleLayout);
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)), QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
this, SLOT(VariableChanged(const QString &))); this, SLOT(VariableChanged(const QString &)));
@@ -895,7 +992,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_subStringSize, _subStringSize,
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)), SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this, SLOT(SubStringSizeChanged(const NumberVariable<int> &))); this, SLOT(SubStringSizeChanged(const NumberVariable<int> &)));
QWidget::connect(_substringRegex, QWidget::connect(_subStringRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this, SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(SubStringRegexChanged(const RegexConfig &))); SLOT(SubStringRegexChanged(const RegexConfig &)));
QWidget::connect(_regexPattern, SIGNAL(textChanged()), this, QWidget::connect(_regexPattern, SIGNAL(textChanged()), this,
@@ -955,12 +1052,19 @@ MacroActionVariableEdit::MacroActionVariableEdit(
SLOT(RandomNumberEndChanged(const NumberVariable<double> &))); SLOT(RandomNumberEndChanged(const NumberVariable<double> &)));
QWidget::connect(_generateInteger, SIGNAL(stateChanged(int)), this, QWidget::connect(_generateInteger, SIGNAL(stateChanged(int)), this,
SLOT(GenerateIntegerChanged(int))); SLOT(GenerateIntegerChanged(int)));
QWidget::connect(_randomValues,
SIGNAL(StringListChanged(const StringList &)), this,
SLOT(RandomValueListChanged(const StringList &)));
QWidget::connect(_allowRepeatValues, SIGNAL(stateChanged(int)), this,
SLOT(AllowRepeatValuesChanged(int)));
QWidget::connect(_jsonQuery, SIGNAL(editingFinished()), this, QWidget::connect(_jsonQuery, SIGNAL(editingFinished()), this,
SLOT(JsonQueryChanged())); SLOT(JsonQueryChanged()));
QWidget::connect( QWidget::connect(
_jsonIndex, _jsonIndex,
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)), SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this, SLOT(JsonIndexChanged(const NumberVariable<int> &))); this, SLOT(JsonIndexChanged(const NumberVariable<int> &)));
QWidget::connect(_jsonExtractSingle, SIGNAL(stateChanged(int)), this,
SLOT(JsonQueryExtractSingleChanged(int)));
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = { const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{variables}}", _variables}, {"{{variables}}", _variables},
@@ -969,9 +1073,6 @@ MacroActionVariableEdit::MacroActionVariableEdit(
{"{{strValue}}", _strValue}, {"{{strValue}}", _strValue},
{"{{numValue}}", _numValue}, {"{{numValue}}", _numValue},
{"{{segmentIndex}}", _segmentIdx}, {"{{segmentIndex}}", _segmentIdx},
{"{{subStringStart}}", _subStringStart},
{"{{subStringSize}}", _subStringSize},
{"{{regexMatchIdx}}", _regexMatchIdx},
{"{{findRegex}}", _findRegex}, {"{{findRegex}}", _findRegex},
{"{{findStr}}", _findStr}, {"{{findStr}}", _findStr},
{"{{replaceStr}}", _replaceStr}, {"{{replaceStr}}", _replaceStr},
@@ -1003,12 +1104,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
PlaceWidgets( PlaceWidgets(
obs_module_text( obs_module_text(
"AdvSceneSwitcher.action.variable.layout.substringIndex"), "AdvSceneSwitcher.action.variable.layout.substringIndex"),
_subStringIndexEntryLayout, widgetPlaceholders); _subStringControlsLayout, widgetPlaceholders);
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.variable.layout.substringRegex"),
_subStringRegexEntryLayout, widgetPlaceholders);
PlaceWidgets( PlaceWidgets(
obs_module_text( obs_module_text(
@@ -1029,20 +1125,23 @@ MacroActionVariableEdit::MacroActionVariableEdit(
obs_module_text( obs_module_text(
"AdvSceneSwitcher.action.variable.layout.randomNumber"), "AdvSceneSwitcher.action.variable.layout.randomNumber"),
randomLayout, widgetPlaceholders); randomLayout, widgetPlaceholders);
_randomLayout->addLayout(randomLayout); _randomNumberLayout->addLayout(randomLayout);
_randomLayout->addWidget(_generateInteger); _randomNumberLayout->addWidget(_generateInteger);
_randomValueLayout->addWidget(_randomValues);
_randomValueLayout->addWidget(_allowRepeatValues);
auto regexConfigLayout = new QHBoxLayout; auto regexConfigLayout = new QHBoxLayout;
regexConfigLayout->addWidget(_substringRegex); regexConfigLayout->addWidget(_subStringRegex);
regexConfigLayout->addStretch(); regexConfigLayout->addStretch();
_substringLayout->addLayout(_subStringIndexEntryLayout); _substringLayout->addLayout(_subStringControlsLayout);
_substringLayout->addLayout(_subStringRegexEntryLayout);
_substringLayout->addWidget(_regexPattern); _substringLayout->addWidget(_regexPattern);
_substringLayout->addLayout(regexConfigLayout); _substringLayout->addLayout(regexConfigLayout);
auto layout = new QVBoxLayout; auto layout = new QVBoxLayout;
layout->addLayout(_entryLayout); layout->addLayout(_entryLayout);
layout->addLayout(_jsonQueryLayout);
layout->addLayout(_substringLayout); layout->addLayout(_substringLayout);
layout->addWidget(_segmentValueStatus); layout->addWidget(_segmentValueStatus);
layout->addWidget(_segmentValue); layout->addWidget(_segmentValue);
@@ -1050,7 +1149,8 @@ MacroActionVariableEdit::MacroActionVariableEdit(
layout->addWidget(_mathExpressionResult); layout->addWidget(_mathExpressionResult);
layout->addLayout(_promptLayout); layout->addLayout(_promptLayout);
layout->addLayout(_placeholderLayout); layout->addLayout(_placeholderLayout);
layout->addLayout(_randomLayout); layout->addLayout(_randomNumberLayout);
layout->addLayout(_randomValueLayout);
setLayout(layout); setLayout(layout);
_entryData = entryData; _entryData = entryData;
@@ -1084,7 +1184,7 @@ void MacroActionVariableEdit::UpdateEntryData()
: MacroSegmentSelection::Type::ACTION); : MacroSegmentSelection::Type::ACTION);
_subStringStart->SetValue(_entryData->_subStringStart); _subStringStart->SetValue(_entryData->_subStringStart);
_subStringSize->SetValue(_entryData->_subStringSize); _subStringSize->SetValue(_entryData->_subStringSize);
_substringRegex->SetRegexConfig(_entryData->_subStringRegex); _subStringRegex->SetRegexConfig(_entryData->_subStringRegex);
_findRegex->SetRegexConfig(_entryData->_findRegex); _findRegex->SetRegexConfig(_entryData->_findRegex);
_regexPattern->setPlainText( _regexPattern->setPlainText(
QString::fromStdString(_entryData->_regexPattern)); QString::fromStdString(_entryData->_regexPattern));
@@ -1109,8 +1209,11 @@ void MacroActionVariableEdit::UpdateEntryData()
_randomNumberStart->SetValue(_entryData->_randomNumberStart); _randomNumberStart->SetValue(_entryData->_randomNumberStart);
_randomNumberEnd->SetValue(_entryData->_randomNumberEnd); _randomNumberEnd->SetValue(_entryData->_randomNumberEnd);
_generateInteger->setChecked(_entryData->_generateInteger); _generateInteger->setChecked(_entryData->_generateInteger);
_allowRepeatValues->setChecked(_entryData->_allowRepeatValues);
_randomValues->SetStringList(_entryData->_randomValues);
_jsonQuery->setText(_entryData->_jsonQuery); _jsonQuery->setText(_entryData->_jsonQuery);
_jsonIndex->SetValue(_entryData->_jsonIndex); _jsonIndex->SetValue(_entryData->_jsonIndex);
_jsonExtractSingle->setChecked(_entryData->_jsonQueryExtractSingle);
SetWidgetVisibility(); SetWidgetVisibility();
} }
@@ -1390,6 +1493,7 @@ void MacroActionVariableEdit::SelectionChanged(const TempVariableRef &var)
{ {
GUARD_LOADING_AND_LOCK(); GUARD_LOADING_AND_LOCK();
_entryData->_tempVar = var; _entryData->_tempVar = var;
IncrementTempVarInUseGeneration();
SetWidgetVisibility(); SetWidgetVisibility();
} }
@@ -1451,6 +1555,18 @@ void MacroActionVariableEdit::GenerateIntegerChanged(int value)
_entryData->_generateInteger = value; _entryData->_generateInteger = value;
} }
void MacroActionVariableEdit::RandomValueListChanged(const StringList &values)
{
GUARD_LOADING_AND_LOCK();
_entryData->_randomValues = values;
}
void MacroActionVariableEdit::AllowRepeatValuesChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_allowRepeatValues = value;
}
void MacroActionVariableEdit::JsonQueryChanged() void MacroActionVariableEdit::JsonQueryChanged()
{ {
GUARD_LOADING_AND_LOCK(); GUARD_LOADING_AND_LOCK();
@@ -1463,6 +1579,12 @@ void MacroActionVariableEdit::JsonIndexChanged(const NumberVariable<int> &value)
_entryData->_jsonIndex = value; _entryData->_jsonIndex = value;
} }
void MacroActionVariableEdit::JsonQueryExtractSingleChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_jsonQueryExtractSingle = value;
}
void MacroActionVariableEdit::SetWidgetVisibility() void MacroActionVariableEdit::SetWidgetVisibility()
{ {
if (!_entryData) { if (!_entryData) {
@@ -1489,8 +1611,20 @@ void MacroActionVariableEdit::SetWidgetVisibility()
{"{{jsonQuery}}", _jsonQuery}, {"{{jsonQuery}}", _jsonQuery},
{"{{jsonQueryHelp}}", _jsonQueryHelp}, {"{{jsonQueryHelp}}", _jsonQueryHelp},
{"{{jsonIndex}}", _jsonIndex}, {"{{jsonIndex}}", _jsonIndex},
{"{{subStringRegex}}", _subStringRegex},
{"{{subStringStart}}", _subStringStart},
{"{{subStringSize}}", _subStringSize},
{"{{regexMatchIdx}}", _regexMatchIdx},
}; };
for (const auto &[_, widget] : widgetPlaceholders) {
_entryLayout->removeWidget(widget);
_subStringControlsLayout->removeWidget(widget);
}
ClearLayout(_entryLayout);
ClearLayout(_subStringControlsLayout);
const char *layoutString = ""; const char *layoutString = "";
if (_entryData->_action == MacroActionVariable::Action::PAD) { if (_entryData->_action == MacroActionVariable::Action::PAD) {
layoutString = obs_module_text( layoutString = obs_module_text(
@@ -1499,16 +1633,15 @@ void MacroActionVariableEdit::SetWidgetVisibility()
MacroActionVariable::Action::TRUNCATE) { MacroActionVariable::Action::TRUNCATE) {
layoutString = obs_module_text( layoutString = obs_module_text(
"AdvSceneSwitcher.action.variable.layout.truncate"); "AdvSceneSwitcher.action.variable.layout.truncate");
} else if (_entryData->_action ==
MacroActionVariable::Action::COPY_VAR) {
layoutString = obs_module_text(
"AdvSceneSwitcher.action.variable.layout.copy");
} else { } else {
layoutString = obs_module_text( layoutString = obs_module_text(
"AdvSceneSwitcher.action.variable.layout.other"); "AdvSceneSwitcher.action.variable.layout.other");
} }
for (const auto &[_, widget] : widgetPlaceholders) {
_entryLayout->removeWidget(widget);
}
ClearLayout(_entryLayout);
PlaceWidgets(layoutString, _entryLayout, widgetPlaceholders); PlaceWidgets(layoutString, _entryLayout, widgetPlaceholders);
if (_entryData->_action == MacroActionVariable::Action::SET_VALUE || if (_entryData->_action == MacroActionVariable::Action::SET_VALUE ||
@@ -1525,6 +1658,7 @@ void MacroActionVariableEdit::SetWidgetVisibility()
} }
_variables2->setVisible( _variables2->setVisible(
_entryData->_action == MacroActionVariable::Action::COPY_VAR ||
_entryData->_action == _entryData->_action ==
MacroActionVariable::Action::APPEND_VAR || MacroActionVariable::Action::APPEND_VAR ||
_entryData->_action == _entryData->_action ==
@@ -1554,15 +1688,25 @@ void MacroActionVariableEdit::SetWidgetVisibility()
MacroActionVariable::Action::SET_ACTION_VALUE || MacroActionVariable::Action::SET_ACTION_VALUE ||
_entryData->_action == _entryData->_action ==
MacroActionVariable::Action::SET_CONDITION_VALUE); MacroActionVariable::Action::SET_CONDITION_VALUE);
bool showRegex = _entryData->_subStringRegex.Enabled();
layoutString =
showRegex
? "AdvSceneSwitcher.action.variable.layout.substringRegex"
: "AdvSceneSwitcher.action.variable.layout.substringIndex";
PlaceWidgets(obs_module_text(layoutString), _subStringControlsLayout,
widgetPlaceholders);
_subStringStart->setVisible(!showRegex);
_subStringSize->setVisible(!showRegex);
_regexMatchIdx->setVisible(showRegex);
SetLayoutVisible(_substringLayout, SetLayoutVisible(_substringLayout,
_entryData->_action == _entryData->_action ==
MacroActionVariable::Action::SUBSTRING); MacroActionVariable::Action::SUBSTRING);
if (_entryData->_action == MacroActionVariable::Action::SUBSTRING) { _regexPattern->setVisible(
bool showRegex = _entryData->_subStringRegex.Enabled(); showRegex &&
SetLayoutVisible(_subStringIndexEntryLayout, !showRegex); _entryData->_action == MacroActionVariable::Action::SUBSTRING);
SetLayoutVisible(_subStringRegexEntryLayout, showRegex);
_regexPattern->setVisible(showRegex);
}
SetLayoutVisible(_findReplaceLayout, SetLayoutVisible(_findReplaceLayout,
_entryData->_action == _entryData->_action ==
MacroActionVariable::Action::FIND_AND_REPLACE); MacroActionVariable::Action::FIND_AND_REPLACE);
@@ -1627,15 +1771,22 @@ void MacroActionVariableEdit::SetWidgetVisibility()
MacroActionVariable::Action::PAD); MacroActionVariable::Action::PAD);
_caseType->setVisible(_entryData->_action == _caseType->setVisible(_entryData->_action ==
MacroActionVariable::Action::CHANGE_CASE); MacroActionVariable::Action::CHANGE_CASE);
SetLayoutVisible(_randomLayout, SetLayoutVisible(_randomNumberLayout,
_entryData->_action == _entryData->_action ==
MacroActionVariable::Action::RANDOM_NUMBER); MacroActionVariable::Action::RANDOM_NUMBER);
SetLayoutVisible(
_randomValueLayout,
_entryData->_action ==
MacroActionVariable::Action::RANDOM_LIST_VALUE);
_jsonQuery->setVisible(_entryData->_action == _jsonQuery->setVisible(_entryData->_action ==
MacroActionVariable::Action::QUERY_JSON); MacroActionVariable::Action::QUERY_JSON);
_jsonQueryHelp->setVisible(_entryData->_action == _jsonQueryHelp->setVisible(_entryData->_action ==
MacroActionVariable::Action::QUERY_JSON); MacroActionVariable::Action::QUERY_JSON);
_jsonIndex->setVisible(_entryData->_action == _jsonIndex->setVisible(_entryData->_action ==
MacroActionVariable::Action::ARRAY_JSON); MacroActionVariable::Action::ARRAY_JSON);
SetLayoutVisible(_jsonQueryLayout,
_entryData->_action ==
MacroActionVariable::Action::QUERY_JSON);
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();

View File

@@ -6,6 +6,7 @@
#include "resizing-text-edit.hpp" #include "resizing-text-edit.hpp"
#include "scene-selection.hpp" #include "scene-selection.hpp"
#include "single-char-selection.hpp" #include "single-char-selection.hpp"
#include "string-list.hpp"
#include "variable-line-edit.hpp" #include "variable-line-edit.hpp"
#include "variable-text-edit.hpp" #include "variable-text-edit.hpp"
#include "variable-spinbox.hpp" #include "variable-spinbox.hpp"
@@ -22,6 +23,7 @@ public:
bool PostLoad(); bool PostLoad();
std::string GetShortDesc() const; std::string GetShortDesc() const;
std::string GetId() const { return id; }; std::string GetId() const { return id; };
std::vector<TempVariableRef> GetTempVarRefs() const;
static std::shared_ptr<MacroAction> Create(Macro *m); static std::shared_ptr<MacroAction> Create(Macro *m);
std::shared_ptr<MacroAction> Copy() const; std::shared_ptr<MacroAction> Copy() const;
void SetSegmentIndexValue(int); void SetSegmentIndexValue(int);
@@ -55,6 +57,8 @@ public:
RANDOM_NUMBER, RANDOM_NUMBER,
QUERY_JSON, QUERY_JSON,
ARRAY_JSON, ARRAY_JSON,
COPY_VAR,
RANDOM_LIST_VALUE,
}; };
Action _action = Action::SET_VALUE; Action _action = Action::SET_VALUE;
@@ -106,8 +110,13 @@ public:
DoubleVariable _randomNumberEnd = 100; DoubleVariable _randomNumberEnd = 100;
bool _generateInteger = true; bool _generateInteger = true;
StringList _randomValues = {"value1", "value2", "value3"};
bool _allowRepeatValues = true;
std::optional<std::string> _lastRandomValue;
StringVariable _jsonQuery = "$.some.nested.value"; StringVariable _jsonQuery = "$.some.nested.value";
IntVariable _jsonIndex = 0; IntVariable _jsonIndex = 0;
bool _jsonQueryExtractSingle = true;
private: private:
void DecrementCurrentSegmentVariableRef(); void DecrementCurrentSegmentVariableRef();
@@ -118,6 +127,7 @@ private:
void HandleCaseChange(Variable *); void HandleCaseChange(Variable *);
void SetToSceneItemName(Variable *); void SetToSceneItemName(Variable *);
void GenerateRandomNumber(Variable *); void GenerateRandomNumber(Variable *);
void PickRandomValue(Variable *);
std::weak_ptr<MacroSegment> _macroSegment; std::weak_ptr<MacroSegment> _macroSegment;
int _segmentIdxLoadValue = -1; int _segmentIdxLoadValue = -1;
@@ -174,8 +184,11 @@ private slots:
void RandomNumberStartChanged(const NumberVariable<double> &); void RandomNumberStartChanged(const NumberVariable<double> &);
void RandomNumberEndChanged(const NumberVariable<double> &); void RandomNumberEndChanged(const NumberVariable<double> &);
void GenerateIntegerChanged(int); void GenerateIntegerChanged(int);
void RandomValueListChanged(const StringList &);
void AllowRepeatValuesChanged(int);
void JsonQueryChanged(); void JsonQueryChanged();
void JsonIndexChanged(const NumberVariable<int> &); void JsonIndexChanged(const NumberVariable<int> &);
void JsonQueryExtractSingleChanged(int);
signals: signals:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
@@ -193,11 +206,10 @@ private:
QLabel *_segmentValueStatus; QLabel *_segmentValueStatus;
ResizingPlainTextEdit *_segmentValue; ResizingPlainTextEdit *_segmentValue;
QVBoxLayout *_substringLayout; QVBoxLayout *_substringLayout;
QHBoxLayout *_subStringIndexEntryLayout; QHBoxLayout *_subStringControlsLayout;
QHBoxLayout *_subStringRegexEntryLayout;
VariableSpinBox *_subStringStart; VariableSpinBox *_subStringStart;
VariableSpinBox *_subStringSize; VariableSpinBox *_subStringSize;
RegexConfigWidget *_substringRegex; RegexConfigWidget *_subStringRegex;
ResizingPlainTextEdit *_regexPattern; ResizingPlainTextEdit *_regexPattern;
VariableSpinBox *_regexMatchIdx; VariableSpinBox *_regexMatchIdx;
QHBoxLayout *_findReplaceLayout; QHBoxLayout *_findReplaceLayout;
@@ -224,10 +236,16 @@ private:
VariableDoubleSpinBox *_randomNumberStart; VariableDoubleSpinBox *_randomNumberStart;
VariableDoubleSpinBox *_randomNumberEnd; VariableDoubleSpinBox *_randomNumberEnd;
QCheckBox *_generateInteger; QCheckBox *_generateInteger;
QVBoxLayout *_randomLayout; QVBoxLayout *_randomNumberLayout;
StringListEdit *_randomValues;
QCheckBox *_allowRepeatValues;
QVBoxLayout *_randomValueLayout;
VariableLineEdit *_jsonQuery; VariableLineEdit *_jsonQuery;
QLabel *_jsonQueryHelp; HelpIcon *_jsonQueryHelp;
VariableSpinBox *_jsonIndex; VariableSpinBox *_jsonIndex;
QCheckBox *_jsonExtractSingle;
HelpIcon *_jsonExtractSingleHelp;
QVBoxLayout *_jsonQueryLayout;
QHBoxLayout *_entryLayout; QHBoxLayout *_entryLayout;
std::shared_ptr<MacroActionVariable> _entryData; std::shared_ptr<MacroActionVariable> _entryData;

View File

@@ -1,10 +1,15 @@
#include "macro-condition-edit.hpp" #include "macro-condition-edit.hpp"
#include "macro-undo-redo.hpp"
#include "advanced-scene-switcher.hpp" #include "advanced-scene-switcher.hpp"
#include "condition-logic.hpp"
#include "macro-settings.hpp" #include "macro-settings.hpp"
#include "macro-signals.hpp"
#include "macro.hpp" #include "macro.hpp"
#include "path-helpers.hpp" #include "path-helpers.hpp"
#include "plugin-state-helpers.hpp" #include "plugin-state-helpers.hpp"
#include "section.hpp" #include "section.hpp"
#include "switch-button.hpp"
#include "ui-helpers.hpp" #include "ui-helpers.hpp"
#include "utility.hpp" #include "utility.hpp"
@@ -100,14 +105,17 @@ void DurationModifierEdit::Collapse(bool collapse)
MacroConditionEdit::MacroConditionEdit( MacroConditionEdit::MacroConditionEdit(
QWidget *parent, std::shared_ptr<MacroCondition> *entryData, QWidget *parent, std::shared_ptr<MacroCondition> *entryData,
const std::string &id, bool isRootCondition) bool isRootCondition)
: MacroSegmentEdit(parent), : MacroSegmentEdit(parent),
_enable(new SwitchButton()),
_logicSelection(new QComboBox()), _logicSelection(new QComboBox()),
_conditionSelection(new FilterComboBox()), _conditionSelection(new FilterComboBox()),
_dur(new DurationModifierEdit()), _dur(new DurationModifierEdit()),
_entryData(entryData), _entryData(entryData),
_isRoot(isRootCondition) _isRoot(isRootCondition)
{ {
QWidget::connect(_enable, SIGNAL(checked(bool)), this,
SLOT(ConditionEnableChanged(bool)));
QWidget::connect(_logicSelection, SIGNAL(currentIndexChanged(int)), QWidget::connect(_logicSelection, SIGNAL(currentIndexChanged(int)),
this, SLOT(LogicSelectionChanged(int))); this, SLOT(LogicSelectionChanged(int)));
QWidget::connect(_conditionSelection, QWidget::connect(_conditionSelection,
@@ -122,14 +130,17 @@ MacroConditionEdit::MacroConditionEdit(
Logic::PopulateLogicTypeSelection(_logicSelection, isRootCondition); Logic::PopulateLogicTypeSelection(_logicSelection, isRootCondition);
populateConditionSelection(_conditionSelection); populateConditionSelection(_conditionSelection);
_section->AddHeaderWidget(_enable);
_section->AddHeaderWidget(_logicSelection); _section->AddHeaderWidget(_logicSelection);
_section->AddHeaderWidget(_conditionSelection); _section->AddHeaderWidget(_conditionSelection);
_section->AddHeaderWidget(_headerInfo); _section->AddHeaderWidget(_headerInfo);
_section->AddHeaderWidget(_dur); _section->AddHeaderWidget(_dur);
_section->AddHeaderWidget(_varMappingToggle);
QVBoxLayout *conditionLayout = new QVBoxLayout; QVBoxLayout *conditionLayout = new QVBoxLayout;
conditionLayout->setContentsMargins({7, 7, 7, 7}); conditionLayout->setContentsMargins({7, 7, 7, 7});
conditionLayout->addWidget(_section); conditionLayout->addWidget(_section);
conditionLayout->addWidget(_outputMappings);
_contentLayout->addLayout(conditionLayout); _contentLayout->addLayout(conditionLayout);
QHBoxLayout *mainLayout = new QHBoxLayout; QHBoxLayout *mainLayout = new QHBoxLayout;
@@ -152,8 +163,17 @@ void MacroConditionEdit::LogicSelectionChanged(int idx)
const auto logic = static_cast<Logic::Type>( const auto logic = static_cast<Logic::Type>(
_logicSelection->itemData(idx).toInt()); _logicSelection->itemData(idx).toInt());
(*_entryData)->SetLogicType(logic); (*_entryData)->SetLogicType(logic);
}
SetEnableAppearance(logic != Logic::Type::NONE); void MacroConditionEdit::ConditionEnableChanged(bool value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
(*_entryData)->SetEnabled(value);
SetDisableEffect(!value);
} }
bool MacroConditionEdit::IsRootNode() const bool MacroConditionEdit::IsRootNode() const
@@ -166,7 +186,9 @@ void MacroConditionEdit::SetLogicSelection()
const auto logic = (*_entryData)->GetLogicType(); const auto logic = (*_entryData)->GetLogicType();
_logicSelection->setCurrentIndex( _logicSelection->setCurrentIndex(
_logicSelection->findData(static_cast<int>(logic))); _logicSelection->findData(static_cast<int>(logic)));
SetEnableAppearance(logic != Logic::Type::NONE); const bool enabled = (*_entryData)->Enabled();
_enable->setChecked(enabled);
SetEnableAppearance(enabled);
} }
void MacroConditionEdit::SetRootNode(bool root) void MacroConditionEdit::SetRootNode(bool root)
@@ -204,7 +226,10 @@ void MacroConditionEdit::SetupWidgets(bool basicSetup)
MacroConditionFactory::CreateWidget(id, this, *_entryData); MacroConditionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)), QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &))); this, SLOT(HeaderInfoChanged(const QString &)));
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
SLOT(ShowVariableMappings(bool)));
_section->SetContent(widget, (*_entryData)->GetCollapsed()); _section->SetContent(widget, (*_entryData)->GetCollapsed());
SetupVarMappings((*_entryData).get());
SetFocusPolicyOfWidgets(); SetFocusPolicyOfWidgets();
_allWidgetsAreSetup = true; _allWidgetsAreSetup = true;
@@ -226,6 +251,19 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
return; return;
} }
Macro *const parentMacro =
(*_entryData)->GetMacro()->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
}
const std::string oldId = (*_entryData)->GetId();
const int oldLogic = (int)(*_entryData)->GetLogicType();
OBSDataAutoRelease oldData = obs_data_create();
(*_entryData)->Save(oldData);
auto temp = DurationModifier(); auto temp = DurationModifier();
_dur->SetValue(temp); _dur->SetValue(temp);
HeaderInfoChanged(""); HeaderInfoChanged("");
@@ -234,20 +272,39 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
{ {
auto lock = LockContext(); auto lock = LockContext();
auto logic = (*_entryData)->GetLogicType(); auto logic = (*_entryData)->GetLogicType();
const bool enabled = (*_entryData)->Enabled();
_entryData->reset(); _entryData->reset();
*_entryData = MacroConditionFactory::Create(id, macro); *_entryData = MacroConditionFactory::Create(id, macro);
(*_entryData)->SetIndex(idx); (*_entryData)->SetIndex(idx);
(*_entryData)->SetLogicType(logic); (*_entryData)->SetLogicType(logic);
(*_entryData)->SetEnabled(enabled);
(*_entryData)->PostLoad(); (*_entryData)->PostLoad();
RunPostLoadSteps(); RunAndClearPostLoadSteps();
}
OBSDataAutoRelease newData = obs_data_create();
(*_entryData)->Save(newData);
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
} else {
RegisterSegmentTypeChangeUndoRedo(
macro, MacroEdit::SegmentType::CONDITION, idx, oldId,
oldData, oldLogic, id, newData,
(int)(*_entryData)->GetLogicType());
} }
auto widget = auto widget =
MacroConditionFactory::CreateWidget(id, this, *_entryData); MacroConditionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)), QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &))); this, SLOT(HeaderInfoChanged(const QString &)));
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
SLOT(ShowVariableMappings(bool)));
_section->SetContent(widget); _section->SetContent(widget);
SetupVarMappings((*_entryData).get());
_dur->setVisible(MacroConditionFactory::UsesDurationModifier(id)); _dur->setVisible(MacroConditionFactory::UsesDurationModifier(id));
SetFocusPolicyOfWidgets(); SetFocusPolicyOfWidgets();
emit MacroSignalManager::Instance()->ConditionTypeCreated(
QString::fromStdString(id));
} }
void MacroConditionEdit::DurationChanged(const Duration &seconds) void MacroConditionEdit::DurationChanged(const Duration &seconds)
@@ -275,4 +332,9 @@ std::shared_ptr<MacroSegment> MacroConditionEdit::Data() const
return *_entryData; return *_entryData;
} }
void MacroConditionEdit::FocusTypeSelection()
{
_conditionSelection->setFocus();
}
} // namespace advss } // namespace advss

View File

@@ -8,6 +8,8 @@
namespace advss { namespace advss {
class SwitchButton;
class DurationModifierEdit : public QWidget { class DurationModifierEdit : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
@@ -34,26 +36,27 @@ class MacroConditionEdit : public MacroSegmentEdit {
Q_OBJECT Q_OBJECT
public: public:
MacroConditionEdit( MacroConditionEdit(QWidget *parent = nullptr,
QWidget *parent = nullptr, std::shared_ptr<MacroCondition> * = nullptr,
std::shared_ptr<MacroCondition> * = nullptr, bool root = true);
const std::string &id = MacroCondition::GetDefaultID().data(),
bool root = true);
bool IsRootNode() const; bool IsRootNode() const;
void SetRootNode(bool); void SetRootNode(bool);
void SetupWidgets(bool basicSetup = false); void SetupWidgets(bool basicSetup = false);
void SetEntryData(std::shared_ptr<MacroCondition> *); void SetEntryData(std::shared_ptr<MacroCondition> *);
void FocusTypeSelection() override;
private slots: private slots:
void LogicSelectionChanged(int idx); void LogicSelectionChanged(int idx);
void ConditionSelectionChanged(const QString &text); void ConditionSelectionChanged(const QString &text);
void DurationChanged(const Duration &value); void DurationChanged(const Duration &value);
void DurationModifierChanged(DurationModifier::Type m); void DurationModifierChanged(DurationModifier::Type m);
void ConditionEnableChanged(bool);
private: private:
void SetLogicSelection(); void SetLogicSelection();
std::shared_ptr<MacroSegment> Data() const; std::shared_ptr<MacroSegment> Data() const;
SwitchButton *_enable;
QComboBox *_logicSelection; QComboBox *_logicSelection;
FilterComboBox *_conditionSelection; FilterComboBox *_conditionSelection;
DurationModifierEdit *_dur; DurationModifierEdit *_dur;

View File

@@ -1,6 +1,9 @@
#include "macro-condition-macro.hpp" #include "macro-condition-macro.hpp"
#include "layout-helpers.hpp" #include "layout-helpers.hpp"
#include "macro-action-edit.hpp" #include "macro-action-edit.hpp"
#include "macro-helpers.hpp"
#include "macro-ref.hpp"
#include "macro-signals.hpp" #include "macro-signals.hpp"
#include "macro.hpp" #include "macro.hpp"
@@ -102,7 +105,7 @@ bool MacroConditionMacro::CheckActionStateCondition()
if (!macro) { if (!macro) {
return false; return false;
} }
if (!IsValidMacroSegmentIndex(macro.get(), _actionIndex - 1, false)) { if (!IsValidActionIndex(macro.get(), _actionIndex - 1)) {
return false; return false;
} }
if (_type == Type::ACTION_DISABLED) { if (_type == Type::ACTION_DISABLED) {
@@ -315,6 +318,7 @@ MacroConditionMacroEdit::MacroConditionMacroEdit(
_actionIndex(new MacroSegmentSelection( _actionIndex(new MacroSegmentSelection(
this, MacroSegmentSelection::Type::ACTION)) this, MacroSegmentSelection::Type::ACTION))
{ {
_macros->HideGroups();
_count->setMaximum(10000000); _count->setMaximum(10000000);
populateTypeSelection(_types); populateTypeSelection(_types);
populateCounterConditionSelection(_counterConditions); populateCounterConditionSelection(_counterConditions);

View File

@@ -6,6 +6,15 @@ namespace advss {
const std::string MacroConditionTempVar::id = "temp_var"; const std::string MacroConditionTempVar::id = "temp_var";
std::vector<TempVariableRef> MacroConditionTempVar::GetTempVarRefs() const
{
auto refs = MacroSegment::GetTempVarRefs();
if (_tempVar.HasValidID()) {
refs.push_back(_tempVar);
}
return refs;
}
bool MacroConditionTempVar::_registered = MacroConditionFactory::Register( bool MacroConditionTempVar::_registered = MacroConditionFactory::Register(
MacroConditionTempVar::id, MacroConditionTempVar::id,
{MacroConditionTempVar::Create, MacroConditionTempVarEdit::Create, {MacroConditionTempVar::Create, MacroConditionTempVarEdit::Create,
@@ -266,6 +275,7 @@ void MacroConditionTempVarEdit::VariableChanged(const TempVariableRef &var)
{ {
GUARD_LOADING_AND_LOCK(); GUARD_LOADING_AND_LOCK();
_entryData->_tempVar = var; _entryData->_tempVar = var;
IncrementTempVarInUseGeneration();
} }
void MacroConditionTempVarEdit::Variable2Changed(const QString &text) void MacroConditionTempVarEdit::Variable2Changed(const QString &text)

View File

@@ -20,6 +20,7 @@ public:
bool Load(obs_data_t *obj); bool Load(obs_data_t *obj);
std::string GetShortDesc() const; std::string GetShortDesc() const;
std::string GetId() const { return id; }; std::string GetId() const { return id; };
std::vector<TempVariableRef> GetTempVarRefs() const;
static std::shared_ptr<MacroCondition> Create(Macro *m) static std::shared_ptr<MacroCondition> Create(Macro *m)
{ {
return std::make_shared<MacroConditionTempVar>(m); return std::make_shared<MacroConditionTempVar>(m);

View File

@@ -1,5 +1,7 @@
#include "macro-condition.hpp" #include "macro-condition.hpp"
#include "condition-logic.hpp"
namespace advss { namespace advss {
MacroCondition::MacroCondition(Macro *m, bool supportsVariableValue) MacroCondition::MacroCondition(Macro *m, bool supportsVariableValue)
@@ -7,6 +9,17 @@ MacroCondition::MacroCondition(Macro *m, bool supportsVariableValue)
{ {
} }
bool MacroCondition::EvaluateCondition()
{
bool newValue = CheckCondition();
_changed = _previousValue.has_value() && (*_previousValue != newValue);
const bool negate = _logic.IsNegationType(GetLogicType());
_risingEdge = _changed &&
((!negate && newValue) || (negate && !newValue));
_previousValue = newValue;
return newValue;
}
bool MacroCondition::Save(obs_data_t *obj) const bool MacroCondition::Save(obs_data_t *obj) const
{ {
MacroSegment::Save(obj); MacroSegment::Save(obj);
@@ -20,6 +33,10 @@ bool MacroCondition::Load(obs_data_t *obj)
{ {
MacroSegment::Load(obj); MacroSegment::Load(obj);
_logic.Load(obj, "logic"); _logic.Load(obj, "logic");
if (_logic.GetType() == Logic::Type::NONE) {
SetEnabled(false);
_logic.SetType(Logic::Type::AND);
}
_durationModifier.Load(obj); _durationModifier.Load(obj);
return true; return true;
} }
@@ -39,7 +56,8 @@ void MacroCondition::ValidateLogicSelection(bool isRootCondition,
return; return;
} }
_logic.SetType(Logic::Type::NONE); _logic.SetType(Logic::Type::AND);
SetEnabled(false);
blog(LOG_WARNING, blog(LOG_WARNING,
"setting invalid logic selection to 'ignore' for macro %s", "setting invalid logic selection to 'ignore' for macro %s",
context); context);

View File

@@ -4,13 +4,19 @@
#include "duration-modifier.hpp" #include "duration-modifier.hpp"
#include "macro-ref.hpp" #include "macro-ref.hpp"
#include <optional>
namespace advss { namespace advss {
class EXPORT MacroCondition : public MacroSegment { class EXPORT MacroCondition : public MacroSegment {
public: public:
MacroCondition(Macro *m, bool supportsVariableValue = false); MacroCondition(Macro *m, bool supportsVariableValue = false);
virtual ~MacroCondition() = default; virtual ~MacroCondition() = default;
virtual bool CheckCondition() = 0;
bool EvaluateCondition();
bool HasChanged() const { return _changed; }
bool IsRisingEdge() const { return _risingEdge; }
virtual bool Save(obs_data_t *obj) const = 0; virtual bool Save(obs_data_t *obj) const = 0;
virtual bool Load(obs_data_t *obj) = 0; virtual bool Load(obs_data_t *obj) = 0;
@@ -28,9 +34,15 @@ public:
static std::string_view GetDefaultID(); static std::string_view GetDefaultID();
protected:
virtual bool CheckCondition() = 0;
private: private:
Logic _logic = Logic(Logic::Type::ROOT_NONE); Logic _logic = Logic(Logic::Type::ROOT_NONE);
DurationModifier _durationModifier; DurationModifier _durationModifier;
std::optional<bool> _previousValue;
bool _changed = false;
bool _risingEdge = false;
}; };
class EXPORT MacroRefCondition : virtual public MacroCondition { class EXPORT MacroRefCondition : virtual public MacroCondition {

View File

@@ -1,8 +1,11 @@
#include "macro-dock-settings.hpp" #include "macro-dock-settings.hpp"
#include "macro-dock.hpp"
#include "macro-dock-window.hpp" #include "macro-dock-window.hpp"
#include "macro-dock.hpp"
#include "macro-helpers.hpp"
#include "macro.hpp" #include "macro.hpp"
#include "plugin-state-helpers.hpp" #include "plugin-state-helpers.hpp"
#include "variable-string.hpp"
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <util/platform.h> #include <util/platform.h>

View File

@@ -110,7 +110,7 @@ void MacroDock::RunClicked()
return; return;
} }
auto ret = macro->PerformActions(true); auto ret = macro->PerformActions(true, true, true);
if (!ret) { if (!ret) {
QString err = QString err =
obs_module_text("AdvSceneSwitcher.macroTab.runFail"); obs_module_text("AdvSceneSwitcher.macroTab.runFail");

View File

@@ -1,17 +1,21 @@
#include "macro-edit.hpp" #include "macro-edit.hpp"
#include "condition-logic.hpp"
#include "macro-undo-redo.hpp"
#include "cursor-shape-changer.hpp" #include "cursor-shape-changer.hpp"
#include "macro.hpp"
#include "macro-action-edit.hpp" #include "macro-action-edit.hpp"
#include "macro-action-macro.hpp" #include "macro-action-macro.hpp"
#include "macro-condition-edit.hpp" #include "macro-condition-edit.hpp"
#include "macro-helpers.hpp"
#include "macro-segment-copy-paste.hpp" #include "macro-segment-copy-paste.hpp"
#include "macro-segment-list.hpp" #include "macro-segment-list.hpp"
#include "macro-settings.hpp" #include "macro-settings.hpp"
#include "macro-signals.hpp" #include "macro-signals.hpp"
#include "macro.hpp"
#include "math-helpers.hpp" #include "math-helpers.hpp"
#include "name-dialog.hpp" #include "name-dialog.hpp"
#include "path-helpers.hpp"
#include "obs-module-helper.hpp" #include "obs-module-helper.hpp"
#include "path-helpers.hpp"
#include "plugin-state-helpers.hpp" #include "plugin-state-helpers.hpp"
#include "splitter-helpers.hpp" #include "splitter-helpers.hpp"
#include "tab-helpers.hpp" #include "tab-helpers.hpp"
@@ -254,6 +258,92 @@ std::shared_ptr<Macro> MacroEdit::GetMacro() const
return _currentMacro; return _currentMacro;
} }
void MacroEdit::InsertSegmentWidget(SegmentType type, int idx)
{
auto macro = _currentMacro;
if (!macro) {
return;
}
switch (type) {
case SegmentType::ACTION: {
if (idx < 0 || idx > (int)macro->Actions().size()) {
break;
}
ui->actionsList->Insert(
idx, new MacroActionEdit(this, &macro->Actions()[idx]));
SetActionData(*macro);
ui->actionsList->SetHelpMsgVisible(false);
break;
}
case SegmentType::ELSE_ACTION: {
if (idx < 0 || idx > (int)macro->ElseActions().size()) {
break;
}
ui->elseActionsList->Insert(
idx,
new MacroActionEdit(this, &macro->ElseActions()[idx]));
SetElseActionData(*macro);
ui->elseActionsList->SetHelpMsgVisible(false);
break;
}
case SegmentType::CONDITION: {
if (idx < 0 || idx > (int)macro->Conditions().size()) {
break;
}
ui->conditionsList->Insert(
idx,
new MacroConditionEdit(this, &macro->Conditions()[idx],
idx == 0));
if (idx == 0 && macro->Conditions().size() > 1) {
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(1))
->SetRootNode(false);
}
SetConditionData(*macro);
ui->conditionsList->SetHelpMsgVisible(false);
break;
}
}
}
void MacroEdit::RemoveSegmentWidget(SegmentType type, int idx)
{
auto macro = _currentMacro;
if (!macro) {
return;
}
switch (type) {
case SegmentType::ACTION:
if (idx >= 0 &&
idx < ui->actionsList->ContentLayout()->count()) {
ui->actionsList->Remove(idx);
SetActionData(*macro);
}
break;
case SegmentType::ELSE_ACTION:
if (idx >= 0 &&
idx < ui->elseActionsList->ContentLayout()->count()) {
ui->elseActionsList->Remove(idx);
SetElseActionData(*macro);
}
break;
case SegmentType::CONDITION:
if (idx >= 0 &&
idx < ui->conditionsList->ContentLayout()->count()) {
ui->conditionsList->Remove(idx);
if (idx == 0 && !macro->Conditions().empty()) {
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(0))
->SetRootNode(true);
}
SetConditionData(*macro);
}
break;
}
}
void MacroEdit::ClearSegmentWidgetCacheFor(Macro *macro) const void MacroEdit::ClearSegmentWidgetCacheFor(Macro *macro) const
{ {
ui->conditionsList->ClearWidgetsFromCacheFor(macro); ui->conditionsList->ClearWidgetsFromCacheFor(macro);
@@ -292,7 +382,7 @@ isValidMacroSegmentIdx(const std::deque<std::shared_ptr<MacroSegment>> &list,
return (idx > 0 || (unsigned)idx < list.size()); return (idx > 0 || (unsigned)idx < list.size());
} }
void MacroEdit::SetupMacroSegmentSelection(MacroSection type, int idx) void MacroEdit::SetupMacroSegmentSelection(SegmentType type, int idx)
{ {
auto macro = _currentMacro; auto macro = _currentMacro;
if (!macro) { if (!macro) {
@@ -305,7 +395,7 @@ void MacroEdit::SetupMacroSegmentSelection(MacroSection type, int idx)
std::deque<std::shared_ptr<MacroSegment>> segments; std::deque<std::shared_ptr<MacroSegment>> segments;
switch (type) { switch (type) {
case MacroEdit::MacroSection::CONDITIONS: case MacroEdit::SegmentType::CONDITION:
setList = ui->conditionsList; setList = ui->conditionsList;
setIdx = &currentConditionIdx; setIdx = &currentConditionIdx;
segments = {macro->Conditions().begin(), segments = {macro->Conditions().begin(),
@@ -316,7 +406,7 @@ void MacroEdit::SetupMacroSegmentSelection(MacroSection type, int idx)
resetIdx1 = &currentActionIdx; resetIdx1 = &currentActionIdx;
resetIdx2 = &currentElseActionIdx; resetIdx2 = &currentElseActionIdx;
break; break;
case MacroEdit::MacroSection::ACTIONS: case MacroEdit::SegmentType::ACTION:
setList = ui->actionsList; setList = ui->actionsList;
setIdx = &currentActionIdx; setIdx = &currentActionIdx;
segments = {macro->Actions().begin(), macro->Actions().end()}; segments = {macro->Actions().begin(), macro->Actions().end()};
@@ -326,7 +416,7 @@ void MacroEdit::SetupMacroSegmentSelection(MacroSection type, int idx)
resetIdx1 = &currentConditionIdx; resetIdx1 = &currentConditionIdx;
resetIdx2 = &currentElseActionIdx; resetIdx2 = &currentElseActionIdx;
break; break;
case MacroEdit::MacroSection::ELSE_ACTIONS: case MacroEdit::SegmentType::ELSE_ACTION:
setList = ui->elseActionsList; setList = ui->elseActionsList;
setIdx = &currentElseActionIdx; setIdx = &currentElseActionIdx;
segments = {macro->ElseActions().begin(), segments = {macro->ElseActions().begin(),
@@ -366,7 +456,7 @@ static void runSegmentHighlightChecksHelper(MacroSegmentList *list)
// highlight its segments if required // highlight its segments if required
auto macroAction = dynamic_cast<MacroActionMacro *>(data.get()); auto macroAction = dynamic_cast<MacroActionMacro *>(data.get());
if (macroAction && if (macroAction &&
macroAction->_action == macroAction->GetAction() ==
MacroActionMacro::Action::NESTED_MACRO) { MacroActionMacro::Action::NESTED_MACRO) {
continue; continue;
} }
@@ -418,8 +508,7 @@ void MacroEdit::PopulateMacroActions(Macro &m, uint32_t afterIdx)
ui->actionsList->SetVisibilityCheckEnable(false); ui->actionsList->SetVisibilityCheckEnable(false);
for (; afterIdx < actions.size(); afterIdx++) { for (; afterIdx < actions.size(); afterIdx++) {
auto newEntry = new MacroActionEdit(this, &actions[afterIdx], auto newEntry = new MacroActionEdit(this, &actions[afterIdx]);
actions[afterIdx]->GetId());
ui->actionsList->Add(newEntry); ui->actionsList->Add(newEntry);
} }
@@ -444,8 +533,7 @@ void MacroEdit::PopulateMacroElseActions(Macro &m, uint32_t afterIdx)
ui->elseActionsList->SetVisibilityCheckEnable(false); ui->elseActionsList->SetVisibilityCheckEnable(false);
for (; afterIdx < actions.size(); afterIdx++) { for (; afterIdx < actions.size(); afterIdx++) {
auto newEntry = new MacroActionEdit(this, &actions[afterIdx], auto newEntry = new MacroActionEdit(this, &actions[afterIdx]);
actions[afterIdx]->GetId());
ui->elseActionsList->Add(newEntry); ui->elseActionsList->Add(newEntry);
} }
@@ -472,8 +560,7 @@ void MacroEdit::PopulateMacroConditions(Macro &m, uint32_t afterIdx)
for (; afterIdx < conditions.size(); afterIdx++) { for (; afterIdx < conditions.size(); afterIdx++) {
auto newEntry = new MacroConditionEdit( auto newEntry = new MacroConditionEdit(
this, &conditions[afterIdx], this, &conditions[afterIdx], root);
conditions[afterIdx]->GetId(), root);
ui->conditionsList->Add(newEntry); ui->conditionsList->Add(newEntry);
root = false; root = false;
} }
@@ -653,6 +740,18 @@ void MacroEdit::HighlightCondition(int idx, QColor color) const
ui->conditionsList->Highlight(idx, color); ui->conditionsList->Highlight(idx, color);
} }
void MacroEdit::ScrollAndFocusNewSegment(MacroSegmentList *list, int newIdx,
int *currentIdx)
{
QTimer::singleShot(0, this, [this, list, newIdx, currentIdx]() {
list->ensureWidgetVisible(list->WidgetAt(*currentIdx));
auto *widget = list->WidgetAt(newIdx);
if (widget) {
widget->FocusTypeSelection();
}
});
}
static void resetSegmentHighlights(MacroSegmentList *list) static void resetSegmentHighlights(MacroSegmentList *list)
{ {
MacroSegmentEdit *widget = nullptr; MacroSegmentEdit *widget = nullptr;
@@ -953,7 +1052,7 @@ void MacroEdit::UpMacroSegmentHotkey()
int conditionSize = macro->Conditions().size(); int conditionSize = macro->Conditions().size();
if (currentActionIdx == -1 && currentConditionIdx == -1) { if (currentActionIdx == -1 && currentConditionIdx == -1) {
if (lastInteracted == MacroSection::CONDITIONS) { if (lastInteracted == SegmentType::CONDITION) {
if (conditionSize == 0) { if (conditionSize == 0) {
MacroActionSelectionChanged(0); MacroActionSelectionChanged(0);
} else { } else {
@@ -1009,7 +1108,7 @@ void MacroEdit::DownMacroSegmentHotkey()
int conditionSize = macro->Conditions().size(); int conditionSize = macro->Conditions().size();
if (currentActionIdx == -1 && currentConditionIdx == -1) { if (currentActionIdx == -1 && currentConditionIdx == -1) {
if (lastInteracted == MacroSection::CONDITIONS) { if (lastInteracted == SegmentType::CONDITION) {
if (conditionSize == 0) { if (conditionSize == 0) {
MacroActionSelectionChanged(0); MacroActionSelectionChanged(0);
} else { } else {
@@ -1072,6 +1171,13 @@ void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id,
return; return;
} }
Macro *const parentMacro = macro->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
}
{ {
auto lock = LockContext(); auto lock = LockContext();
macro->Actions().emplace(macro->Actions().begin() + idx, macro->Actions().emplace(macro->Actions().begin() + idx,
@@ -1080,16 +1186,22 @@ void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id,
macro->Actions().at(idx)->Load(data); macro->Actions().at(idx)->Load(data);
} }
macro->Actions().at(idx)->PostLoad(); macro->Actions().at(idx)->PostLoad();
RunPostLoadSteps(); RunAndClearPostLoadSteps();
macro->UpdateActionIndices(); macro->UpdateActionIndices();
ui->actionsList->Insert( ui->actionsList->Insert(
idx, idx, new MacroActionEdit(this, &macro->Actions()[idx]));
new MacroActionEdit(this, &macro->Actions()[idx], id));
SetActionData(*macro); SetActionData(*macro);
} }
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
} else {
RegisterSegmentAddUndoRedo(macro, SegmentType::ACTION, idx);
}
HighlightAction(idx); HighlightAction(idx);
ui->actionsList->SetHelpMsgVisible(false); ui->actionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged()); emit(MacroSegmentOrderChanged());
ScrollAndFocusNewSegment(ui->actionsList, idx, &currentActionIdx);
} }
void MacroEdit::AddMacroAction(int idx) void MacroEdit::AddMacroAction(int idx)
@@ -1117,6 +1229,9 @@ void MacroEdit::AddMacroAction(int idx)
macro->Actions().at(idx - 1)->Save(data); macro->Actions().at(idx - 1)->Save(data);
} }
AddMacroAction(macro.get(), idx, id, data); AddMacroAction(macro.get(), idx, id, data);
if (idx < (int)macro->Actions().size()) {
macro->Actions().at(idx)->FixupVarMappingRefs();
}
} }
void MacroEdit::on_actionAdd_clicked() void MacroEdit::on_actionAdd_clicked()
@@ -1147,6 +1262,24 @@ void MacroEdit::RemoveMacroAction(int idx)
return; return;
} }
Macro *const parentMacro = macro->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
} else {
OBSDataAutoRelease segData = obs_data_create();
std::string segId;
{
auto lock = LockContext();
macro->Actions().at(idx)->Save(segData);
segId = macro->Actions().at(idx)->GetId();
}
RegisterSegmentRemoveUndoRedo(macro.get(), SegmentType::ACTION,
idx, segId, segData,
(int)Logic::Type::ROOT_NONE);
}
{ {
auto lock = LockContext(); auto lock = LockContext();
ui->actionsList->Remove(idx); ui->actionsList->Remove(idx);
@@ -1156,8 +1289,11 @@ void MacroEdit::RemoveMacroAction(int idx)
macro->UpdateActionIndices(); macro->UpdateActionIndices();
SetActionData(*macro); SetActionData(*macro);
} }
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
}
MacroActionSelectionChanged(-1); MacroActionSelectionChanged(-1);
lastInteracted = MacroSection::ACTIONS; lastInteracted = SegmentType::ACTION;
emit(MacroSegmentOrderChanged()); emit(MacroSegmentOrderChanged());
} }
@@ -1337,7 +1473,7 @@ void MacroEdit::MoveMacroActionDown(int idx)
void MacroEdit::MacroElseActionSelectionChanged(int idx) void MacroEdit::MacroElseActionSelectionChanged(int idx)
{ {
SetupMacroSegmentSelection(MacroSection::ELSE_ACTIONS, idx); SetupMacroSegmentSelection(SegmentType::ELSE_ACTION, idx);
} }
void MacroEdit::MacroElseActionReorder(int to, int from) void MacroEdit::MacroElseActionReorder(int to, int from)
@@ -1374,6 +1510,13 @@ void MacroEdit::AddMacroElseAction(Macro *macro, int idx, const std::string &id,
return; return;
} }
Macro *const parentMacro = macro->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
}
{ {
auto lock = LockContext(); auto lock = LockContext();
macro->ElseActions().emplace(macro->ElseActions().begin() + idx, macro->ElseActions().emplace(macro->ElseActions().begin() + idx,
@@ -1383,16 +1526,25 @@ void MacroEdit::AddMacroElseAction(Macro *macro, int idx, const std::string &id,
macro->ElseActions().at(idx)->Load(data); macro->ElseActions().at(idx)->Load(data);
} }
macro->ElseActions().at(idx)->PostLoad(); macro->ElseActions().at(idx)->PostLoad();
RunPostLoadSteps(); RunAndClearPostLoadSteps();
macro->UpdateElseActionIndices(); macro->UpdateElseActionIndices();
ui->elseActionsList->Insert( ui->elseActionsList->Insert(
idx, new MacroActionEdit( idx,
this, &macro->ElseActions()[idx], id)); new MacroActionEdit(this, &macro->ElseActions()[idx]));
SetElseActionData(*macro); SetElseActionData(*macro);
} }
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
} else {
RegisterSegmentAddUndoRedo(macro, SegmentType::ELSE_ACTION,
idx);
}
HighlightElseAction(idx); HighlightElseAction(idx);
ui->elseActionsList->SetHelpMsgVisible(false); ui->elseActionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged()); emit(MacroSegmentOrderChanged());
ScrollAndFocusNewSegment(ui->elseActionsList, idx,
&currentElseActionIdx);
} }
void MacroEdit::AddMacroElseAction(int idx) void MacroEdit::AddMacroElseAction(int idx)
@@ -1420,6 +1572,9 @@ void MacroEdit::AddMacroElseAction(int idx)
macro->ElseActions().at(idx - 1)->Save(data); macro->ElseActions().at(idx - 1)->Save(data);
} }
AddMacroElseAction(macro.get(), idx, id, data); AddMacroElseAction(macro.get(), idx, id, data);
if (idx < (int)macro->ElseActions().size()) {
macro->ElseActions().at(idx)->FixupVarMappingRefs();
}
} }
void MacroEdit::RemoveMacroElseAction(int idx) void MacroEdit::RemoveMacroElseAction(int idx)
@@ -1433,6 +1588,25 @@ void MacroEdit::RemoveMacroElseAction(int idx)
return; return;
} }
Macro *const parentMacro = macro->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
} else {
OBSDataAutoRelease segData = obs_data_create();
std::string segId;
{
auto lock = LockContext();
macro->ElseActions().at(idx)->Save(segData);
segId = macro->ElseActions().at(idx)->GetId();
}
RegisterSegmentRemoveUndoRedo(macro.get(),
SegmentType::ELSE_ACTION, idx,
segId, segData,
(int)Logic::Type::ROOT_NONE);
}
{ {
auto lock = LockContext(); auto lock = LockContext();
ui->elseActionsList->Remove(idx); ui->elseActionsList->Remove(idx);
@@ -1442,8 +1616,11 @@ void MacroEdit::RemoveMacroElseAction(int idx)
macro->UpdateElseActionIndices(); macro->UpdateElseActionIndices();
SetElseActionData(*macro); SetElseActionData(*macro);
} }
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
}
MacroElseActionSelectionChanged(-1); MacroElseActionSelectionChanged(-1);
lastInteracted = MacroSection::ELSE_ACTIONS; lastInteracted = SegmentType::ELSE_ACTION;
emit(MacroSegmentOrderChanged()); emit(MacroSegmentOrderChanged());
} }
@@ -1504,7 +1681,7 @@ void MacroEdit::MoveMacroElseActionDown(int idx)
void MacroEdit::MacroActionSelectionChanged(int idx) void MacroEdit::MacroActionSelectionChanged(int idx)
{ {
SetupMacroSegmentSelection(MacroSection::ACTIONS, idx); SetupMacroSegmentSelection(SegmentType::ACTION, idx);
} }
void MacroEdit::MacroActionReorder(int to, int from) void MacroEdit::MacroActionReorder(int to, int from)
@@ -1564,6 +1741,9 @@ void MacroEdit::AddMacroCondition(int idx)
macro->Conditions().at(idx - 1)->Save(data); macro->Conditions().at(idx - 1)->Save(data);
} }
AddMacroCondition(macro.get(), idx, id, data.Get(), logic); AddMacroCondition(macro.get(), idx, id, data.Get(), logic);
if (idx < (int)macro->Conditions().size()) {
macro->Conditions().at(idx)->FixupVarMappingRefs();
}
} }
void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id, void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
@@ -1574,6 +1754,13 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
return; return;
} }
Macro *const parentMacro = macro->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
}
{ {
auto lock = LockContext(); auto lock = LockContext();
auto cond = macro->Conditions().emplace( auto cond = macro->Conditions().emplace(
@@ -1583,18 +1770,25 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
macro->Conditions().at(idx)->Load(data); macro->Conditions().at(idx)->Load(data);
} }
macro->Conditions().at(idx)->PostLoad(); macro->Conditions().at(idx)->PostLoad();
RunPostLoadSteps(); RunAndClearPostLoadSteps();
(*cond)->SetLogicType(logic); (*cond)->SetLogicType(logic);
macro->UpdateConditionIndices(); macro->UpdateConditionIndices();
ui->conditionsList->Insert( ui->conditionsList->Insert(
idx, idx,
new MacroConditionEdit(this, &macro->Conditions()[idx], new MacroConditionEdit(this, &macro->Conditions()[idx],
id, idx == 0)); idx == 0));
SetConditionData(*macro); SetConditionData(*macro);
} }
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
} else {
RegisterSegmentAddUndoRedo(macro, SegmentType::CONDITION, idx);
}
HighlightCondition(idx); HighlightCondition(idx);
ui->conditionsList->SetHelpMsgVisible(false); ui->conditionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged()); emit(MacroSegmentOrderChanged());
ScrollAndFocusNewSegment(ui->conditionsList, idx, &currentConditionIdx);
} }
void MacroEdit::on_conditionAdd_clicked() void MacroEdit::on_conditionAdd_clicked()
@@ -1625,6 +1819,26 @@ void MacroEdit::RemoveMacroCondition(int idx)
return; return;
} }
Macro *const parentMacro = macro->GetNestedParentMacro();
OBSDataAutoRelease parentBeforeData;
if (parentMacro) {
parentBeforeData = obs_data_create();
parentMacro->Save(parentBeforeData);
} else {
OBSDataAutoRelease segData = obs_data_create();
std::string segId;
int logic;
{
auto lock = LockContext();
macro->Conditions().at(idx)->Save(segData);
segId = macro->Conditions().at(idx)->GetId();
logic = (int)macro->Conditions().at(idx)->GetLogicType();
}
RegisterSegmentRemoveUndoRedo(macro.get(),
SegmentType::CONDITION, idx,
segId, segData, logic);
}
{ {
auto lock = LockContext(); auto lock = LockContext();
ui->conditionsList->Remove(idx); ui->conditionsList->Remove(idx);
@@ -1639,8 +1853,11 @@ void MacroEdit::RemoveMacroCondition(int idx)
} }
SetConditionData(*macro); SetConditionData(*macro);
} }
if (parentMacro) {
RegisterMacroModifyUndoRedo(parentMacro, parentBeforeData);
}
MacroConditionSelectionChanged(-1); MacroConditionSelectionChanged(-1);
lastInteracted = MacroSection::CONDITIONS; lastInteracted = SegmentType::CONDITION;
emit(MacroSegmentOrderChanged()); emit(MacroSegmentOrderChanged());
} }
@@ -1765,7 +1982,7 @@ void MacroEdit::MoveMacroConditionDown(int idx)
void MacroEdit::MacroConditionSelectionChanged(int idx) void MacroEdit::MacroConditionSelectionChanged(int idx)
{ {
SetupMacroSegmentSelection(MacroSection::CONDITIONS, idx); SetupMacroSegmentSelection(SegmentType::CONDITION, idx);
} }
void MacroEdit::MacroConditionReorder(int to, int from) void MacroEdit::MacroConditionReorder(int to, int from)
@@ -1826,6 +2043,13 @@ bool MacroEdit::IsEmpty() const
ui->elseActionsList->IsEmpty(); ui->elseActionsList->IsEmpty();
} }
void MacroEdit::HideElseSection() const
{
ui->toggleElseActions->setVisible(false);
ui->macroElseActions->setMaximumHeight(0);
ui->macroElseActionSplitter->handle(1)->setCursor(Qt::ArrowCursor);
}
void MacroEdit::ShowAllMacroSections() void MacroEdit::ShowAllMacroSections()
{ {
if (!ElseSectionIsVisible()) { if (!ElseSectionIsVisible()) {

View File

@@ -8,18 +8,19 @@ namespace advss {
class Macro; class Macro;
class MacroSegment; class MacroSegment;
/*******************************************************************************
* Advanced Scene Switcher window
*******************************************************************************/
class MacroEdit : public QWidget { class MacroEdit : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
enum class SegmentType { CONDITION, ACTION, ELSE_ACTION };
MacroEdit(QWidget *parent, QStringList helpMsg = {}); MacroEdit(QWidget *parent, QStringList helpMsg = {});
void SetMacro(const std::shared_ptr<Macro> &); void SetMacro(const std::shared_ptr<Macro> &);
std::shared_ptr<Macro> GetMacro() const; std::shared_ptr<Macro> GetMacro() const;
void ClearSegmentWidgetCacheFor(Macro *) const; void ClearSegmentWidgetCacheFor(Macro *) const;
void SetControlsDisabled(bool disable) const; void SetControlsDisabled(bool disable) const;
void InsertSegmentWidget(SegmentType type, int idx);
void RemoveSegmentWidget(SegmentType type, int idx);
void HighlightAction(int idx, QColor color = QColor(Qt::green)) const; void HighlightAction(int idx, QColor color = QColor(Qt::green)) const;
void HighlightElseAction(int idx, void HighlightElseAction(int idx,
QColor color = QColor(Qt::green)) const; QColor color = QColor(Qt::green)) const;
@@ -37,6 +38,7 @@ public:
void PasteMacroSegment(); void PasteMacroSegment();
bool IsEmpty() const; bool IsEmpty() const;
void ShowAllMacroSections(); void ShowAllMacroSections();
void HideElseSection() const;
private slots: private slots:
void on_conditionAdd_clicked(); void on_conditionAdd_clicked();
@@ -114,12 +116,12 @@ protected:
bool eventFilter(QObject *obj, QEvent *event) override; bool eventFilter(QObject *obj, QEvent *event) override;
private: private:
enum class MacroSection { CONDITIONS, ACTIONS, ELSE_ACTIONS };
void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0); void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0);
void PopulateMacroElseActions(Macro &m, uint32_t afterIdx = 0); void PopulateMacroElseActions(Macro &m, uint32_t afterIdx = 0);
void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0); void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0);
void SetupMacroSegmentSelection(MacroSection type, int idx); void ScrollAndFocusNewSegment(MacroSegmentList *list, int newIdx,
int *currentIdx);
void SetupMacroSegmentSelection(SegmentType type, int idx);
void void
SetupContextMenu(const QPoint &pos, SetupContextMenu(const QPoint &pos,
const std::function<void(MacroEdit *, int)> &remove, const std::function<void(MacroEdit *, int)> &remove,
@@ -131,7 +133,7 @@ private:
void RunSegmentHighlightChecks(); void RunSegmentHighlightChecks();
bool ElseSectionIsVisible() const; bool ElseSectionIsVisible() const;
MacroSection lastInteracted = MacroSection::CONDITIONS; SegmentType lastInteracted = SegmentType::CONDITION;
int currentConditionIdx = -1; int currentConditionIdx = -1;
int currentActionIdx = -1; int currentActionIdx = -1;
int currentElseActionIdx = -1; int currentElseActionIdx = -1;

View File

@@ -0,0 +1,89 @@
#include "macro-export-extensions.hpp"
#include "action-queue.hpp"
#include "variable.hpp"
#include <obs.hpp>
namespace advss {
static bool setup();
static bool setupDone = setup();
static bool setup()
{
// --- Variables ---
AddMacroExportExtension(
{"AdvSceneSwitcher.macroTab.export.variables", "variables",
[](obs_data_t *data, const QStringList &selectedIds) {
if (selectedIds.isEmpty()) {
SaveVariables(data);
return;
}
OBSDataArrayAutoRelease array =
obs_data_array_create();
for (const auto &v : GetVariables()) {
const QString name =
QString::fromStdString(v->Name());
if (!selectedIds.contains(name)) {
continue;
}
OBSDataAutoRelease item = obs_data_create();
v->Save(item);
obs_data_array_push_back(array, item);
}
obs_data_set_array(data, "variables", array);
},
[](obs_data_t *data, const QStringList &) {
ImportVariables(data);
},
[]() -> QList<QPair<QString, QString>> {
QList<QPair<QString, QString>> items;
for (const auto &v : GetVariables()) {
const QString name =
QString::fromStdString(v->Name());
items.append({name, name});
}
return items;
}});
// --- Action Queues ---
AddMacroExportExtension(
{"AdvSceneSwitcher.macroTab.export.actionQueues",
"actionQueues",
[](obs_data_t *data, const QStringList &selectedIds) {
if (selectedIds.isEmpty()) {
SaveActionQueues(data);
return;
}
OBSDataArrayAutoRelease array =
obs_data_array_create();
for (const auto &q : GetActionQueues()) {
const QString name =
QString::fromStdString(q->Name());
if (!selectedIds.contains(name)) {
continue;
}
OBSDataAutoRelease item = obs_data_create();
q->Save(item);
obs_data_array_push_back(array, item);
}
obs_data_set_array(data, "actionQueues", array);
},
[](obs_data_t *data, const QStringList &) {
ImportQueues(data);
},
[]() -> QList<QPair<QString, QString>> {
QList<QPair<QString, QString>> items;
for (const auto &q : GetActionQueues()) {
const QString name =
QString::fromStdString(q->Name());
items.append({name, name});
}
return items;
}});
return true;
}
} // namespace advss

View File

@@ -0,0 +1,17 @@
#include "macro-export-extensions.hpp"
namespace advss {
static std::vector<MacroExportExtension> extensions;
void AddMacroExportExtension(const MacroExportExtension &ext)
{
extensions.emplace_back(ext);
}
const std::vector<MacroExportExtension> &GetMacroExportExtensions()
{
return extensions;
}
} // namespace advss

View File

@@ -0,0 +1,41 @@
#pragma once
#include "export-symbol-helper.hpp"
#include <functional>
#include <obs-data.h>
#include <QList>
#include <QPair>
#include <QString>
#include <vector>
namespace advss {
struct MacroExportExtension {
const char *displayNameKey;
const char *jsonKey;
// Export callback.
// Write content into 'data'. 'selectedIds' contains the item IDs the
// user explicitly checked; if empty (no per-item widget provided) save
// everything.
std::function<void(obs_data_t *data, const QStringList &selectedIds)>
save;
// Import callback.
// Read content from 'data'. 'selectedIds' contains the item IDs the
// user explicitly checked; if empty import every item found in 'data'.
std::function<void(obs_data_t *data, const QStringList &selectedIds)>
load;
// Optional: enumerate items available for export as (id, displayName)
// pairs. When nullptr the extension appears as a single all-or-nothing
// checkbox in the export dialog.
std::function<QList<QPair<QString, QString>>()> getExportItems =
nullptr;
};
EXPORT void AddMacroExportExtension(const MacroExportExtension &ext);
const std::vector<MacroExportExtension> &GetMacroExportExtensions();
} // namespace advss

View File

@@ -1,22 +1,183 @@
#include "macro-export-import-dialog.hpp" #include "macro-export-import-dialog.hpp"
#include "macro-export-extensions.hpp"
#include "obs-module-helper.hpp" #include "obs-module-helper.hpp"
#include "section.hpp"
#include <obs.hpp> #include <obs.hpp>
#include <QLayout>
#include <QLabel>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QLabel>
#include <QLayout>
#include <QScrollArea>
#include <QSplitter>
#include <algorithm>
#include <numeric>
namespace advss { namespace advss {
static bool usePlainText = false; static bool usePlainText = false;
MacroExportImportDialog::MacroExportImportDialog(Type type) // ---------------------------------------------------------------------------
// Compress / decompress helpers
// ---------------------------------------------------------------------------
static QString compressMacroString(const QString &input)
{
QByteArray inputData = input.toUtf8();
auto compressedData = qCompress(inputData);
QByteArray encodedData = compressedData.toBase64();
return QString::fromUtf8(encodedData);
}
static QString decompressMacroString(const QString &input)
{
QByteArray encodedData = input.toUtf8();
QByteArray compressedData = QByteArray::fromBase64(encodedData);
auto outputData = qUncompress(compressedData);
return QString::fromUtf8(outputData);
}
static bool isValidData(const QString &json)
{
OBSDataAutoRelease data =
obs_data_create_from_json(json.toStdString().c_str());
return !!data;
}
// ---------------------------------------------------------------------------
// Extension section
// ---------------------------------------------------------------------------
QWidget *MacroExportImportDialog::BuildExtensionWidget()
{
auto wrapper = new QWidget(this);
auto wrapperLayout = new QVBoxLayout(wrapper);
wrapperLayout->setContentsMargins(0, 0, 0, 0);
wrapperLayout->setSpacing(4);
wrapperLayout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.macroTab.export.additionalContent")));
auto outerContent = new QWidget();
auto outerLayout = new QVBoxLayout(outerContent);
outerLayout->setContentsMargins(4, 4, 4, 4);
outerLayout->setSpacing(4);
const auto &extensions = GetMacroExportExtensions();
std::vector<int> order(extensions.size());
std::iota(order.begin(), order.end(), 0);
std::sort(order.begin(), order.end(), [&](int a, int b) {
return QString::localeAwareCompare(
obs_module_text(extensions[a].displayNameKey),
obs_module_text(extensions[b].displayNameKey)) <
0;
});
for (int extIdx : order) {
const auto &ext = extensions[extIdx];
ExtensionUI ui;
// Simple all-or-nothing checkbox (no sub-items).
if (!ext.getExportItems) {
ui.mainCheck = new QCheckBox(
obs_module_text(ext.displayNameKey),
outerContent);
ui.mainCheck->setChecked(false);
connect(ui.mainCheck, &QCheckBox::stateChanged, this,
&MacroExportImportDialog::UpdateExportString);
outerLayout->addWidget(ui.mainCheck);
_extensionUIs.append(std::move(ui));
_extensionOrder.push_back(extIdx);
continue;
}
// Extensions with per-item selection get their own inner
// Section: the main checkbox sits in the header (next to the
// toggle arrow) and the per-item checkboxes live in the
// collapsible content area.
auto items = ext.getExportItems();
if (items.isEmpty()) {
continue;
}
std::sort(items.begin(), items.end(),
[](const QPair<QString, QString> &a,
const QPair<QString, QString> &b) {
return QString::localeAwareCompare(
a.second, b.second) < 0;
});
auto innerSection = new Section(200, outerContent);
ui.mainCheck =
new QCheckBox(obs_module_text(ext.displayNameKey));
ui.mainCheck->setChecked(false);
innerSection->AddHeaderWidget(ui.mainCheck);
auto subWidget = new QWidget();
auto subLayout = new QVBoxLayout(subWidget);
subLayout->setContentsMargins(4, 2, 4, 2);
subLayout->setSpacing(2);
for (const auto &[id, displayName] : items) {
auto cb = new QCheckBox(displayName, subWidget);
cb->setChecked(false);
subLayout->addWidget(cb);
ui.itemChecks.append({id, cb});
connect(cb, &QCheckBox::stateChanged, this,
&MacroExportImportDialog::UpdateExportString);
}
// Main checkbox toggles all sub-items.
// Capture uiIdx (position in _extensionUIs after append).
const int uiIdx = _extensionUIs.size();
connect(ui.mainCheck, &QCheckBox::stateChanged, this,
[this, uiIdx](int state) {
if (state == Qt::PartiallyChecked)
return;
const bool checked = (state == Qt::Checked);
if (uiIdx < _extensionUIs.size()) {
for (auto &[id, cb] :
_extensionUIs[uiIdx].itemChecks) {
QSignalBlocker b(cb);
cb->setChecked(checked);
}
}
UpdateExportString();
});
innerSection->SetContent(subWidget, true);
outerLayout->addWidget(innerSection);
_extensionUIs.append(std::move(ui));
_extensionOrder.push_back(extIdx);
}
outerLayout->addStretch();
auto scrollArea = new QScrollArea(wrapper);
scrollArea->setWidget(outerContent);
scrollArea->setWidgetResizable(true);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setFrameShape(QFrame::NoFrame);
wrapperLayout->addWidget(scrollArea);
return wrapper;
}
// ---------------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------------
MacroExportImportDialog::MacroExportImportDialog(Type type,
const QString &baseJson)
: QDialog(nullptr), : QDialog(nullptr),
_baseJson(baseJson),
_importExportString(new QPlainTextEdit(this)), _importExportString(new QPlainTextEdit(this)),
_usePlainText(new QCheckBox(obs_module_text( _usePlainText(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.export.usePlainText"))) "AdvSceneSwitcher.macroTab.export.usePlainText")))
{ {
_importExportString->setReadOnly(type == Type::EXPORT_MACRO); _importExportString->setReadOnly(type == Type::EXPORT_MACRO);
auto label = new QLabel(obs_module_text( auto label = new QLabel(obs_module_text(
type == Type::EXPORT_MACRO type == Type::EXPORT_MACRO
? "AdvSceneSwitcher.macroTab.export.info" ? "AdvSceneSwitcher.macroTab.export.info"
@@ -39,40 +200,90 @@ MacroExportImportDialog::MacroExportImportDialog(Type type)
connect(_usePlainText, &QCheckBox::stateChanged, this, connect(_usePlainText, &QCheckBox::stateChanged, this,
&MacroExportImportDialog::UsePlainTextChanged); &MacroExportImportDialog::UsePlainTextChanged);
auto layout = new QVBoxLayout; auto layout = new QVBoxLayout(this);
layout->addWidget(label);
layout->addWidget(_importExportString); if (type == Type::EXPORT_MACRO && !GetMacroExportExtensions().empty()) {
layout->addWidget(_usePlainText); auto splitter = new QSplitter(Qt::Vertical, this);
splitter->setChildrenCollapsible(false);
splitter->addWidget(BuildExtensionWidget());
auto textWidget = new QWidget(splitter);
auto textLayout = new QVBoxLayout(textWidget);
textLayout->setContentsMargins(0, 0, 0, 0);
textLayout->addWidget(label);
textLayout->addWidget(_importExportString);
textLayout->addWidget(_usePlainText);
splitter->addWidget(textWidget);
layout->addWidget(splitter);
} else {
layout->addWidget(label);
layout->addWidget(_importExportString);
layout->addWidget(_usePlainText);
}
layout->addWidget(buttons); layout->addWidget(buttons);
setLayout(layout); setLayout(layout);
setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle")); setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
if (type == Type::EXPORT_MACRO) {
RefreshExportText();
}
} }
QString compressMacroString(const QString &input) // ---------------------------------------------------------------------------
// Export string helpers
// ---------------------------------------------------------------------------
QString MacroExportImportDialog::BuildExportJson() const
{ {
QByteArray inputData = input.toUtf8(); OBSDataAutoRelease data =
auto compressedData = qCompress(inputData); obs_data_create_from_json(_baseJson.toStdString().c_str());
QByteArray encodedData = compressedData.toBase64(); if (!data) {
return QString::fromUtf8(encodedData); return _baseJson;
}
const auto &extensions = GetMacroExportExtensions();
for (int i = 0; i < (int)_extensionUIs.size(); ++i) {
const auto &ui = _extensionUIs[i];
QStringList selectedIds;
if (ui.itemChecks.isEmpty()) {
if (!ui.mainCheck->isChecked()) {
continue;
}
} else {
for (const auto &[id, cb] : ui.itemChecks) {
if (cb->isChecked()) {
selectedIds << id;
}
}
if (selectedIds.isEmpty()) {
continue;
}
}
extensions[_extensionOrder[i]].save(data, selectedIds);
}
const char *json = obs_data_get_json(data);
return json ? QString::fromUtf8(json) : QString{};
} }
QString decompressMacroString(const QString &input) void MacroExportImportDialog::RefreshExportText()
{ {
QByteArray encodedData = input.toUtf8(); const QString json = BuildExportJson();
QByteArray compressedData = QByteArray::fromBase64(encodedData); if (usePlainText) {
auto outputData = qUncompress(compressedData); _importExportString->setPlainText(json);
return QString::fromUtf8(outputData); } else {
_importExportString->setPlainText(compressMacroString(json));
}
} }
void MacroExportImportDialog::ExportMacros(const QString &json) void MacroExportImportDialog::UpdateExportString()
{ {
MacroExportImportDialog dialog( RefreshExportText();
MacroExportImportDialog::Type::EXPORT_MACRO);
dialog._importExportString->setPlainText(compressMacroString(json));
dialog.adjustSize();
dialog.updateGeometry();
dialog.exec();
} }
void MacroExportImportDialog::UsePlainTextChanged(int value) void MacroExportImportDialog::UsePlainTextChanged(int value)
@@ -90,26 +301,45 @@ void MacroExportImportDialog::UsePlainTextChanged(int value)
usePlainText = value; usePlainText = value;
} }
static bool isValidData(const QString &json) // ---------------------------------------------------------------------------
// Static entry points
// ---------------------------------------------------------------------------
void MacroExportImportDialog::ExportMacros(obs_data_t *macroData)
{ {
OBSDataAutoRelease data = const char *rawJson = obs_data_get_json(macroData);
obs_data_create_from_json(json.toStdString().c_str()); const QString baseJson = rawJson ? QString::fromUtf8(rawJson)
return !!data; : QString{};
MacroExportImportDialog dialog(Type::EXPORT_MACRO, baseJson);
dialog.adjustSize();
dialog.updateGeometry();
dialog.exec();
} }
bool MacroExportImportDialog::ImportMacros(QString &json) bool MacroExportImportDialog::ImportMacros(QString &json)
{ {
MacroExportImportDialog dialog( MacroExportImportDialog dialog(Type::IMPORT_MACRO);
MacroExportImportDialog::Type::IMPORT_MACRO); if (dialog.exec() != QDialog::Accepted) {
if (dialog.exec() == QDialog::Accepted) { return false;
json = decompressMacroString(
dialog._importExportString->toPlainText());
if (!isValidData(json)) { // Fallback to support raw json format
json = dialog._importExportString->toPlainText();
}
return true;
} }
return false;
json = decompressMacroString(dialog._importExportString->toPlainText());
if (!isValidData(json)) {
// Fallback: support raw (uncompressed) JSON pasted directly.
json = dialog._importExportString->toPlainText();
}
// Invoke all extension load callbacks.
OBSDataAutoRelease data =
obs_data_create_from_json(json.toStdString().c_str());
if (data) {
for (const auto &ext : GetMacroExportExtensions()) {
ext.load(data, {});
}
}
return true;
} }
} // namespace advss } // namespace advss

View File

@@ -1,25 +1,47 @@
#pragma once #pragma once
#include <QCheckBox> #include <QCheckBox>
#include <QDialog> #include <QDialog>
#include <QList>
#include <QPlainTextEdit> #include <QPlainTextEdit>
#include <obs-data.h>
#include <vector>
namespace advss { namespace advss {
class MacroExportImportDialog : public QDialog { class MacroExportImportDialog : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
enum class Type { EXPORT_MACRO, IMPORT_MACRO }; enum class Type { EXPORT_MACRO, IMPORT_MACRO };
MacroExportImportDialog(Type type);
static void ExportMacros(const QString &json); static void ExportMacros(obs_data_t *macroData);
static bool ImportMacros(QString &json); static bool ImportMacros(QString &json);
private slots: private slots:
void UsePlainTextChanged(int); void UsePlainTextChanged(int);
void UpdateExportString();
private: private:
explicit MacroExportImportDialog(Type type,
const QString &baseJson = {});
// Per-extension widgets
struct ExtensionUI {
QCheckBox *mainCheck = nullptr;
// (itemId, checkbox) pairs - empty when no per-item selection.
QList<QPair<QString, QCheckBox *>> itemChecks;
};
QWidget *BuildExtensionWidget();
QString BuildExportJson() const;
void RefreshExportText();
QString _baseJson;
QPlainTextEdit *_importExportString; QPlainTextEdit *_importExportString;
QCheckBox *_usePlainText; QCheckBox *_usePlainText;
QList<ExtensionUI> _extensionUIs;
std::vector<int> _extensionOrder;
}; };
} // namespace advss } // namespace advss

View File

@@ -1,5 +1,6 @@
#include "macro-helpers.hpp" #include "macro-helpers.hpp"
#include "macro.hpp" #include "macro.hpp"
#include "macro-action-macro.hpp"
#include "plugin-state-helpers.hpp" #include "plugin-state-helpers.hpp"
namespace advss { namespace advss {
@@ -8,6 +9,87 @@ static std::atomic_bool abortMacroWait = {false};
static std::atomic_bool macroSceneSwitched = {false}; static std::atomic_bool macroSceneSwitched = {false};
static std::atomic_int shutdownConditionCount = {0}; static std::atomic_int shutdownConditionCount = {0};
static void appendNestedMacros(std::deque<std::shared_ptr<Macro>> &macros,
Macro *macro)
{
if (!macro) {
return;
}
const auto iterate = [&macros](const std::deque<
std::shared_ptr<MacroAction>> &actions) {
for (const auto &action : actions) {
const auto nestedMacroAction =
dynamic_cast<MacroActionMacro *>(action.get());
if (!nestedMacroAction) {
continue;
}
macros.push_back(nestedMacroAction->_nestedMacro);
appendNestedMacros(
macros, nestedMacroAction->_nestedMacro.get());
}
};
iterate(macro->Actions());
iterate(macro->ElseActions());
}
std::deque<std::shared_ptr<Macro>> &GetTopLevelMacros()
{
static std::deque<std::shared_ptr<Macro>> macros;
return macros;
}
std::deque<std::shared_ptr<Macro>> &GetTemporaryMacros()
{
static std::deque<std::shared_ptr<Macro>> tempMacros;
return tempMacros;
}
std::deque<std::shared_ptr<Macro>> GetAllMacros()
{
auto macros = GetTopLevelMacros();
for (const auto &topLevelMacro : macros) {
appendNestedMacros(macros, topLevelMacro.get());
}
const auto &tempMacros = GetTemporaryMacros();
macros.insert(macros.end(), tempMacros.begin(), tempMacros.end());
for (const auto &tempMacro : tempMacros) {
appendNestedMacros(macros, tempMacro.get());
}
return macros;
}
Macro *GetMacroByName(const char *name)
{
for (const auto &m : GetTopLevelMacros()) {
if (m->Name() == name) {
return m.get();
}
}
return nullptr;
}
Macro *GetMacroByQString(const QString &name)
{
return GetMacroByName(name.toUtf8().constData());
}
std::weak_ptr<Macro> GetWeakMacroByName(const char *name)
{
for (const auto &m : GetTopLevelMacros()) {
if (m->Name() == name) {
return m;
}
}
return {};
}
std::optional<std::deque<std::shared_ptr<MacroAction>>> std::optional<std::deque<std::shared_ptr<MacroAction>>>
GetMacroActions(Macro *macro) GetMacroActions(Macro *macro)
{ {
@@ -17,6 +99,15 @@ GetMacroActions(Macro *macro)
return macro->Actions(); return macro->Actions();
} }
std::optional<std::deque<std::shared_ptr<MacroAction>>>
GetMacroElseActions(Macro *macro)
{
if (!macro) {
return {};
}
return macro->ElseActions();
}
std::optional<std::deque<std::shared_ptr<MacroCondition>>> std::optional<std::deque<std::shared_ptr<MacroCondition>>>
GetMacroConditions(Macro *macro) GetMacroConditions(Macro *macro)
{ {
@@ -26,9 +117,32 @@ GetMacroConditions(Macro *macro)
return macro->Conditions(); return macro->Conditions();
} }
std::string_view GetSceneSwitchActionId() bool IsGroupMacro(Macro *macro)
{ {
return MacroAction::GetDefaultID(); return macro && macro->IsGroup();
}
std::vector<std::shared_ptr<Macro>> GetGroupMacroEntries(Macro *macro)
{
if (!macro || !macro->IsGroup()) {
return {};
}
std::vector<std::shared_ptr<Macro>> entries;
entries.reserve(macro->GroupSize());
const auto &macros = GetTopLevelMacros();
for (auto it = macros.begin(); it < macros.end(); it++) {
if ((*it)->Name() != macro->Name()) {
continue;
}
for (uint32_t i = 1; i <= macro->GroupSize(); i++) {
entries.emplace_back(*std::next(it, i));
}
break;
}
return entries;
} }
std::condition_variable &GetMacroWaitCV() std::condition_variable &GetMacroWaitCV()
@@ -100,6 +214,20 @@ bool MacroIsPaused(const Macro *macro)
return macro ? macro->Paused() : true; return macro ? macro->Paused() : true;
} }
void SetMacroPaused(Macro *macro, bool paused)
{
if (macro) {
macro->SetPaused(paused);
}
}
void StopMacro(Macro *macro)
{
if (macro) {
macro->Stop();
}
}
bool MacroWasPausedSince( bool MacroWasPausedSince(
const Macro *macro, const Macro *macro,
const std::chrono::high_resolution_clock::time_point &time) const std::chrono::high_resolution_clock::time_point &time)
@@ -123,9 +251,20 @@ void AddMacroHelperThread(Macro *macro, std::thread &&newThread)
macro->AddHelperThread(std::move(newThread)); macro->AddHelperThread(std::move(newThread));
} }
bool RunMacroActions(Macro *macro) bool RunMacroActions(Macro *macro, bool forceParallel, bool ignorePause)
{ {
return macro && macro->PerformActions(true); return macro && macro->PerformActions(true, forceParallel, ignorePause);
}
bool RunMacroElseActions(Macro *macro, bool forceParallel, bool ignorePause)
{
return macro &&
macro->PerformActions(false, forceParallel, ignorePause);
}
bool CheckMacroConditions(Macro *macro, bool ignorePause)
{
return macro && macro->CheckConditions(ignorePause);
} }
void ResetMacroConditionTimers(Macro *macro) void ResetMacroConditionTimers(Macro *macro)
@@ -144,20 +283,42 @@ void ResetMacroRunCount(Macro *macro)
macro->ResetRunCount(); macro->ResetRunCount();
} }
bool IsValidMacroSegmentIndex(const Macro *m, const int idx, bool isCondition) bool IsValidActionIndex(const Macro *m, const int idx)
{ {
if (!m || idx < 0) { if (!m || idx < 0) {
return false; return false;
} }
if (isCondition) {
if (idx >= (int)m->Conditions().size()) { if (idx >= (int)m->Actions().size()) {
return false; return false;
}
} else {
if (idx >= (int)m->Actions().size()) {
return false;
}
} }
return true;
}
bool IsValidElseActionIndex(const Macro *m, const int idx)
{
if (!m || idx < 0) {
return false;
}
if (idx >= (int)m->ElseActions().size()) {
return false;
}
return true;
}
bool IsValidConditionIndex(const Macro *m, const int idx)
{
if (!m || idx < 0) {
return false;
}
if (idx >= (int)m->Conditions().size()) {
return false;
}
return true; return true;
} }

View File

@@ -1,12 +1,13 @@
#pragma once #pragma once
#include "export-symbol-helper.hpp" #include "export-symbol-helper.hpp"
#include <QString>
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
#include <condition_variable> #include <condition_variable>
#include <deque> #include <deque>
#include <optional> #include <optional>
#include <string_view>
#include <thread> #include <thread>
struct obs_data; struct obs_data;
@@ -18,16 +19,25 @@ class Macro;
class MacroAction; class MacroAction;
class MacroCondition; class MacroCondition;
EXPORT std::deque<std::shared_ptr<Macro>> &GetMacros(); static const int macro_func = 10;
EXPORT std::deque<std::shared_ptr<Macro>> &GetTopLevelMacros();
std::deque<std::shared_ptr<Macro>> &GetTemporaryMacros();
EXPORT std::deque<std::shared_ptr<Macro>> GetAllMacros();
Macro *GetMacroByName(const char *name);
Macro *GetMacroByQString(const QString &name);
std::weak_ptr<Macro> GetWeakMacroByName(const char *name);
EXPORT std::optional<std::deque<std::shared_ptr<MacroAction>>> EXPORT std::optional<std::deque<std::shared_ptr<MacroAction>>>
GetMacroActions(Macro *); GetMacroActions(Macro *);
EXPORT std::optional<std::deque<std::shared_ptr<MacroAction>>>
GetMacroElseActions(Macro *);
EXPORT std::optional<std::deque<std::shared_ptr<MacroCondition>>> EXPORT std::optional<std::deque<std::shared_ptr<MacroCondition>>>
GetMacroConditions(Macro *); GetMacroConditions(Macro *);
std::string_view GetSceneSwitchActionId(); EXPORT bool IsGroupMacro(Macro *);
EXPORT std::vector<std::shared_ptr<Macro>> GetGroupMacroEntries(Macro *);
constexpr auto macro_func = 10;
EXPORT std::condition_variable &GetMacroWaitCV(); EXPORT std::condition_variable &GetMacroWaitCV();
EXPORT std::condition_variable &GetMacroTransitionCV(); EXPORT std::condition_variable &GetMacroTransitionCV();
@@ -48,6 +58,8 @@ LastMacroConditionCheckTime(const Macro *);
EXPORT bool MacroIsStopped(const Macro *); EXPORT bool MacroIsStopped(const Macro *);
EXPORT bool MacroIsPaused(const Macro *); EXPORT bool MacroIsPaused(const Macro *);
EXPORT void SetMacroPaused(Macro *, bool paused);
EXPORT void StopMacro(Macro *);
EXPORT bool EXPORT bool
MacroWasPausedSince(const Macro *, MacroWasPausedSince(const Macro *,
const std::chrono::high_resolution_clock::time_point &); const std::chrono::high_resolution_clock::time_point &);
@@ -56,10 +68,16 @@ EXPORT bool MacroWasCheckedSinceLastStart(const Macro *);
EXPORT void AddMacroHelperThread(Macro *, std::thread &&); EXPORT void AddMacroHelperThread(Macro *, std::thread &&);
EXPORT bool CheckMacros(); EXPORT bool CheckMacros();
EXPORT bool CheckMacroConditions(Macro *, bool ignorePause = false);
EXPORT bool RunMacroActions(Macro *); EXPORT bool RunMacroActions(Macro *, bool forceParallel = false,
bool ignorePause = false);
EXPORT bool RunMacroElseActions(Macro *, bool forceParallel = false,
bool ignorePause = false);
EXPORT bool RunMacros(); EXPORT bool RunMacros();
void StopAllMacros();
void SignalStopAllMacros();
void WaitForAllMacros();
EXPORT void LoadMacros(obs_data_t *obj); EXPORT void LoadMacros(obs_data_t *obj);
EXPORT void SaveMacros(obs_data_t *obj); EXPORT void SaveMacros(obs_data_t *obj);
@@ -68,6 +86,8 @@ EXPORT void InvalidateMacroTempVarValues();
EXPORT void ResetMacroConditionTimers(Macro *); EXPORT void ResetMacroConditionTimers(Macro *);
EXPORT void ResetMacroRunCount(Macro *); EXPORT void ResetMacroRunCount(Macro *);
bool IsValidMacroSegmentIndex(const Macro *m, const int idx, bool isCondition); bool IsValidActionIndex(const Macro *m, const int idx);
bool IsValidElseActionIndex(const Macro *m, const int idx);
bool IsValidConditionIndex(const Macro *m, const int idx);
} // namespace advss } // namespace advss

View File

@@ -1,5 +1,8 @@
#include "macro-ref.hpp" #include "macro-ref.hpp"
#include "macro-helpers.hpp"
#include "macro.hpp" #include "macro.hpp"
#include "plugin-state-helpers.hpp"
namespace advss { namespace advss {
@@ -19,6 +22,7 @@ void MacroRef::Load(obs_data_t *obj)
auto name = obs_data_get_string(obj, "macro"); auto name = obs_data_get_string(obj, "macro");
_postLoadName = name; _postLoadName = name;
_macro = GetWeakMacroByName(name); _macro = GetWeakMacroByName(name);
AddPostLoadStep([this]() { MacroRef::PostLoad(); });
} }
void MacroRef::PostLoad() void MacroRef::PostLoad()
@@ -71,11 +75,11 @@ void LoadMacroList(obs_data_t *obj, std::vector<MacroRef> &macros,
{ {
obs_data_array_t *array = obs_data_get_array(obj, name.c_str()); obs_data_array_t *array = obs_data_get_array(obj, name.c_str());
size_t count = obs_data_array_count(array); size_t count = obs_data_array_count(array);
macros.reserve(macros.size() + count);
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(array, i); obs_data_t *array_obj = obs_data_array_item(array, i);
MacroRef ref; macros.emplace_back();
ref.Load(array_obj); macros.back().Load(array_obj);
macros.push_back(ref);
obs_data_release(array_obj); obs_data_release(array_obj);
} }
obs_data_array_release(array); obs_data_array_release(array);

View File

@@ -108,7 +108,7 @@ void MacroRunButton::Pressed()
const bool abortMacro = DisplayMessage( const bool abortMacro = DisplayMessage(
err.arg(QString::fromStdString(macro->Name())), true); err.arg(QString::fromStdString(macro->Name())), true);
if (abortMacro) { if (abortMacro) {
macro->Stop(); macro->SignalStop();
} }
} }

323
lib/macro/macro-search.cpp Normal file
View File

@@ -0,0 +1,323 @@
#include "macro-search.hpp"
#include "layout-helpers.hpp"
#include "macro.hpp"
#include "macro-action-factory.hpp"
#include "macro-condition-factory.hpp"
#include "macro-helpers.hpp"
#include "macro-signals.hpp"
#include "plugin-state-helpers.hpp"
#include "ui-helpers.hpp"
#include <obs.hpp>
#include <QComboBox>
#include <QLayout>
#include <QLineEdit>
#include <QPushButton>
namespace advss {
static bool searchEnabled = false;
static bool setup();
static bool setupDone = setup();
static QLayout *searchLayout = nullptr;
static QComboBox *searchType = nullptr;
static RegexConfigWidget *searchRegex = nullptr;
static QPushButton *showSettings = nullptr;
static std::function<void()> refreshFilter;
static void save(obs_data_t *data)
{
GetMacroSearchSettings().Save(data, "macroSearchSettings");
}
static void load(obs_data_t *data)
{
GetMacroSearchSettings().Load(data, "macroSearchSettings");
}
static bool setup()
{
AddSaveStep(save);
AddLoadStep(load);
return true;
}
void MacroSearchSettings::Save(obs_data_t *data, const char *name)
{
OBSDataAutoRelease settings = obs_data_create();
obs_data_set_bool(settings, "showAlways", showAlways);
obs_data_set_int(settings, "searchType", static_cast<int>(searchType));
obs_data_set_string(settings, "searchString", searchString.c_str());
regex.Save(settings);
obs_data_set_obj(data, name, settings);
}
void MacroSearchSettings::Load(obs_data_t *data, const char *name)
{
OBSDataAutoRelease settings = obs_data_get_obj(data, name);
showAlways = obs_data_get_bool(settings, "showAlways");
searchType = static_cast<SearchType>(
obs_data_get_int(settings, "searchType"));
searchString = obs_data_get_string(settings, "searchString");
regex.Load(settings);
}
static void showAdvancedSearchSettings(bool show)
{
assert(searchType);
assert(searchRegex);
searchType->setVisible(show);
searchRegex->setVisible(show);
}
static bool shouldShowSearch()
{
static const int showSearchThreshold = 10;
return GetMacroSearchSettings().showAlways ||
GetTopLevelMacros().size() >= showSearchThreshold;
}
void CheckMacroSearchVisibility()
{
assert(searchLayout);
assert(searchType);
assert(searchRegex);
assert(showSettings);
searchEnabled = shouldShowSearch();
SetLayoutVisible(searchLayout, searchEnabled);
showAdvancedSearchSettings(false);
showSettings->setChecked(false);
refreshFilter();
}
void SetupMacroSearchWidgets(QLayout *searchLayoutArg, QLineEdit *searchText,
QPushButton *searchClear, QComboBox *searchTypeArg,
RegexConfigWidget *searchRegexArg,
QPushButton *showSettingsArg,
const std::function<void()> &refreshArg)
{
searchLayout = searchLayoutArg;
searchType = searchTypeArg;
searchRegex = searchRegexArg;
refreshFilter = refreshArg;
showSettings = showSettingsArg;
searchClear->setMaximumWidth(22);
SetButtonIcon(searchClear, GetThemeTypeName() == "Light"
? "theme:Light/close.svg"
: "theme:Dark/close.svg");
searchClear->setDisabled(GetMacroSearchSettings().searchString.empty());
QWidget::connect(searchClear, &QPushButton::clicked, searchClear,
[=]() {
searchText->setText("");
searchClear->setDisabled(true);
});
const auto ph = searchText->placeholderText();
const QFontMetrics fm(searchText->font());
const int width = fm.horizontalAdvance(ph);
// Add a little padding so the text isn't cramped
searchText->setMinimumWidth(width + 10);
searchText->setText(
QString::fromStdString(GetMacroSearchSettings().searchString));
QWidget::connect(searchText, &QLineEdit::textChanged, searchText,
[searchClear](const QString &text) {
GetMacroSearchSettings().searchString =
text.toStdString();
searchClear->setDisabled(text.isEmpty());
refreshFilter();
});
searchType->addItem(
obs_module_text("AdvSceneSwitcher.macroTab.search.name"),
static_cast<int>(MacroSearchSettings::SearchType::NAME));
searchType->addItem(
obs_module_text("AdvSceneSwitcher.macroTab.search.allSegments"),
static_cast<int>(
MacroSearchSettings::SearchType::ALL_SEGMENTS));
searchType->addItem(
obs_module_text("AdvSceneSwitcher.macroTab.search.conditions"),
static_cast<int>(MacroSearchSettings::SearchType::CONDITIONS));
searchType->addItem(
obs_module_text("AdvSceneSwitcher.macroTab.search.actions"),
static_cast<int>(MacroSearchSettings::SearchType::ACTIONS));
searchType->addItem(
obs_module_text("AdvSceneSwitcher.macroTab.search.label"),
static_cast<int>(MacroSearchSettings::SearchType::LABEL));
searchType->setCurrentIndex(searchType->findData(
static_cast<int>(GetMacroSearchSettings().searchType)));
QWidget::connect(
searchType, &QComboBox::currentIndexChanged, searchType, []() {
GetMacroSearchSettings().searchType =
static_cast<MacroSearchSettings::SearchType>(
searchType->currentData().toInt());
refreshFilter();
});
searchRegex->SetRegexConfig(GetMacroSearchSettings().regex);
QWidget::connect(searchRegex, &RegexConfigWidget::RegexConfigChanged,
searchRegex, [](const RegexConfig &regex) {
GetMacroSearchSettings().regex = regex;
refreshFilter();
});
showSettings->setCheckable(true);
showSettings->setMaximumWidth(11);
SetButtonIcon(showSettings, GetThemeTypeName() == "Light"
? ":/res/images/dots-vert.svg"
: "theme:Dark/dots-vert.svg");
QWidget::connect(showSettings, &QPushButton::toggled, showSettings,
showAdvancedSearchSettings);
QWidget::connect(MacroSignalManager::Instance(),
&MacroSignalManager::Rename, searchText,
[]() { refreshFilter(); });
CheckMacroSearchVisibility();
}
MacroSearchSettings &GetMacroSearchSettings()
{
static MacroSearchSettings settings;
return settings;
}
static bool stringMatches(const std::string &text)
{
const auto &settings = GetMacroSearchSettings();
const auto &regex = settings.regex;
const auto &searchString = settings.searchString;
if (regex.Enabled()) {
return regex.Matches(text, searchString);
}
return QString::fromStdString(text).contains(
QString::fromStdString(searchString), Qt::CaseInsensitive);
}
static bool segmentTypeMatches(MacroSegment *segment, bool isCondition)
{
if (!segment) {
return false;
}
const auto getNameFromId =
isCondition ? MacroConditionFactory::GetConditionName
: MacroActionFactory::GetActionName;
const auto name = getNameFromId(segment->GetId());
return stringMatches(obs_module_text(name.c_str()));
}
static bool segmentLabelMatches(MacroSegment *segment)
{
if (!segment) {
return false;
}
if (!segment->GetUseCustomLabel()) {
return false;
}
const auto label = segment->GetCustomLabel();
return stringMatches(label);
}
bool MacroMatchesSearchFilter(Macro *macro)
{
assert(macro);
if (!searchEnabled) {
return true;
}
const auto &settings = GetMacroSearchSettings();
if (settings.searchString.empty()) {
return true;
}
if (macro->IsGroup()) {
const auto groupEntries = GetGroupMacroEntries(macro);
for (const auto &entry : groupEntries) {
if (MacroMatchesSearchFilter(entry.get())) {
return true;
}
}
return false;
}
switch (settings.searchType) {
case MacroSearchSettings::SearchType::NAME: {
const auto name = macro->Name();
return stringMatches(name);
}
case MacroSearchSettings::SearchType::ALL_SEGMENTS:
for (const auto &condition : macro->Conditions()) {
if (segmentTypeMatches(condition.get(), true)) {
return true;
}
}
for (const auto &action : macro->Actions()) {
if (segmentTypeMatches(action.get(), false)) {
return true;
}
}
for (const auto &action : macro->ElseActions()) {
if (segmentTypeMatches(action.get(), false)) {
return true;
}
}
return false;
case MacroSearchSettings::SearchType::CONDITIONS:
for (const auto &condition : macro->Conditions()) {
if (segmentTypeMatches(condition.get(), true)) {
return true;
}
}
return false;
case MacroSearchSettings::SearchType::ACTIONS:
for (const auto &action : macro->Actions()) {
if (segmentTypeMatches(action.get(), false)) {
return true;
}
}
for (const auto &action : macro->ElseActions()) {
if (segmentTypeMatches(action.get(), false)) {
return true;
}
}
return false;
case MacroSearchSettings::SearchType::LABEL:
for (const auto &condition : macro->Conditions()) {
if (segmentLabelMatches(condition.get())) {
return true;
}
}
for (const auto &action : macro->Actions()) {
if (segmentLabelMatches(action.get())) {
return true;
}
}
for (const auto &action : macro->ElseActions()) {
if (segmentLabelMatches(action.get())) {
return true;
}
}
return false;
default:
break;
}
// Unhandled search type
assert(false);
return true;
}
} // namespace advss

View File

@@ -0,0 +1,43 @@
#pragma once
#include "regex-config.hpp"
#include <functional>
#include <string>
class QComboBox;
class QLayout;
class QLineEdit;
class QPushButton;
namespace advss {
class Macro;
struct MacroSearchSettings {
void Save(obs_data_t *data, const char *name);
void Load(obs_data_t *data, const char *name);
enum class SearchType {
NAME = 0,
ALL_SEGMENTS = 10,
CONDITIONS = 20,
ACTIONS = 30,
LABEL = 40,
};
SearchType searchType = SearchType::NAME;
std::string searchString;
RegexConfig regex;
bool showAlways = false;
};
void CheckMacroSearchVisibility();
void SetupMacroSearchWidgets(QLayout *searchLayout, QLineEdit *searchText,
QPushButton *searchClear, QComboBox *searchType,
RegexConfigWidget *searchRegex,
QPushButton *showSettings,
const std::function<void()> &refresh);
MacroSearchSettings &GetMacroSearchSettings();
bool MacroMatchesSearchFilter(Macro *macro);
} // namespace advss

View File

@@ -1,5 +1,7 @@
#include "macro-segment-copy-paste.hpp" #include "macro-segment-copy-paste.hpp"
#include "advanced-scene-switcher.hpp" #include "advanced-scene-switcher.hpp"
#include "condition-logic.hpp"
#include "macro.hpp" #include "macro.hpp"
#include <QShortcut> #include <QShortcut>
@@ -72,15 +74,24 @@ void MacroEdit::PasteMacroSegment()
} }
AddMacroCondition(macro.get(), macro->Conditions().size(), AddMacroCondition(macro.get(), macro->Conditions().size(),
copyInfo.segment->GetId(), data.Get(), logic); copyInfo.segment->GetId(), data.Get(), logic);
if (!macro->Conditions().empty()) {
macro->Conditions().back()->FixupVarMappingRefs();
}
break; break;
} }
case MacroSegmentCopyInfo::Type::ACTION: case MacroSegmentCopyInfo::Type::ACTION:
AddMacroAction(macro.get(), macro->Actions().size(), AddMacroAction(macro.get(), macro->Actions().size(),
copyInfo.segment->GetId(), data.Get()); copyInfo.segment->GetId(), data.Get());
if (!macro->Actions().empty()) {
macro->Actions().back()->FixupVarMappingRefs();
}
break; break;
case MacroSegmentCopyInfo::Type::ELSE: case MacroSegmentCopyInfo::Type::ELSE:
AddMacroElseAction(macro.get(), macro->ElseActions().size(), AddMacroElseAction(macro.get(), macro->ElseActions().size(),
copyInfo.segment->GetId(), data.Get()); copyInfo.segment->GetId(), data.Get());
if (!macro->ElseActions().empty()) {
macro->ElseActions().back()->FixupVarMappingRefs();
}
break; break;
default: default:
break; break;

View File

@@ -201,6 +201,8 @@ bool MacroSegmentList::PopulateWidgetsFromCache(const Macro *macro)
_contentLayout->addWidget(widget); _contentLayout->addWidget(widget);
widget->show(); widget->show();
} }
// Widgets are now owned by the layout again, so the cache entry is stale.
_widgetCache.erase(it);
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();
@@ -463,7 +465,17 @@ void MacroSegmentList::SetupVisibleMacroSegmentWidgets()
const auto viewportRect = viewport()->rect(); const auto viewportRect = viewport()->rect();
for (auto segment : widget()->findChildren<MacroSegmentEdit *>()) { for (int i = 0; i < _contentLayout->count(); ++i) {
auto item = _contentLayout->itemAt(i);
if (!item) {
continue;
}
auto segment = dynamic_cast<MacroSegmentEdit *>(item->widget());
if (!segment) {
continue;
}
const auto pos = segment->mapTo(viewport(), QPoint(0, 0)); const auto pos = segment->mapTo(viewport(), QPoint(0, 0));
const QRect rect(pos, segment->size()); const QRect rect(pos, segment->size());
if (!viewportRect.intersects(rect)) { if (!viewportRect.intersects(rect)) {

View File

@@ -73,35 +73,46 @@ void MacroSegmentSelection::MacroSegmentOrderChanged()
} }
static QString GetMacroSegmentDescription(Macro *macro, int idx, static QString GetMacroSegmentDescription(Macro *macro, int idx,
bool isCondition) MacroSegmentSelection::Type type)
{ {
if (!macro) { if (!macro) {
return ""; return "";
} }
if (!IsValidMacroSegmentIndex(macro, idx, isCondition)) {
MacroSegment *segment;
switch (type) {
case MacroSegmentSelection::Type::CONDITION:
if (!IsValidConditionIndex(macro, idx)) {
return "";
}
segment = GetMacroConditions(macro)->at(idx).get();
break;
case MacroSegmentSelection::Type::ACTION:
if (!IsValidActionIndex(macro, idx)) {
return "";
}
segment = GetMacroActions(macro)->at(idx).get();
break;
case MacroSegmentSelection::Type::ELSE_ACTION:
if (!IsValidElseActionIndex(macro, idx)) {
return "";
}
segment = GetMacroElseActions(macro)->at(idx).get();
break;
default:
return ""; return "";
} }
MacroSegment *segment; const auto idToName = type == MacroSegmentSelection::Type::CONDITION
if (isCondition) { ? MacroConditionFactory::GetConditionName
segment = GetMacroConditions(macro)->at(idx).get(); : MacroActionFactory::GetActionName;
} else { const QString typeStr =
segment = GetMacroActions(macro)->at(idx).get(); obs_module_text(idToName(segment->GetId()).c_str());
} const QString description =
QString::fromStdString(segment->GetShortDesc());
QString description = QString::fromStdString(segment->GetShortDesc()); QString result = typeStr;
QString type;
if (isCondition) {
type = obs_module_text(MacroConditionFactory::GetConditionName(
segment->GetId())
.c_str());
} else {
type = obs_module_text(
MacroActionFactory::GetActionName(segment->GetId())
.c_str());
}
QString result = type;
if (!description.isEmpty()) { if (!description.isEmpty()) {
result += ": " + description; result += ": " + description;
} }
@@ -123,11 +134,25 @@ void MacroSegmentSelection::SetupDescription() const
return; return;
} }
bool validIndex = false;
switch (_type) {
case Type::CONDITION:
validIndex = IsValidConditionIndex(_macro, index - 1);
break;
case Type::ACTION:
validIndex = IsValidActionIndex(_macro, index - 1);
break;
case Type::ELSE_ACTION:
validIndex = IsValidElseActionIndex(_macro, index - 1);
break;
default:
break;
}
QString description; QString description;
if (IsValidMacroSegmentIndex(_macro, index - 1, if (validIndex) {
_type == Type::CONDITION)) { description =
description = GetMacroSegmentDescription( GetMacroSegmentDescription(_macro, index - 1, _type);
_macro, index - 1, _type == Type::CONDITION);
} else { } else {
description = obs_module_text( description = obs_module_text(
"AdvSceneSwitcher.macroSegmentSelection.invalid"); "AdvSceneSwitcher.macroSegmentSelection.invalid");
@@ -165,8 +190,23 @@ void MacroSegmentSelection::MarkSelectedSegment()
if (index.GetType() == IntVariable::Type::VARIABLE) { if (index.GetType() == IntVariable::Type::VARIABLE) {
return; return;
} }
if (!IsValidMacroSegmentIndex(_macro, index - 1,
_type == Type::CONDITION)) { bool validIndex = false;
switch (_type) {
case Type::CONDITION:
validIndex = IsValidConditionIndex(_macro, index - 1);
break;
case Type::ACTION:
validIndex = IsValidActionIndex(_macro, index - 1);
break;
case Type::ELSE_ACTION:
validIndex = IsValidElseActionIndex(_macro, index - 1);
break;
default:
break;
}
if (!validIndex) {
return; return;
} }

View File

@@ -8,7 +8,7 @@ class Macro;
class MacroSegmentSelection : public QWidget { class MacroSegmentSelection : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
enum class Type { CONDITION, ACTION }; enum class Type { CONDITION, ACTION, ELSE_ACTION };
MacroSegmentSelection(QWidget *parent, Type type, MacroSegmentSelection(QWidget *parent, Type type,
bool allowVariables = true); bool allowVariables = true);

View File

@@ -1,32 +1,119 @@
#include "macro-segment.hpp" #include "macro-segment.hpp"
#include "macro.hpp" #include "macro.hpp"
#include "mouse-wheel-guard.hpp" #include "mouse-wheel-guard.hpp"
#include "path-helpers.hpp"
#include "section.hpp" #include "section.hpp"
#include "ui-helpers.hpp" #include "ui-helpers.hpp"
#include "variable.hpp"
#include <QApplication> #include <QApplication>
#include <QEvent> #include <QEvent>
#include <QFrame>
#include <QGraphicsOpacityEffect> #include <QGraphicsOpacityEffect>
#include <QLabel> #include <QLabel>
#include <QMouseEvent> #include <QMouseEvent>
#include <QPushButton>
#include <QScrollBar> #include <QScrollBar>
#include <QVBoxLayout>
namespace advss { namespace advss {
std::vector<TempVariableRef> MacroSegment::GetTempVarRefs() const
{
std::vector<TempVariableRef> refs;
for (const auto &mapping : _varMappings) {
if (mapping.tempVar.HasValidID()) {
refs.push_back(mapping.tempVar);
}
}
return refs;
}
void MacroSegment::ApplyVarMappings()
{
auto macro = GetMacro();
for (const auto &mapping : _varMappings) {
auto var = mapping.variable.lock();
if (!var) {
continue;
}
const auto tempVar = mapping.tempVar.GetTempVariable(macro);
if (!tempVar) {
continue;
}
const auto value = tempVar->Value();
if (!value) {
continue;
}
var->SetValue(*value);
}
}
const std::vector<VarMapping> &MacroSegment::GetVarMappings() const
{
return _varMappings;
}
void MacroSegment::SetVarMappings(const std::vector<VarMapping> &mappings)
{
_varMappings = mappings;
IncrementTempVarInUseGeneration();
}
void MacroSegment::FixupVarMappingRefs()
{
for (auto &mapping : _varMappings) {
if (!mapping.tempVar.HasValidID()) {
continue;
}
for (const auto &var : _tempVariables) {
if (var.ID() == mapping.tempVar.GetID()) {
mapping.tempVar = var.GetRef();
break;
}
}
}
NotifyUIAboutTempVarChange(this);
}
std::vector<TempVariable> MacroSegment::GetOwnTempVars() const
{
return _tempVariables;
}
MacroSegment::MacroSegment(Macro *m, bool supportsVariableValue) MacroSegment::MacroSegment(Macro *m, bool supportsVariableValue)
: _macro(m), : _macro(m),
_supportsVariableValue(supportsVariableValue) _supportsVariableValue(supportsVariableValue)
{ {
} }
void MacroSegment::SetVarMappingExpanded(bool expanded)
{
_varMappingExpanded = expanded;
}
bool MacroSegment::Save(obs_data_t *obj) const bool MacroSegment::Save(obs_data_t *obj) const
{ {
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_bool(data, "collapsed", _collapsed); obs_data_set_bool(data, "collapsed", _collapsed);
obs_data_set_bool(data, "varMappingExpanded", _varMappingExpanded);
obs_data_set_bool(data, "useCustomLabel", _useCustomLabel); obs_data_set_bool(data, "useCustomLabel", _useCustomLabel);
obs_data_set_string(data, "customLabel", _customLabel.c_str()); obs_data_set_string(data, "customLabel", _customLabel.c_str());
obs_data_set_bool(data, "enabled", _enabled); obs_data_set_bool(data, "enabled", _enabled);
obs_data_set_int(data, "version", 1); obs_data_set_int(data, "version", 2);
OBSDataArrayAutoRelease mappingsArray = obs_data_array_create();
for (const auto &mapping : _varMappings) {
OBSDataAutoRelease item = obs_data_create();
mapping.tempVar.Save(item, GetMacro(), "tempVar");
obs_data_set_string(
item, "variable",
GetWeakVariableName(mapping.variable).c_str());
obs_data_array_push_back(mappingsArray, item);
}
obs_data_set_array(data, "varMappings", mappingsArray);
obs_data_set_obj(obj, "segmentSettings", data); obs_data_set_obj(obj, "segmentSettings", data);
return true; return true;
} }
@@ -35,6 +122,7 @@ bool MacroSegment::Load(obs_data_t *obj)
{ {
OBSDataAutoRelease data = obs_data_get_obj(obj, "segmentSettings"); OBSDataAutoRelease data = obs_data_get_obj(obj, "segmentSettings");
_collapsed = obs_data_get_bool(data, "collapsed"); _collapsed = obs_data_get_bool(data, "collapsed");
_varMappingExpanded = obs_data_get_bool(data, "varMappingExpanded");
_useCustomLabel = obs_data_get_bool(data, "useCustomLabel"); _useCustomLabel = obs_data_get_bool(data, "useCustomLabel");
_customLabel = obs_data_get_string(data, "customLabel"); _customLabel = obs_data_get_string(data, "customLabel");
obs_data_set_default_bool(data, "enabled", true); obs_data_set_default_bool(data, "enabled", true);
@@ -45,6 +133,26 @@ bool MacroSegment::Load(obs_data_t *obj)
_enabled = obs_data_get_bool(obj, "enabled"); _enabled = obs_data_get_bool(obj, "enabled");
} }
// Reset the previously unused "enabled" value for conditions to "true"
if (obs_data_get_int(data, "version") < 2 &&
obs_data_has_user_value(obj, "logic")) {
_enabled = true;
}
_varMappings.clear();
OBSDataArrayAutoRelease mappingsArray =
obs_data_get_array(data, "varMappings");
const size_t count = obs_data_array_count(mappingsArray);
_varMappings.reserve(count);
for (size_t i = 0; i < count; i++) {
OBSDataAutoRelease item = obs_data_array_item(mappingsArray, i);
VarMapping mapping;
mapping.variable = GetWeakVariableByName(
obs_data_get_string(item, "variable"));
_varMappings.push_back(std::move(mapping));
_varMappings.back().tempVar.Load(item, GetMacro(), "tempVar");
}
ClearAvailableTempvars(); ClearAvailableTempvars();
return true; return true;
} }
@@ -152,6 +260,16 @@ void MacroSegment::AddTempvar(const std::string &id, const std::string &name,
NotifyUIAboutTempVarChange(this); NotifyUIAboutTempVarChange(this);
} }
bool MacroSegment::IsTempVarInUse(const std::string &id) const
{
for (const auto &var : _tempVariables) {
if (var.ID() == id) {
return var.IsInUse();
}
}
return false;
}
void MacroSegment::SetTempVarValue(const std::string &id, void MacroSegment::SetTempVarValue(const std::string &id,
const std::string &value) const std::string &value)
{ {
@@ -211,11 +329,38 @@ MacroSegmentEdit::MacroSegmentEdit(QWidget *parent)
_headerInfo(new QLabel()), _headerInfo(new QLabel()),
_frame(new QWidget), _frame(new QWidget),
_contentLayout(new QVBoxLayout), _contentLayout(new QVBoxLayout),
_outputMappings(new TempVarOutputMappingsWidget(this)),
_varMappingToggle(new QPushButton(this)),
_noBorderframe(new QFrame), _noBorderframe(new QFrame),
_borderFrame(new QFrame), _borderFrame(new QFrame),
_dropLineAbove(new QFrame), _dropLineAbove(new QFrame),
_dropLineBelow(new QFrame) _dropLineBelow(new QFrame)
{ {
const auto iconPath = QString::fromStdString(GetDataFilePath(
"res/images/" + GetThemeTypeName() + "Variable.svg"));
_varMappingToggle->setIcon(QIcon(iconPath));
_varMappingToggle->setMaximumWidth(22);
_varMappingToggle->setFlat(true);
_varMappingToggle->setCheckable(true);
_varMappingToggle->setVisible(false);
_varMappingToggle->setToolTip(obs_module_text(
"AdvSceneSwitcher.tempVar.outputMappings.toggle"));
QWidget::connect(_varMappingToggle, &QPushButton::toggled,
_outputMappings,
&TempVarOutputMappingsWidget::SetPanelExpanded);
QWidget::connect(_varMappingToggle, &QPushButton::toggled, this,
[this](bool checked) {
if (Data()) {
Data()->SetVarMappingExpanded(checked);
}
});
QWidget::connect(_outputMappings,
&TempVarOutputMappingsWidget::ExpandableChanged,
_varMappingToggle, &QPushButton::setVisible);
QWidget::connect(_section, &Section::Collapsed, _outputMappings,
&TempVarOutputMappingsWidget::SetSectionCollapsed);
_dropLineAbove->setLineWidth(3); _dropLineAbove->setLineWidth(3);
_dropLineAbove->setFixedHeight(11); _dropLineAbove->setFixedHeight(11);
_dropLineBelow->setLineWidth(3); _dropLineBelow->setLineWidth(3);
@@ -277,6 +422,21 @@ MacroSegmentEdit::MacroSegmentEdit(QWidget *parent)
_headerInfo->installEventFilter(this); _headerInfo->installEventFilter(this);
} }
void MacroSegmentEdit::SetupVarMappings(MacroSegment *segment)
{
_outputMappings->SetSegment(segment);
_varMappingToggle->setChecked(segment &&
segment->GetVarMappingExpanded());
}
void MacroSegmentEdit::ShowVariableMappings(bool show)
{
_varMappingToggle->setChecked(show);
if (Data()) {
Data()->SetVarMappingExpanded(show);
}
}
bool MacroSegmentEdit::eventFilter(QObject *obj, QEvent *ev) bool MacroSegmentEdit::eventFilter(QObject *obj, QEvent *ev)
{ {
if (obj == _headerInfo && ev->type() == QEvent::MouseMove) { if (obj == _headerInfo && ev->type() == QEvent::MouseMove) {

View File

@@ -7,17 +7,27 @@
#include "sync-helpers.hpp" #include "sync-helpers.hpp"
#include "temp-variable.hpp" #include "temp-variable.hpp"
#include <QWidget>
#include <QFrame>
#include <QVBoxLayout>
#include <QTimer>
#include <obs-data.h> #include <obs-data.h>
#include <QHBoxLayout>
#include <QPushButton>
#include <QWidget>
#include <memory>
class QFrame;
class QLabel; class QLabel;
class QVBoxLayout;
namespace advss { namespace advss {
class Macro; class Macro;
class Variable;
struct VarMapping {
TempVariableRef tempVar;
std::weak_ptr<Variable> variable;
};
class EXPORT MacroSegment : public Lockable { class EXPORT MacroSegment : public Lockable {
public: public:
@@ -28,12 +38,15 @@ public:
int GetIndex() const { return _idx; } int GetIndex() const { return _idx; }
void SetCollapsed(bool collapsed) { _collapsed = collapsed; } void SetCollapsed(bool collapsed) { _collapsed = collapsed; }
bool GetCollapsed() const { return _collapsed; } bool GetCollapsed() const { return _collapsed; }
void SetVarMappingExpanded(bool expanded);
bool GetVarMappingExpanded() const { return _varMappingExpanded; }
void SetUseCustomLabel(bool enable) { _useCustomLabel = enable; } void SetUseCustomLabel(bool enable) { _useCustomLabel = enable; }
bool GetUseCustomLabel() const { return _useCustomLabel; } bool GetUseCustomLabel() const { return _useCustomLabel; }
void SetCustomLabel(const std::string &label) { _customLabel = label; } void SetCustomLabel(const std::string &label) { _customLabel = label; }
std::string GetCustomLabel() const { return _customLabel; } std::string GetCustomLabel() const { return _customLabel; }
virtual bool Save(obs_data_t *obj) const = 0; virtual bool Save(obs_data_t *obj) const = 0;
virtual bool Load(obs_data_t *obj) = 0; virtual bool Load(obs_data_t *obj) = 0;
virtual std::vector<TempVariableRef> GetTempVarRefs() const;
virtual bool PostLoad(); virtual bool PostLoad();
virtual std::string GetShortDesc() const; virtual std::string GetShortDesc() const;
virtual std::string GetId() const = 0; virtual std::string GetId() const = 0;
@@ -42,6 +55,11 @@ public:
void SetEnabled(bool); void SetEnabled(bool);
bool Enabled() const; bool Enabled() const;
virtual std::string GetVariableValue() const; virtual std::string GetVariableValue() const;
void ApplyVarMappings();
const std::vector<VarMapping> &GetVarMappings() const;
void SetVarMappings(const std::vector<VarMapping> &mappings);
std::vector<TempVariable> GetOwnTempVars() const;
void FixupVarMappingRefs();
protected: protected:
friend bool SupportsVariableValue(MacroSegment *); friend bool SupportsVariableValue(MacroSegment *);
@@ -54,6 +72,7 @@ protected:
void AddTempvar(const std::string &id, const std::string &name, void AddTempvar(const std::string &id, const std::string &name,
const std::string &description = ""); const std::string &description = "");
bool IsTempVarInUse(const std::string &id) const;
void SetTempVarValue(const std::string &id, const std::string &value); void SetTempVarValue(const std::string &id, const std::string &value);
template<typename T, typename = std::enable_if_t< template<typename T, typename = std::enable_if_t<
@@ -64,6 +83,14 @@ protected:
: std::string("false")); : std::string("false"));
} }
template<typename F, typename = std::enable_if_t<std::is_invocable_v<F>>>
void SetTempVarValue(const std::string &id, F &&valueProvider)
{
if (IsTempVarInUse(id)) {
SetTempVarValue(id, valueProvider());
}
}
private: private:
void ClearAvailableTempvars(); void ClearAvailableTempvars();
std::optional<const TempVariable> std::optional<const TempVariable>
@@ -77,6 +104,7 @@ private:
// UI helper // UI helper
bool _highlight = false; bool _highlight = false;
bool _collapsed = false; bool _collapsed = false;
bool _varMappingExpanded = false;
bool _enabled = true; bool _enabled = true;
// Custom header labels // Custom header labels
@@ -89,6 +117,7 @@ private:
int _variableRefs = 0; int _variableRefs = 0;
std::string _variableValue; std::string _variableValue;
std::vector<TempVariable> _tempVariables; std::vector<TempVariable> _tempVariables;
std::vector<VarMapping> _varMappings;
friend class Macro; friend class Macro;
}; };
@@ -107,9 +136,13 @@ public:
void SetSelected(bool); void SetSelected(bool);
virtual std::shared_ptr<MacroSegment> Data() const = 0; virtual std::shared_ptr<MacroSegment> Data() const = 0;
virtual void SetupWidgets(bool basicSetup = false) = 0; virtual void SetupWidgets(bool basicSetup = false) = 0;
virtual void FocusTypeSelection() {}
void SetupVarMappings(MacroSegment *segment);
public slots: public slots:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
void ShowVariableMappings(bool show);
protected slots: protected slots:
void Collapsed(bool) const; void Collapsed(bool) const;
@@ -124,6 +157,8 @@ protected:
QLabel *_headerInfo; QLabel *_headerInfo;
QWidget *_frame; QWidget *_frame;
QVBoxLayout *_contentLayout; QVBoxLayout *_contentLayout;
TempVarOutputMappingsWidget *_outputMappings;
QPushButton *_varMappingToggle;
bool _allWidgetsAreSetup = false; bool _allWidgetsAreSetup = false;
private: private:

View File

@@ -1,8 +1,11 @@
#include "macro-selection.hpp" #include "macro-selection.hpp"
#include "advanced-scene-switcher.hpp" #include "advanced-scene-switcher.hpp"
#include "layout-helpers.hpp" #include "layout-helpers.hpp"
#include "macro.hpp" #include "macro-helpers.hpp"
#include "macro-ref.hpp"
#include "macro-signals.hpp" #include "macro-signals.hpp"
#include "macro.hpp"
#include "ui-helpers.hpp" #include "ui-helpers.hpp"
#include <QDialogButtonBox> #include <QDialogButtonBox>
@@ -14,10 +17,7 @@ MacroSelection::MacroSelection(QWidget *parent)
: FilterComboBox(parent, : FilterComboBox(parent,
obs_module_text("AdvSceneSwitcher.selectMacro")) obs_module_text("AdvSceneSwitcher.selectMacro"))
{ {
for (const auto &m : GetMacros()) { for (const auto &m : GetTopLevelMacros()) {
if (m->IsGroup()) {
continue;
}
addItem(QString::fromStdString(m->Name())); addItem(QString::fromStdString(m->Name()));
} }
@@ -54,6 +54,7 @@ void MacroSelection::HideSelectedMacro()
return; return;
} }
#ifndef UNIT_TEST
const auto m = ssWindow->ui->macros->GetCurrentMacro(); const auto m = ssWindow->ui->macros->GetCurrentMacro();
if (!m) { if (!m) {
return; return;
@@ -64,6 +65,23 @@ void MacroSelection::HideSelectedMacro()
} }
qobject_cast<QListView *>(view())->setRowHidden(idx, true); qobject_cast<QListView *>(view())->setRowHidden(idx, true);
#endif // !UNIT_TEST
}
void MacroSelection::HideGroups()
{
for (const auto &macro : GetTopLevelMacros()) {
if (!macro->IsGroup()) {
continue;
}
int idx = findText(QString::fromStdString(macro->Name()));
if (idx == -1) {
continue;
}
qobject_cast<QListView *>(view())->setRowHidden(idx, true);
}
} }
void MacroSelection::ShowAllMacros() void MacroSelection::ShowAllMacros()

View File

@@ -15,6 +15,7 @@ public:
MacroSelection(QWidget *parent); MacroSelection(QWidget *parent);
void SetCurrentMacro(const MacroRef &); void SetCurrentMacro(const MacroRef &);
void HideSelectedMacro(); // Macro currently being edited void HideSelectedMacro(); // Macro currently being edited
void HideGroups();
void ShowAllMacros(); void ShowAllMacros();
private slots: private slots:

View File

@@ -18,6 +18,7 @@ signals:
void Remove(const QString &); void Remove(const QString &);
void SegmentOrderChanged(); void SegmentOrderChanged();
void HighlightChanged(bool value); void HighlightChanged(bool value);
void ConditionTypeCreated(const QString &typeId);
private: private:
MacroSignalManager(QObject *parent = nullptr); MacroSignalManager(QObject *parent = nullptr);

View File

@@ -1,11 +1,13 @@
#include "advanced-scene-switcher.hpp" #include "advanced-scene-switcher.hpp"
#include "action-queue.hpp"
#include "macro-action-edit.hpp" #include "macro-action-edit.hpp"
#include "macro-condition-edit.hpp" #include "macro-condition-edit.hpp"
#include "macro-export-import-dialog.hpp" #include "macro-export-import-dialog.hpp"
#include "macro-helpers.hpp"
#include "macro-search.hpp"
#include "macro-settings.hpp" #include "macro-settings.hpp"
#include "macro-signals.hpp" #include "macro-signals.hpp"
#include "macro-tree.hpp" #include "macro-tree.hpp"
#include "macro-undo-redo.hpp"
#include "macro.hpp" #include "macro.hpp"
#include "math-helpers.hpp" #include "math-helpers.hpp"
#include "name-dialog.hpp" #include "name-dialog.hpp"
@@ -17,6 +19,7 @@
#include "version.h" #include "version.h"
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <QColor> #include <QColor>
#include <QMenu> #include <QMenu>
@@ -24,6 +27,8 @@ namespace advss {
static QObject *addPulse = nullptr; static QObject *addPulse = nullptr;
static QTimer onChangeHighlightTimer; static QTimer onChangeHighlightTimer;
static std::chrono::high_resolution_clock::time_point
lastOnChangeHighlightCheckTime{};
static void disableAddButtonHighlight() static void disableAddButtonHighlight()
{ {
@@ -118,6 +123,7 @@ void AdvSceneSwitcher::on_macroAdd_clicked()
ui->macros->Add(newMacro); ui->macros->Add(newMacro);
MacroSignalManager::Instance()->Add( MacroSignalManager::Instance()->Add(
QString::fromStdString(name)); QString::fromStdString(name));
RegisterMacroAddUndoRedo(name);
return; return;
} }
@@ -127,6 +133,7 @@ void AdvSceneSwitcher::on_macroAdd_clicked()
ui->macros->AddToGroup(newMacro, selectedMacro); ui->macros->AddToGroup(newMacro, selectedMacro);
MacroSignalManager::Instance()->Add( MacroSignalManager::Instance()->Add(
QString::fromStdString(name)); QString::fromStdString(name));
RegisterMacroAddUndoRedo(name);
return; return;
} }
@@ -135,31 +142,20 @@ void AdvSceneSwitcher::on_macroAdd_clicked()
ui->macros->Add(newMacro, selectedMacro); ui->macros->Add(newMacro, selectedMacro);
MacroSignalManager::Instance()->Add( MacroSignalManager::Instance()->Add(
QString::fromStdString(name)); QString::fromStdString(name));
RegisterMacroAddUndoRedo(name);
return; return;
} }
Macro::PrepareMoveToGroup(selectedMacroGroup, newMacro); Macro::PrepareMoveToGroup(selectedMacroGroup, newMacro);
ui->macros->Add(newMacro, selectedMacro); ui->macros->Add(newMacro, selectedMacro);
MacroSignalManager::Instance()->Add(QString::fromStdString(name)); MacroSignalManager::Instance()->Add(QString::fromStdString(name));
RegisterMacroAddUndoRedo(name);
} }
static void addGroupSubitems(std::vector<std::shared_ptr<Macro>> &macros, static void addGroupSubitems(std::vector<std::shared_ptr<Macro>> &macros,
const std::shared_ptr<Macro> &group) const std::shared_ptr<Macro> &group)
{ {
std::vector<std::shared_ptr<Macro>> subitems; auto subitems = GetGroupMacroEntries(group.get());
subitems.reserve(group->GroupSize());
// Find all subitems
auto allMacros = GetMacros();
for (auto it = allMacros.begin(); it < allMacros.end(); it++) {
if ((*it)->Name() != group->Name()) {
continue;
}
for (uint32_t i = 1; i <= group->GroupSize(); i++) {
subitems.emplace_back(*std::next(it, i));
}
break;
}
// Remove subitems which were already selected to avoid duplicates // Remove subitems which were already selected to avoid duplicates
for (const auto &subitem : subitems) { for (const auto &subitem : subitems) {
@@ -194,6 +190,12 @@ void AdvSceneSwitcher::RemoveMacro(std::shared_ptr<Macro> &macro)
} }
} }
if (macro->IsGroup()) {
RegisterGroupDeleteUndoRedo(macro.get());
} else {
RegisterMacroRemoveUndoRedo(macro.get());
}
if (macro->IsGroup()) { if (macro->IsGroup()) {
std::vector<std::shared_ptr<Macro>> macros = {macro}; std::vector<std::shared_ptr<Macro>> macros = {macro};
addGroupSubitems(macros, macro); addGroupSubitems(macros, macro);
@@ -207,7 +209,7 @@ void AdvSceneSwitcher::RemoveMacro(std::shared_ptr<Macro> &macro)
// Don't cache widgets for about to be deleted macros // Don't cache widgets for about to be deleted macros
MacroSegmentList::SetCachingEnabled(false); MacroSegmentList::SetCachingEnabled(false);
ui->macros->Remove(macro); ui->macros->Remove(macro);
MacroSegmentList::SetCachingEnabled(!switcher->disableMacroWidgetCache); MacroSegmentList::SetCachingEnabled(true);
MacroSignalManager::Instance()->Remove(name); MacroSignalManager::Instance()->Remove(name);
} }
@@ -250,13 +252,16 @@ void AdvSceneSwitcher::RemoveSelectedMacros()
void AdvSceneSwitcher::RenameMacro(std::shared_ptr<Macro> &macro, void AdvSceneSwitcher::RenameMacro(std::shared_ptr<Macro> &macro,
const QString &name) const QString &name)
{ {
auto oldName = QString::fromStdString(macro->Name()); const std::string oldName = macro->Name();
const std::string newName = name.toStdString();
{ {
auto lock = LockContext(); auto lock = LockContext();
macro->SetName(name.toStdString()); macro->SetName(newName);
} }
MacroSignalManager::Instance()->Rename(oldName, name); RegisterMacroRenameUndoRedo(oldName, newName);
MacroSignalManager::Instance()->Rename(QString::fromStdString(oldName),
name);
} }
void AdvSceneSwitcher::on_macroRemove_clicked() void AdvSceneSwitcher::on_macroRemove_clicked()
@@ -326,13 +331,9 @@ void AdvSceneSwitcher::ExportMacros() const
obs_data_array_push_back(macroArray, obj); obs_data_array_push_back(macroArray, obj);
} }
obs_data_set_array(data, "macros", macroArray); obs_data_set_array(data, "macros", macroArray);
SaveVariables(data);
SaveActionQueues(data);
obs_data_set_string(data, "version", g_GIT_TAG); obs_data_set_string(data, "version", g_GIT_TAG);
auto json = obs_data_get_json(data);
QString exportString(json);
MacroExportImportDialog::ExportMacros(exportString); MacroExportImportDialog::ExportMacros(data);
} }
bool AdvSceneSwitcher::ResolveMacroImportNameConflict( bool AdvSceneSwitcher::ResolveMacroImportNameConflict(
@@ -394,8 +395,6 @@ void AdvSceneSwitcher::ImportMacros()
ImportMacros(); ImportMacros();
return; return;
} }
ImportVariables(data);
ImportQueues(data);
auto version = obs_data_get_string(data, "version"); auto version = obs_data_get_string(data, "version");
if (strcmp(version, g_GIT_TAG) != 0) { if (strcmp(version, g_GIT_TAG) != 0) {
@@ -410,22 +409,30 @@ void AdvSceneSwitcher::ImportMacros()
int groupSize = 0; int groupSize = 0;
std::shared_ptr<Macro> group; std::shared_ptr<Macro> group;
std::vector<std::shared_ptr<Macro>> importedMacros; std::vector<std::shared_ptr<Macro>> importedMacros;
auto &tempMacros = GetTemporaryMacros();
auto lock = LockContext(); std::unique_lock<std::mutex> lock(*GetMutex());
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
tempMacros.clear();
OBSDataAutoRelease array_obj = obs_data_array_item(array, i); OBSDataAutoRelease array_obj = obs_data_array_item(array, i);
auto macro = std::make_shared<Macro>(); auto macro = std::make_shared<Macro>();
tempMacros.emplace_back(macro);
macro->Load(array_obj); macro->Load(array_obj);
RunPostLoadSteps(); RunAndClearPostLoadSteps();
if (macroNameExists(macro->Name()) && if (macroNameExists(macro->Name())) {
!ResolveMacroImportNameConflict(macro)) { lock.unlock();
groupSize--; bool resolved = ResolveMacroImportNameConflict(macro);
continue; lock.lock();
if (!resolved) {
groupSize--;
continue;
}
} }
importedMacros.emplace_back(macro); importedMacros.emplace_back(macro);
GetMacros().emplace_back(macro); GetTopLevelMacros().emplace_back(macro);
if (groupSize > 0 && !macro->IsGroup()) { if (groupSize > 0 && !macro->IsGroup()) {
Macro::PrepareMoveToGroup(group, macro); Macro::PrepareMoveToGroup(group, macro);
groupSize--; groupSize--;
@@ -444,11 +451,13 @@ void AdvSceneSwitcher::ImportMacros()
for (const auto &macro : importedMacros) { for (const auto &macro : importedMacros) {
macro->PostLoad(); macro->PostLoad();
} }
RunPostLoadSteps(); RunAndClearPostLoadSteps();
tempMacros.clear();
ui->macros->Reset(GetMacros(), ui->macros->Reset(GetTopLevelMacros(),
GetGlobalMacroSettings()._highlightExecuted); GetGlobalMacroSettings()._highlightExecuted);
disableAddButtonHighlight(); disableAddButtonHighlight();
RegisterMacrosImportUndoRedo(importedMacros);
} }
void AdvSceneSwitcher::on_macroName_editingFinished() void AdvSceneSwitcher::on_macroName_editingFinished()
@@ -479,14 +488,17 @@ void AdvSceneSwitcher::on_runMacroInParallel_stateChanged(int value) const
macro->SetRunInParallel(value); macro->SetRunInParallel(value);
} }
void AdvSceneSwitcher::on_runMacroOnChange_stateChanged(int value) const void AdvSceneSwitcher::on_actionTriggerMode_currentIndexChanged(int index) const
{ {
auto macro = GetSelectedMacro(); auto macro = GetSelectedMacro();
if (!macro) { if (!macro) {
return; return;
} }
auto lock = LockContext(); auto lock = LockContext();
macro->SetMatchOnChange(value); const auto mode = static_cast<Macro::ActionTriggerMode>(
ui->actionTriggerMode->itemData(index).toInt());
macro->SetActionTriggerMode(mode);
} }
void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable) const void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable) const
@@ -494,7 +506,7 @@ void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable) const
ui->macroName->setDisabled(disable); ui->macroName->setDisabled(disable);
ui->runMacro->setDisabled(disable); ui->runMacro->setDisabled(disable);
ui->runMacroInParallel->setDisabled(disable); ui->runMacroInParallel->setDisabled(disable);
ui->runMacroOnChange->setDisabled(disable); ui->actionTriggerMode->setDisabled(disable);
ui->macroEdit->SetControlsDisabled(disable); ui->macroEdit->SetControlsDisabled(disable);
} }
@@ -524,10 +536,12 @@ void AdvSceneSwitcher::MacroSelectionChanged()
{ {
const QSignalBlocker b1(ui->macroName); const QSignalBlocker b1(ui->macroName);
const QSignalBlocker b2(ui->runMacroInParallel); const QSignalBlocker b2(ui->runMacroInParallel);
const QSignalBlocker b3(ui->runMacroOnChange); const QSignalBlocker b3(ui->actionTriggerMode);
ui->macroName->setText(macro->Name().c_str()); ui->macroName->setText(macro->Name().c_str());
ui->runMacroInParallel->setChecked(macro->RunInParallel()); ui->runMacroInParallel->setChecked(macro->RunInParallel());
ui->runMacroOnChange->setChecked(macro->MatchOnChange()); ui->actionTriggerMode->setCurrentIndex(
ui->actionTriggerMode->findData(static_cast<int>(
macro->GetActionTriggerMode())));
} }
macro->ResetUIHelpers(); macro->ResetUIHelpers();
@@ -557,10 +571,18 @@ void AdvSceneSwitcher::HighlightOnChange() const
return; return;
} }
if (macro->OnChangePreventedActionsRecently()) { if (macro->Paused()) {
HighlightWidget(ui->runMacroOnChange, Qt::yellow, return;
}
if (macro->ActionTriggerModePreventedActionsSince(
lastOnChangeHighlightCheckTime)) {
HighlightWidget(ui->actionTriggerMode, Qt::yellow,
Qt::transparent, true); Qt::transparent, true);
} }
lastOnChangeHighlightCheckTime =
std::chrono::high_resolution_clock::now();
} }
void AdvSceneSwitcher::on_macroSettings_clicked() void AdvSceneSwitcher::on_macroSettings_clicked()
@@ -610,17 +632,20 @@ setupToolBar(const std::initializer_list<std::initializer_list<QWidget *>>
auto toolbar = new QToolBar(); auto toolbar = new QToolBar();
toolbar->setIconSize({16, 16}); toolbar->setIconSize({16, 16});
QAction *lastSeperator = nullptr; QAction *lastSeparator = nullptr;
for (const auto &widgetGroup : widgetGroups) { for (const auto &widgetGroup : widgetGroups) {
for (const auto &widget : widgetGroup) { for (const auto &widget : widgetGroup) {
if (!widget) {
continue;
}
toolbar->addWidget(widget); toolbar->addWidget(widget);
} }
lastSeperator = toolbar->addSeparator(); lastSeparator = toolbar->addSeparator();
} }
if (lastSeperator) { if (lastSeparator) {
toolbar->removeAction(lastSeperator); toolbar->removeAction(lastSeparator);
} }
// Prevent "extension" button from showing up // Prevent "extension" button from showing up
@@ -633,7 +658,9 @@ void AdvSceneSwitcher::SetupMacroTab()
{ {
ui->macros->installEventFilter(this); ui->macros->installEventFilter(this);
if (GetMacros().size() == 0 && !switcher->disableHints) { auto &macros = GetTopLevelMacros();
if (macros.size() == 0 && !switcher->disableHints) {
addPulse = HighlightWidget(ui->macroAdd, QColor(Qt::green)); addPulse = HighlightWidget(ui->macroAdd, QColor(Qt::green));
} }
@@ -641,8 +668,7 @@ void AdvSceneSwitcher::SetupMacroTab()
{ui->macroUp, ui->macroDown}}); {ui->macroUp, ui->macroDown}});
ui->macroControlLayout->addWidget(macroControls); ui->macroControlLayout->addWidget(macroControls);
ui->macros->Reset(GetMacros(), ui->macros->Reset(macros, GetGlobalMacroSettings()._highlightExecuted);
GetGlobalMacroSettings()._highlightExecuted);
connect(ui->macros, SIGNAL(MacroSelectionChanged()), this, connect(ui->macros, SIGNAL(MacroSelectionChanged()), this,
SLOT(MacroSelectionChanged())); SLOT(MacroSelectionChanged()));
ui->runMacro->SetMacroTree(ui->macros); ui->runMacro->SetMacroTree(ui->macros);
@@ -655,21 +681,55 @@ void AdvSceneSwitcher::SetupMacroTab()
ui->macroPriorityWarning->setVisible( ui->macroPriorityWarning->setVisible(
switcher->functionNamesByPriority[0] != macro_func); switcher->functionNamesByPriority[0] != macro_func);
lastOnChangeHighlightCheckTime =
std::chrono::high_resolution_clock::now();
onChangeHighlightTimer.setInterval(1500); onChangeHighlightTimer.setInterval(1500);
connect(&onChangeHighlightTimer, SIGNAL(timeout()), this, connect(&onChangeHighlightTimer, SIGNAL(timeout()), this,
SLOT(HighlightOnChange())); SLOT(HighlightOnChange()));
onChangeHighlightTimer.start(); onChangeHighlightTimer.start();
// Reserve more space for macro edit area than for the macro list SetupMacroSearchWidgets(ui->macroSearchLayout, ui->macroSearchText,
ui->macroListMacroEditSplitter->setStretchFactor(0, 1); ui->macroSearchClear, ui->macroSearchType,
ui->macroListMacroEditSplitter->setStretchFactor(1, 4); ui->macroSearchRegex,
ui->macroSearchShowSettings,
[this]() { ui->macros->RefreshFilter(); });
if (switcher->saveWindowGeo) { static const std::vector<
if (shouldRestoreSplitter( std::pair<Macro::ActionTriggerMode, const char *>>
switcher->macroListMacroEditSplitterPosition)) { actionTriggerModes = {
{Macro::ActionTriggerMode::ALWAYS,
"AdvSceneSwitcher.macroTab.actionTriggerMode.always"},
{Macro::ActionTriggerMode::MACRO_RESULT_CHANGED,
"AdvSceneSwitcher.macroTab.actionTriggerMode.onOverallChange"},
{Macro::ActionTriggerMode::ANY_CONDITION_CHANGED,
"AdvSceneSwitcher.macroTab.actionTriggerMode.onAnyConditionChange"},
{Macro::ActionTriggerMode::ANY_CONDITION_TRIGGERED,
"AdvSceneSwitcher.macroTab.actionTriggerMode.onAnyConditionTriggered"},
};
for (const auto &[mode, name] : actionTriggerModes) {
ui->actionTriggerMode->addItem(obs_module_text(name),
static_cast<int>(mode));
}
ui->macroListBox->setSizePolicy(QSizePolicy::Ignored,
QSizePolicy::Preferred);
ui->macroListBox->setMinimumWidth(0);
ui->macroEditGroup->setSizePolicy(QSizePolicy::Ignored,
QSizePolicy::Preferred);
ui->macroEditGroup->setMinimumWidth(0);
if (shouldRestoreSplitter(
switcher->macroListMacroEditSplitterPosition)) {
ui->macroListMacroEditSplitter->setSizes(
switcher->macroListMacroEditSplitterPosition);
} else {
QTimer::singleShot(0, this, [this]() {
const auto totalWidth =
ui->macroListMacroEditSplitter->width();
ui->macroListMacroEditSplitter->setSizes( ui->macroListMacroEditSplitter->setSizes(
switcher->macroListMacroEditSplitterPosition); {totalWidth / 5, totalWidth * 4 / 5});
} });
} }
} }
@@ -700,6 +760,17 @@ void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
remove->setDisabled(ui->macros->SelectionEmpty()); remove->setDisabled(ui->macros->SelectionEmpty());
menu.addSeparator(); menu.addSeparator();
auto pause = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.pause"), this,
&AdvSceneSwitcher::PauseSelectedMacros);
pause->setDisabled(ui->macros->SelectionEmpty());
auto unpause = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.unpause"), this,
&AdvSceneSwitcher::UnpauseSelectedMacros);
unpause->setDisabled(ui->macros->SelectionEmpty());
menu.addSeparator();
auto group = menu.addAction( auto group = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.group"), ui->macros, obs_module_text("AdvSceneSwitcher.macroTab.group"), ui->macros,
&MacroTree::GroupSelectedItems); &MacroTree::GroupSelectedItems);
@@ -752,12 +823,55 @@ void AdvSceneSwitcher::CopyMacro()
newMacro->Load(data); newMacro->Load(data);
newMacro->PostLoad(); newMacro->PostLoad();
newMacro->SetName(name); newMacro->SetName(name);
RunPostLoadSteps(); RunAndClearPostLoadSteps();
Macro::PrepareMoveToGroup(macro->Parent(), newMacro); Macro::PrepareMoveToGroup(macro->Parent(), newMacro);
ui->macros->Add(newMacro, macro); ui->macros->Add(newMacro, macro);
disableAddButtonHighlight(); disableAddButtonHighlight();
MacroSignalManager::Instance()->Add(QString::fromStdString(name)); MacroSignalManager::Instance()->Add(QString::fromStdString(name));
RegisterMacroAddUndoRedo(name);
}
void AdvSceneSwitcher::PauseSelectedMacros()
{
auto selectedMacros = GetSelectedMacros();
if (selectedMacros.empty()) {
return;
}
auto lock = LockContext();
for (const auto &macro : selectedMacros) {
if (macro->IsGroup()) {
for (const auto &subitem :
GetGroupMacroEntries(macro.get())) {
subitem->SetPaused(true);
}
} else {
macro->SetPaused(true);
}
}
ui->macros->UpdateRunningStates();
}
void AdvSceneSwitcher::UnpauseSelectedMacros()
{
auto selectedMacros = GetSelectedMacros();
if (selectedMacros.empty()) {
return;
}
auto lock = LockContext();
for (const auto &macro : selectedMacros) {
if (macro->IsGroup()) {
for (const auto &subitem :
GetGroupMacroEntries(macro.get())) {
subitem->SetPaused(false);
}
} else {
macro->SetPaused(false);
}
}
ui->macros->UpdateRunningStates();
} }
bool MacroTabIsInFocus() bool MacroTabIsInFocus()

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