Compare commits

..

68 Commits
1.32.9 ... ui

Author SHA1 Message Date
WarmUpTill
df358de5fb Fix double free 2026-05-11 17:22:36 +02:00
WarmUpTill
6d7e23c88f locale: Clarify "reevaluate condition state" option for run macro action 2026-05-08 21:08:30 +02:00
WarmUpTill
9fff05c57c Fix "Run Macro" help being visible when option is not selected 2026-05-08 21:08:30 +02:00
WarmUpTill
6da151f7d7 Fix variable tab not listing variables on startup 2026-05-07 20:16:42 +02:00
WarmUpTill
61ee58ba70 Enable resolving plugin symbols in OBS crash reports 2026-05-06 20:40:15 +02:00
WarmUpTill
312fe1d648 Don't highlight paused macro in macro list 2026-05-06 20:40:15 +02:00
WarmUpTill
5d3c83c292 Don't log "on change" when macro is paused 2026-05-06 20:40:15 +02:00
WarmUpTill
964b6c6c71 Don't highlight action trigger mode when macro is paused 2026-05-06 20:40:15 +02:00
WarmUpTill
27116c0cd3 Ensure plugin is stopped before running cleanup steps 2026-05-06 20:40:15 +02:00
WarmUpTill
06c29bced5 Replace "Ignore Entry" condition logic with SwitchButton toggle 2026-05-06 20:40:10 +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
146 changed files with 7341 additions and 985 deletions

View File

@@ -255,6 +255,10 @@ ${_usage_host:-}"
cmake_args+=(
-DCMAKE_PREFIX_PATH="${advss_deps_path}"
-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}
)

View File

@@ -18,7 +18,4 @@ if (( ! ${+commands[brew]} )) {
}
brew bundle --file ${SCRIPT_HOME}/.Brewfile
# Workaround to make sure locally built openssl is picked up by cmake
brew uninstall --ignore-dependencies openssl@3 || true
rehash || true
log_group

View File

@@ -6,7 +6,7 @@ on:
description: "Project name detected by parsing build spec file"
value: ${{ jobs.check-event.outputs.pluginName }}
env:
DEP_DIR: .deps/advss-build-dependencies-3
DEP_DIR: .deps/advss-build-dependencies-4
jobs:
check-event:
name: Check GitHub Event Data 🔎

View File

@@ -504,7 +504,11 @@ endif()
# --- End of section ---
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 ---

View File

@@ -41,7 +41,8 @@
"description": "Build for macOS 11.0+ (Universal binary) for CI",
"generator": "Xcode",
"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",
"description": "Build for Windows x64 on CI",
"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",
"cacheVariables": {
"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",
"cacheVariables": {
"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
```
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).
## Contributing

View File

@@ -99,7 +99,6 @@ AdvSceneSwitcher.macroList.deleted="gelöscht"
AdvSceneSwitcher.macroList.duplicate="\"%1\" ist bereits ausgewählt!"
; Macro Logic
AdvSceneSwitcher.logic.none="Eintrag ignorieren"
AdvSceneSwitcher.logic.and="Und"
AdvSceneSwitcher.logic.or="Oder"
AdvSceneSwitcher.logic.andNot="Und nicht"
@@ -145,11 +144,7 @@ AdvSceneSwitcher.condition.file="Datei"
AdvSceneSwitcher.condition.file.type.match="entspricht"
AdvSceneSwitcher.condition.file.type.contentChange="Inhalt geändert"
AdvSceneSwitcher.condition.file.type.dateChange="Änderungsdatum geändert"
AdvSceneSwitcher.condition.file.remote="Entfernte Datei"
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.file.layout="{{filePath}}{{conditions}}{{regex}}"
AdvSceneSwitcher.condition.media="Medien"
AdvSceneSwitcher.condition.media.source="Quelle"
AdvSceneSwitcher.condition.media.anyOnScene="Beliebige Medienquelle in"
@@ -215,8 +210,8 @@ AdvSceneSwitcher.condition.record.state.start="Aufnahme läuft"
AdvSceneSwitcher.condition.record.state.pause="Aufnahme pausiert"
AdvSceneSwitcher.condition.record.state.stop="Aufnahme gestoppt"
AdvSceneSwitcher.condition.process="Prozess"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}läuft{{focused}}und ist fokusiert"
AdvSceneSwitcher.condition.process.entry.focus="Aktueller Vordergrundprozess: {{focusProcess}}"
AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}läuft{{focused}}und ist fokusiert"
AdvSceneSwitcher.condition.process.layout.focus="Aktueller Vordergrundprozess: {{focusProcess}}"
AdvSceneSwitcher.condition.idle="Leerlauf"
AdvSceneSwitcher.condition.idle.entry="Keine Tastatur- oder Mauseingaben für {{duration}}"
AdvSceneSwitcher.condition.pluginState="Plugin-Status"
@@ -412,7 +407,6 @@ AdvSceneSwitcher.action.recording.type.unpause="Aufnahme nicht mehr pausieren"
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.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.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"
@@ -421,7 +415,6 @@ AdvSceneSwitcher.action.replay.type.save="Replay Buffer speichern"
AdvSceneSwitcher.action.streaming="Stream"
AdvSceneSwitcher.action.streaming.type.stop="Stream stoppen"
AdvSceneSwitcher.action.streaming.type.start="Stream starten"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="Ausführen"
AdvSceneSwitcher.action.sceneVisibility="Sichtbarkeit von Szenenelementen"
AdvSceneSwitcher.action.sceneVisibility.type.show="Anzeigen"
@@ -457,7 +450,7 @@ AdvSceneSwitcher.action.macro="Makro"
AdvSceneSwitcher.action.macro.type.pause="Pausieren"
AdvSceneSwitcher.action.macro.type.unpause="Nicht mehr pausieren"
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.pluginState="Plugin-Status"
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.sourceShow="Übergang der Quelle anzeigen"
AdvSceneSwitcher.action.transition.type.sourceHide="Übergang der Quelle verstecken"
AdvSceneSwitcher.action.transition.entry.line1="Anpassen {{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Übergangstyp festlegen auf {{transitions}}"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Übergangsdauer festlegen auf {{duration}}Sekunden"
AdvSceneSwitcher.action.transition.layout.type="Anpassen {{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Übergangstyp festlegen auf {{transitions}}"
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Übergangsdauer festlegen auf {{duration}}Sekunden"
AdvSceneSwitcher.action.timer="Timer"
AdvSceneSwitcher.action.timer.type.pause="Pausieren"
AdvSceneSwitcher.action.timer.type.continue="Fortsetzen"

View File

@@ -40,6 +40,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Show system t
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Disable UI hints"
AdvSceneSwitcher.generalTab.generalBehavior.comboBoxFilterDisable="Disable filtering by typing in drop down menus"
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailure="Display warning if plugins cannot be loaded"
AdvSceneSwitcher.generalTab.generalBehavior.suppressCrashRecoveryDialog="Do not ask to keep the plugin stopped after an unclean shutdown"
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage="<html><body>Loading of the following plugin libraries was unsuccessful, which could result in some Advanced Scene Switcher functions not being available:%1Check the OBS logs for details.<br>This message can be disabled on the General tab.</body></html>"
AdvSceneSwitcher.generalTab.generalBehavior.warnCorruptedInstallMessage="The plugin installation seems to be corrupted and might crash!\nPlease make sure the plugin was installed correctly!"
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="Hide tabs which can be represented via macros"
@@ -305,7 +306,6 @@ AdvSceneSwitcher.macroList.deleted="deleted"
AdvSceneSwitcher.macroList.duplicate="\"%1\" is alreay selected!"
# Macro Logic
AdvSceneSwitcher.logic.none="Ignore entry"
AdvSceneSwitcher.logic.and="And"
AdvSceneSwitcher.logic.or="Or"
AdvSceneSwitcher.logic.andNot="And not"
@@ -368,11 +368,7 @@ AdvSceneSwitcher.condition.file.type.dateChange="modification date changed"
AdvSceneSwitcher.condition.file.type.exists="exists"
AdvSceneSwitcher.condition.file.type.isFile="is a file"
AdvSceneSwitcher.condition.file.type.isFolder="is a folder"
AdvSceneSwitcher.condition.file.remote="Remote file"
AdvSceneSwitcher.condition.file.local="Local file"
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
AdvSceneSwitcher.condition.file.layout="{{filePath}}{{conditions}}{{regex}}"
AdvSceneSwitcher.condition.media="Media"
AdvSceneSwitcher.condition.media.checkType.state="State matches"
AdvSceneSwitcher.condition.media.checkType.time="Time restriction matches"
@@ -487,8 +483,9 @@ AdvSceneSwitcher.condition.record.state.stop="Recording stopped"
AdvSceneSwitcher.condition.record.state.duration="Recording duration is longer than"
AdvSceneSwitcher.condition.record.entry="{{condition}}{{duration}}"
AdvSceneSwitcher.condition.process="Process"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}is running{{focused}}and is focused"
AdvSceneSwitcher.condition.process.entry.focus="Current foreground process:{{focusProcess}}"
AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}is running{{focused}}and is focused"
AdvSceneSwitcher.condition.process.layout.focus="Current foreground process:{{focusProcess}}"
AdvSceneSwitcher.condition.process.layout.path="{{checkPath}}Match binary path:{{path}}{{pathRegex}}"
AdvSceneSwitcher.condition.idle="Idle"
AdvSceneSwitcher.condition.idle.entry="No keyboard or mouse inputs for{{duration}}"
AdvSceneSwitcher.condition.pluginState="Plugin state"
@@ -676,6 +673,34 @@ AdvSceneSwitcher.condition.websocket.type.event="Scene Switcher Event"
AdvSceneSwitcher.condition.websocket.useRegex="Use regular expressions"
AdvSceneSwitcher.condition.websocket.entry.request="{{type}}was received:"
AdvSceneSwitcher.condition.websocket.entry.event="{{type}}was received from{{connection}}:"
AdvSceneSwitcher.condition.http="HTTP"
AdvSceneSwitcher.condition.http.layout="Receive{{method}}request on{{server}}"
AdvSceneSwitcher.condition.http.layout.path="Path:{{path}}{{regex}}"
AdvSceneSwitcher.condition.http.layout.body="Body:{{body}}{{regex}}"
AdvSceneSwitcher.condition.http.method.any="any"
AdvSceneSwitcher.condition.http.method.get="GET"
AdvSceneSwitcher.condition.http.method.post="POST"
AdvSceneSwitcher.condition.http.method.put="PUT"
AdvSceneSwitcher.condition.http.method.patch="PATCH"
AdvSceneSwitcher.condition.http.method.delete="DELETE"
AdvSceneSwitcher.httpServer.select="Select HTTP server"
AdvSceneSwitcher.httpServer.add="Add HTTP server"
AdvSceneSwitcher.httpServer.configure="Configure HTTP server"
AdvSceneSwitcher.httpServer.invalid="Invalid HTTP server"
AdvSceneSwitcher.httpServer.name="Name:"
AdvSceneSwitcher.httpServer.port="Port:"
AdvSceneSwitcher.httpServer.startOnLoad="Start on load"
AdvSceneSwitcher.httpServer.status.listening="Listening"
AdvSceneSwitcher.httpServer.status.stopped="Stopped"
AdvSceneSwitcher.httpServerTab.title="HTTP Servers"
AdvSceneSwitcher.httpServerTab.help="No HTTP servers configured.\nAdd one to receive incoming HTTP requests in conditions."
AdvSceneSwitcher.httpServerTab.addButton.tooltip="Add HTTP server"
AdvSceneSwitcher.httpServerTab.removeButton.tooltip="Remove HTTP server"
AdvSceneSwitcher.httpServerTab.name.header="Name"
AdvSceneSwitcher.httpServerTab.port.header="Port"
AdvSceneSwitcher.httpServerTab.status.header="Status"
AdvSceneSwitcher.httpServerTab.removeSingle.text="Are you sure you want to remove \"%1\"?"
AdvSceneSwitcher.httpServerTab.removeMultiple.text="Are you sure you want to remove %1 HTTP servers?"
AdvSceneSwitcher.condition.temporaryVariable="Macro property"
AdvSceneSwitcher.condition.variable="Variable"
AdvSceneSwitcher.condition.variable.type.compare="equals"
@@ -879,6 +904,15 @@ AdvSceneSwitcher.action.audio.fade.rate="{{fade}}Fade{{fadeTypes}}{{rate}}per se
AdvSceneSwitcher.action.audio.fade.wait="Wait for fade to complete."
AdvSceneSwitcher.action.audio.fade.abort="Abort already active fade."
AdvSceneSwitcher.action.audio.entry="{{actions}}{{audioSources}}{{volume}}{{volumeDB}}{{percentDBToggle}}{{syncOffset}}{{monitorTypes}}{{track}}"
AdvSceneSwitcher.action.playAudio="Play Audio File"
AdvSceneSwitcher.action.playAudio.file.browse="Browse for audio file"
AdvSceneSwitcher.action.playAudio.layout.volume="Volume{{volumeDB}}"
AdvSceneSwitcher.action.playAudio.layout.monitor="Monitoring{{monitorTypes}}"
AdvSceneSwitcher.action.playAudio.wait="Wait for playback to complete"
AdvSceneSwitcher.action.playAudio.monitorUnavailable="Audio monitoring not available"
AdvSceneSwitcher.action.playAudio.tracks="Tracks"
AdvSceneSwitcher.action.playAudio.layout.startOffset="{{useStartOffset}}Start at{{startOffset}}"
AdvSceneSwitcher.action.playAudio.layout.playbackDuration="{{useDuration}}Play for{{playbackDuration}}(0 = until end)"
AdvSceneSwitcher.action.recording="Recording"
AdvSceneSwitcher.action.recording.type.stop="Stop recording"
AdvSceneSwitcher.action.recording.type.start="Start recording"
@@ -887,9 +921,10 @@ AdvSceneSwitcher.action.recording.type.unpause="Unpause recording"
AdvSceneSwitcher.action.recording.type.split="Split recording file"
AdvSceneSwitcher.action.recording.type.changeOutputFolder="Change output folder"
AdvSceneSwitcher.action.recording.type.changeOutputFileFormat="Change filename formatting"
AdvSceneSwitcher.action.recording.type.addChapter="Add chapter"
AdvSceneSwitcher.action.recording.pause.hint="Note that depending on your recording settings you might not be able to pause recording"
AdvSceneSwitcher.action.recording.split.hint="Make sure to enable automatic file splitting in the OBS settings first!"
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{chapterName}}{{pauseHint}}{{splitHint}}"
AdvSceneSwitcher.action.replay="Replay buffer"
AdvSceneSwitcher.action.replay.saveWarn="Warning: Saving too frequently might result in the replay buffer not actually being saved!"
AdvSceneSwitcher.action.replay.durationWarn="Warning: Changing the maximum replay time will only apply the next time the replay buffer is started!"
@@ -906,7 +941,8 @@ AdvSceneSwitcher.action.streaming.type.server="Set server URL"
AdvSceneSwitcher.action.streaming.type.streamKey="Set stream key"
AdvSceneSwitcher.action.streaming.type.username="Set username"
AdvSceneSwitcher.action.streaming.type.password="Set password"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.streaming.getCurrentValue="Get current value"
AdvSceneSwitcher.action.streaming.layout="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}{{getCurrentValue}}"
AdvSceneSwitcher.action.run="Run"
AdvSceneSwitcher.action.run.wait.entry="{{wait}}Wait for process exit or at most {{timeout}}{{waitHelp}}"
AdvSceneSwitcher.action.run.wait.help.tooltip="Note that macro properties won't work if you leave this unticked, as the process spawns detached from the rest of logic and there's no control over it."
@@ -949,6 +985,8 @@ AdvSceneSwitcher.action.source.type.openPropertiesDialog="Open properties dialog
AdvSceneSwitcher.action.source.type.closeInteractionDialog="Close interaction dialog"
AdvSceneSwitcher.action.source.type.closeFilterDialog="Close filter dialog"
AdvSceneSwitcher.action.source.type.closePropertiesDialog="Close properties dialog"
AdvSceneSwitcher.action.source.type.getSetting="Get setting value"
AdvSceneSwitcher.action.source.type.getSettings="Get settings JSON"
AdvSceneSwitcher.action.source.entry="{{actions}}{{sources}}{{settingsButtons}}{{deinterlaceMode}}{{deinterlaceOrder}}{{refresh}}"
AdvSceneSwitcher.action.source.entry.settings="{{settings}}{{settingsInputMethod}}{{settingValue}}{{tempVar}}"
AdvSceneSwitcher.action.source.warning="Warning: Enabling and disabling sources globally cannot be controlled by the OBS UI\nYou might be looking for \"Scene item visibility\""
@@ -971,6 +1009,49 @@ AdvSceneSwitcher.action.source.inputMethod.json="Set setting JSON string"
AdvSceneSwitcher.action.source.refresh="Refresh"
AdvSceneSwitcher.action.source.refresh.tooltip="Repopulate the source settings selection with the settings of the source which's name matches the variable value."
AdvSceneSwitcher.action.source.dialog.accept="Accept changes"
AdvSceneSwitcher.action.sourceInteraction="Source Interaction"
AdvSceneSwitcher.action.sourceInteraction.source="Source"
AdvSceneSwitcher.action.sourceInteraction.noSelection="Select a step to edit it"
AdvSceneSwitcher.action.sourceInteraction.record="Record interaction ..."
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseMove="Mouse move (%1, %2)"
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseUp="up"
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseDown="down"
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseClick="Mouse %1 %2 (%3, %4)"
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseClickCount=" x%1"
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseWheel="Mouse wheel (%1, %2) dx=%3 dy=%4"
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.keyUp="up"
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.keyDown="down"
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.keyPress="Key %1"
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.keyPressWithText="Key %1 '%2'"
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.typeText="Type \"%1\""
AdvSceneSwitcher.action.sourceInteraction.step.listEntry.wait="Wait %1 ms"
AdvSceneSwitcher.action.sourceInteraction.step.edit.type="Type"
AdvSceneSwitcher.action.sourceInteraction.step.edit.mouseMove="Mouse move"
AdvSceneSwitcher.action.sourceInteraction.step.edit.mouseClick="Mouse click"
AdvSceneSwitcher.action.sourceInteraction.step.edit.mouseWheel="Mouse wheel"
AdvSceneSwitcher.action.sourceInteraction.step.edit.keyPress="Key press"
AdvSceneSwitcher.action.sourceInteraction.step.edit.typeText="Type text"
AdvSceneSwitcher.action.sourceInteraction.step.edit.wait="Wait"
AdvSceneSwitcher.action.sourceInteraction.step.edit.x="X"
AdvSceneSwitcher.action.sourceInteraction.step.edit.y="Y"
AdvSceneSwitcher.action.sourceInteraction.step.edit.button="Button"
AdvSceneSwitcher.action.sourceInteraction.step.edit.mouseUp="Mouse up"
AdvSceneSwitcher.action.sourceInteraction.step.edit.clickCount="Click count"
AdvSceneSwitcher.action.sourceInteraction.step.edit.wheelDx="Delta X"
AdvSceneSwitcher.action.sourceInteraction.step.edit.wheelDy="Delta Y"
AdvSceneSwitcher.action.sourceInteraction.step.edit.vkey="Virtual key"
AdvSceneSwitcher.action.sourceInteraction.step.edit.keyUp="Key up"
AdvSceneSwitcher.action.sourceInteraction.step.edit.text="Text"
AdvSceneSwitcher.action.sourceInteraction.step.edit.waitMs="Duration (ms)"
AdvSceneSwitcher.action.sourceInteraction.button.left="Left"
AdvSceneSwitcher.action.sourceInteraction.button.middle="Middle"
AdvSceneSwitcher.action.sourceInteraction.button.right="Right"
AdvSceneSwitcher.action.sourceInteraction.record.title="Record Source Interactions"
AdvSceneSwitcher.action.sourceInteraction.record.start="Start recording"
AdvSceneSwitcher.action.sourceInteraction.record.stop="Stop recording"
AdvSceneSwitcher.action.sourceInteraction.record.accept="Accept"
AdvSceneSwitcher.action.sourceInteraction.record.placeholder="Start recording and interact with the source to capture steps"
AdvSceneSwitcher.action.sourceInteraction.record.invalidSource="No valid source is selected.\nPlease select a source that supports interaction before recording."
AdvSceneSwitcher.action.media="Media"
AdvSceneSwitcher.action.media.type.play="Play"
AdvSceneSwitcher.action.media.type.pause="Pause"
@@ -990,12 +1071,12 @@ AdvSceneSwitcher.action.macro.type.pause="Pause"
AdvSceneSwitcher.action.macro.type.unpause="Unpause"
AdvSceneSwitcher.action.macro.type.togglePause="Toggle pause"
AdvSceneSwitcher.action.macro.type.resetCounter="Reset counter"
AdvSceneSwitcher.action.macro.type.run="Run macro"
AdvSceneSwitcher.action.macro.type.runActions="Run macro actions"
AdvSceneSwitcher.action.macro.type.run.conditions.ignore="Do not consider condition state"
AdvSceneSwitcher.action.macro.type.run.conditions.true="Only if conditions evaluate to true"
AdvSceneSwitcher.action.macro.type.run.conditions.false="Only if conditions evaluate to false"
AdvSceneSwitcher.action.macro.type.run.updateConditionMatchState="Reevaluate the condition state before executing this action"
AdvSceneSwitcher.action.macro.type.run.updateConditionMatchState.help="The plugin operates in phases:\n * The phase evaluating the macro conditions\n * The phase running macro actions\nMacros executed before this particular action might have side effects on the condition state of macros.\nCheck this option if you want those side effects to be taken into account when evaluating the condition state."
AdvSceneSwitcher.action.macro.type.run.updateConditionMatchState="Check conditions now, even if macro is paused"
AdvSceneSwitcher.action.macro.type.run.updateConditionMatchState.help="The plugin operates in phases:\n * The phase evaluating the macro conditions\n * The phase running macro actions\nMacros executed before this particular action might have side effects on the condition state of macros.\nAdditionally, paused macros are assumed to not match their conditions.\nCheck this option to force a fresh condition evaluation before running this action, regardless of whether the macro is paused."
AdvSceneSwitcher.action.macro.type.run.actionType.regular="actions"
AdvSceneSwitcher.action.macro.type.run.actionType.else="else-actions"
AdvSceneSwitcher.action.macro.type.run.skipWhenPaused="Skip execution when macro is paused"
@@ -1005,6 +1086,10 @@ AdvSceneSwitcher.action.macro.type.stop="Stop actions"
AdvSceneSwitcher.action.macro.type.disableAction="Disable action"
AdvSceneSwitcher.action.macro.type.enableAction="Enable action"
AdvSceneSwitcher.action.macro.type.toggleAction="Toggle action"
AdvSceneSwitcher.action.macro.type.getInfo="Get macro info"
AdvSceneSwitcher.action.macro.type.runMacro="Run macro"
AdvSceneSwitcher.action.macro.type.runMacro.noConditionsWarning="Warning: The selected macro has no conditions!"
AdvSceneSwitcher.action.macro.type.runMacro.help="Unlike \"Run macro actions\", this option will immediately re-evaluate the conditions of the selected macro and then run either its actions or else-actions depending on the result."
AdvSceneSwitcher.action.macro.type.nestedMacro="Nested macro"
AdvSceneSwitcher.action.macro.actionSelectionType.index="at index"
AdvSceneSwitcher.action.macro.actionSelectionType.label="with label"
@@ -1015,6 +1100,7 @@ AdvSceneSwitcher.action.macro.type.nestedMacro.elseActionHelp="This section allo
AdvSceneSwitcher.action.macro.layout.run="{{actions}}{{actionSections}}of{{macros}}"
AdvSceneSwitcher.action.macro.layout.run.condition="{{conditionBehaviors}}of{{conditionMacros}}"
AdvSceneSwitcher.action.macro.layout.actionState="{{actions}}{{actionSelectionType}}{{actionIndex}}{{label}}{{regex}}{{actionTypes}}in{{actionSections}}section of{{macros}}"
AdvSceneSwitcher.action.macro.layout.runMacro="{{actions}}{{runMacroHelp}}{{macros}}"
AdvSceneSwitcher.action.macro.layout.other="{{actions}}{{macros}}"
AdvSceneSwitcher.action.pluginState="Plugin state"
AdvSceneSwitcher.action.pluginState.type.stop="Stop the Advanced Scene Switcher plugin"
@@ -1087,13 +1173,16 @@ AdvSceneSwitcher.action.studioMode.type.enable="Enable studio mode"
AdvSceneSwitcher.action.studioMode.type.disable="Disable studio mode"
AdvSceneSwitcher.action.studioMode.entry="{{actions}}{{scenes}}"
AdvSceneSwitcher.action.transition="Transition"
AdvSceneSwitcher.action.transition.type.scene="scene transition"
AdvSceneSwitcher.action.transition.type.sceneOverride="scene transition override"
AdvSceneSwitcher.action.transition.type.sourceShow="source show transition"
AdvSceneSwitcher.action.transition.type.sourceHide="source hide transition"
AdvSceneSwitcher.action.transition.entry.line1="Modify{{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Set transition type to{{transitions}}"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Set transition duration to{{duration}}seconds"
AdvSceneSwitcher.action.transition.type.scene="Modify scene transition"
AdvSceneSwitcher.action.transition.type.sceneOverride="Modify scene transition override"
AdvSceneSwitcher.action.transition.type.sourceShow="Modify source show transition"
AdvSceneSwitcher.action.transition.type.sourceHide="Modify source hide transition"
AdvSceneSwitcher.action.transition.type.tbar="Modify T-Bar position"
AdvSceneSwitcher.action.transition.type.releaseTbar="Release T-Bar"
AdvSceneSwitcher.action.transition.layout.type="{{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Set transition type to{{transitions}}"
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Set transition duration to{{duration}}seconds"
AdvSceneSwitcher.action.transition.layout.tbar="Set T-Bar position to{{tbarPosition}}"
AdvSceneSwitcher.action.timer="Timer"
AdvSceneSwitcher.action.timer.type.pause="Pause"
AdvSceneSwitcher.action.timer.type.continue="Continue"
@@ -1253,23 +1342,52 @@ AdvSceneSwitcher.action.twitch.type.channel.info.category.set="Set stream catego
AdvSceneSwitcher.action.twitch.type.channel.info.tags.set="Set stream tags"
AdvSceneSwitcher.action.twitch.type.channel.info.language.set="Set stream language"
AdvSceneSwitcher.action.twitch.type.raid.start="Start raid"
AdvSceneSwitcher.action.twitch.type.raid.end="Cancel raid"
AdvSceneSwitcher.action.twitch.type.shoutout.send="Send shoutout"
AdvSceneSwitcher.action.twitch.type.shieldMode.start="Enable shield mode"
AdvSceneSwitcher.action.twitch.type.shieldMode.end="Disable shield mode"
AdvSceneSwitcher.action.twitch.type.commercial.start="Start commercial"
AdvSceneSwitcher.action.twitch.type.commercial.snooze="Snooze next ad"
AdvSceneSwitcher.action.twitch.type.marker.create="Create stream marker"
AdvSceneSwitcher.action.twitch.type.clip.create="Create stream clip"
AdvSceneSwitcher.action.twitch.type.chat.announcement.send="Send chat announcement"
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable="Enable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="Disable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.chat.followerOnly.enable="Enable chat's follower-only mode"
AdvSceneSwitcher.action.twitch.type.chat.followerOnly.disable="Disable chat's follower-only mode"
AdvSceneSwitcher.action.twitch.type.chat.subscriberOnly.enable="Enable chat's subscriber-only mode"
AdvSceneSwitcher.action.twitch.type.chat.subscriberOnly.disable="Disable chat's subscriber-only mode"
AdvSceneSwitcher.action.twitch.type.chat.slowMode.enable="Enable chat's slow mode"
AdvSceneSwitcher.action.twitch.type.chat.slowMode.disable="Disable chat's slow mode"
AdvSceneSwitcher.action.twitch.type.chat.nonModeratorDelay.enable="Enable chat's non-moderator message delay"
AdvSceneSwitcher.action.twitch.type.chat.nonModeratorDelay.disable="Disable chat's non-moderator message delay"
AdvSceneSwitcher.action.twitch.type.chat.uniqueMode.enable="Enable chat's unique message mode"
AdvSceneSwitcher.action.twitch.type.chat.uniqueMode.disable="Disable chat's unique message mode"
AdvSceneSwitcher.action.twitch.type.chat.sendMessage="Send chat message"
AdvSceneSwitcher.action.twitch.type.user.getInfo="Get user information"
AdvSceneSwitcher.action.twitch.type.user.ban="Ban user"
AdvSceneSwitcher.action.twitch.type.user.unban="Unban user"
AdvSceneSwitcher.action.twitch.type.user.block="Block user"
AdvSceneSwitcher.action.twitch.type.user.unblock="Unblock user"
AdvSceneSwitcher.action.twitch.type.user.moderator.add="Add user as moderator"
AdvSceneSwitcher.action.twitch.type.user.moderator.delete="Remove user as moderator"
AdvSceneSwitcher.action.twitch.type.user.vip.add="Add user as VIP"
AdvSceneSwitcher.action.twitch.type.user.vip.delete="Remove user as VIP"
AdvSceneSwitcher.action.twitch.type.reward.getInfo="Get channel points reward information"
AdvSceneSwitcher.action.twitch.type.channel.getInfo="Get channel information"
AdvSceneSwitcher.action.twitch.reward.toggleControl="Toggle reward name / variable selection control"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!"
AdvSceneSwitcher.action.twitch.layout.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}"
AdvSceneSwitcher.action.twitch.layout.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{nonModDelayDuration}}{{channel}}{{pointsReward}}"
AdvSceneSwitcher.action.twitch.layout.chat="Using account{{account}}{{actions}}on{{channel}}"
AdvSceneSwitcher.action.twitch.layout.channel.getInfo="Using account{{account}}{{actions}}of{{channel}}"
AdvSceneSwitcher.action.twitch.layout.user.getInfo="Using account{{account}}{{actions}}for{{userInfoQueryType}}{{userLogin}}{{userId}}"
AdvSceneSwitcher.action.twitch.layout.reward.getInfo="Using account{{account}}{{actions}}for channel{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
AdvSceneSwitcher.action.twitch.layout.user.getInfo.row1="Using account{{account}}{{actions}}"
AdvSceneSwitcher.action.twitch.layout.user.getInfo.row2="for{{userInfoQueryType}}{{userLogin}}{{userId}}"
AdvSceneSwitcher.action.twitch.layout.user.moderation.row1="Using account{{account}}{{actions}}"
AdvSceneSwitcher.action.twitch.layout.user.moderation.row2="on channel{{channel}}for{{userInfoQueryType}}{{userLogin}}{{userId}}"
AdvSceneSwitcher.action.twitch.layout.user.ban.row1="Using account{{account}}{{actions}}timeout{{duration}}"
AdvSceneSwitcher.action.twitch.layout.user.ban.row2="on channel{{channel}}for{{userInfoQueryType}}{{userLogin}}{{userId}}reason{{banReason}}"
AdvSceneSwitcher.action.twitch.layout.reward.getInfo.row1="Using account{{account}}{{actions}}for channel{{channel}}"
AdvSceneSwitcher.action.twitch.layout.reward.getInfo.row2="{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
AdvSceneSwitcher.action.twitch.title.title="Enter title"
AdvSceneSwitcher.action.twitch.marker.description="Describe marker"
AdvSceneSwitcher.action.twitch.clip.hasDelay="Add a slight delay before capturing the clip"
@@ -1290,6 +1408,8 @@ AdvSceneSwitcher.action.twitch.tags.duplicate.info="This tag is already in the l
AdvSceneSwitcher.action.twitch.tags.limit="Tag Limit Reached"
AdvSceneSwitcher.action.twitch.tags.limit.info="You can only have up to %1 tags."
AdvSceneSwitcher.action.twitch.type.channel.info.contentClassification.set="Set content classification labels"
AdvSceneSwitcher.action.twitch.type.channel.info.brandedContent.enable="Enable branded content"
AdvSceneSwitcher.action.twitch.type.channel.info.brandedContent.disable="Disable branded content"
AdvSceneSwitcher.action.twitch.contentClassification.debatedSocialIssuesAndPolitics="Discussions or debates about politics or sensitive social issues such as elections, civic integrity, military conflict, and civil rights."
AdvSceneSwitcher.action.twitch.contentClassification.drugsIntoxication="Excessive tobacco glorification or promotion, any marijuana consumption/use, legal drug and alcohol induced intoxication, discussions of illegal drugs."
AdvSceneSwitcher.action.twitch.contentClassification.sexualThemes="Content that focuses on sexualized activities, sexual topics, or experiences."
@@ -1395,6 +1515,7 @@ AdvSceneSwitcher.askBackup="Detected a new version of the Advanced Scene Switche
AdvSceneSwitcher.askForMacro="Select macro{{macroSelection}}"
AdvSceneSwitcher.crashDetected="OBS did not shut down cleanly (for example, due to a crash or freeze).\n\nThe Advanced Scene Switcher plugin would normally start automatically.\nWould you like to keep it stopped for now?"
AdvSceneSwitcher.crashDetected.suppressCheckbox="Do not show this dialog again"
AdvSceneSwitcher.close="Close"
AdvSceneSwitcher.browse="Browse"
@@ -1664,6 +1785,7 @@ AdvSceneSwitcher.script.language.lua="LUA"
AdvSceneSwitcher.script.language.select="--select language--"
AdvSceneSwitcher.script.language.layout="Script language:{{language}}"
AdvSceneSwitcher.script.file.open="Open"
AdvSceneSwitcher.script.file.select="Select Script File"
AdvSceneSwitcher.script.file.open.failed="Could not open script file!"
AdvSceneSwitcher.script.file.warning.notFound="Warning: The given file path was not found!"
AdvSceneSwitcher.script.file.warning.openFail="Warning: Could not open the script file!"
@@ -1672,6 +1794,10 @@ AdvSceneSwitcher.script.file.layout="Script file:{{path}}{{open}}"
AdvSceneSwitcher.tempVar.select="--select value--"
AdvSceneSwitcher.tempVar.selectionInfo.lastValues="Last values:"
AdvSceneSwitcher.tempVar.outputMappings="Save outputs to variables:"
AdvSceneSwitcher.tempVar.outputMappings.add="+ Add output mapping"
AdvSceneSwitcher.tempVar.outputMappings.toggle="Assign outputs to variables"
AdvSceneSwitcher.tempVar.outputMappings.remove="Remove output mapping"
AdvSceneSwitcher.tempVar.twitch.broadcaster_user_id="Twitch broadcaster user ID"
AdvSceneSwitcher.tempVar.twitch.broadcaster_user_id.description="The numerical Twitch user ID of the broadcaster."
@@ -2181,8 +2307,21 @@ AdvSceneSwitcher.tempVar.macro.runCount="Run count"
AdvSceneSwitcher.tempVar.macro.runCount.description="The number of times a macro was executed."
AdvSceneSwitcher.tempVar.macro.matchedCount="Matched count"
AdvSceneSwitcher.tempVar.macro.matchedCount.description="The number of macros of which the condition state was true."
AdvSceneSwitcher.tempVar.macro.info.conditionCount="Condition count"
AdvSceneSwitcher.tempVar.macro.info.conditionCount.description="The number of conditions in the macro."
AdvSceneSwitcher.tempVar.macro.info.actionCount="Action count"
AdvSceneSwitcher.tempVar.macro.info.actionCount.description="The number of actions in the macro."
AdvSceneSwitcher.tempVar.macro.info.elseActionCount="Else-action count"
AdvSceneSwitcher.tempVar.macro.info.elseActionCount.description="The number of else-actions in the macro."
AdvSceneSwitcher.tempVar.macro.info.paused="Paused"
AdvSceneSwitcher.tempVar.macro.info.paused.description="Whether the macro is currently paused. Value is \"true\" or \"false\"."
AdvSceneSwitcher.tempVar.macro.info.runCount="Run count"
AdvSceneSwitcher.tempVar.macro.info.runCount.description="The number of times the macro has been executed."
AdvSceneSwitcher.tempVar.macro.info.secondsSinceLastRun="Seconds since last run"
AdvSceneSwitcher.tempVar.macro.info.secondsSinceLastRun.description="The number of seconds elapsed since the macro was last executed. Value is -1 if the macro has never been executed."
AdvSceneSwitcher.tempVar.process.name="Process name"
AdvSceneSwitcher.tempVar.process.path="Process path"
AdvSceneSwitcher.tempVar.run.process.id="Process ID"
AdvSceneSwitcher.tempVar.run.process.id.description="PID of the process assigned by the system."
@@ -2213,6 +2352,8 @@ AdvSceneSwitcher.tempVar.video.text="OCR text"
AdvSceneSwitcher.tempVar.video.text.description="The text detected in a given video input frame."
AdvSceneSwitcher.tempVar.video.color="Average color"
AdvSceneSwitcher.tempVar.video.color.description="The average RGB color in a given video input frame in HexArgb format."
AdvSceneSwitcher.tempVar.video.dominantColor="Dominant color"
AdvSceneSwitcher.tempVar.video.dominantColor.description="The most dominant RGB color in a given video input frame in HexArgb format.\nDetermined using k-means clustering, which groups similar colors and returns the center of the largest group.\nDue to the nature of the algorithm the result may vary slightly between evaluations."
AdvSceneSwitcher.tempVar.websocket.message="Received websocket message"
AdvSceneSwitcher.tempVar.websocket.message.description="The received websocket message, which matched the given pattern"
@@ -2342,9 +2483,14 @@ AdvSceneSwitcher.tempVar.gameCapture.executable="Executable"
AdvSceneSwitcher.tempVar.gameCapture.executable.description="Executable name of the application captured by the source."
AdvSceneSwitcher.tempVar.http.status="Status code"
AdvSceneSwitcher.tempVar.http.body="Message body"
AdvSceneSwitcher.tempVar.http.error="Error"
AdvSceneSwitcher.tempVar.http.error.description="Empty when no error occurred.\nOther possible values:\n\n * Could not establish connection\n * Failed to bind IP address\n * Failed to read connection\n * Failed to write connection\n * Maximum redirect count exceeded\n * Connection handling canceled\n * SSL connection failed\n * SSL certificate loading failed\n * SSL server verification failed\n * Unsupported HTTP multipart boundary characters\n * Compression failed\n * Connection timed out\n * Proxy connection failed\n * Unknown"
AdvSceneSwitcher.tempVar.http.method="Method"
AdvSceneSwitcher.tempVar.http.method.description="Received HTTP request method"
AdvSceneSwitcher.tempVar.http.path="Request path"
AdvSceneSwitcher.tempVar.http.path.description="Received HTTP request path"
AdvSceneSwitcher.tempVar.http.body="Message body"
AdvSceneSwitcher.tempVar.http.body.description="Received HTTP request body"
AdvSceneSwitcher.tempVar.mqtt.message="Message"
@@ -2354,6 +2500,14 @@ AdvSceneSwitcher.tempVar.cursor.y="Cursor position (Y)"
AdvSceneSwitcher.tempVar.replay.lastSavePath="Last save path"
AdvSceneSwitcher.tempVar.replay.lastSavePath.description="The file path of the last replay buffer saved."
AdvSceneSwitcher.tempVar.sequence.macro="Macro"
AdvSceneSwitcher.tempVar.sequence.macro.description="The name of the macro executed by the \"Sequence\" action.\nIf no macro was executed the returned value is the empty string."
AdvSceneSwitcher.tempVar.sequence.nextMacro="Next Macro"
AdvSceneSwitcher.tempVar.sequence.nextMacro.description="The name of the macro which will be executed next after the index was set.\nIf no macro will be executed the returned value is the empty string."
AdvSceneSwitcher.tempVar.random.macro="Macro"
AdvSceneSwitcher.tempVar.random.macro.description="The name of the macro executed by the \"Random\" action.\nIf no macro was executed the returned value is the empty string."
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectCanvas="--select canvas--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"

View File

@@ -83,7 +83,6 @@ AdvSceneSwitcher.macroTab.highlightTrueConditions="Resaltar condiciones de la ma
AdvSceneSwitcher.macroTab.highlightPerformedActions="Resaltar acciones realizadas recientemente de la macro seleccionada actualmente"
; Lógica de macros
AdvSceneSwitcher.logic.none="Omitir entrada"
AdvSceneSwitcher.logic.and="Y"
AdvSceneSwitcher.logic.or="O"
AdvSceneSwitcher.logic.andNot="Y no"
@@ -117,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.window="Ventana"
AdvSceneSwitcher.condition.file="Archivo"
AdvSceneSwitcher.condition.file.entry.line1="Contenido de{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
AdvSceneSwitcher.condition.file.layout="Contenido de{{filePath}}{{conditions}}{{regex}}"
AdvSceneSwitcher.condition.media="Medios"
AdvSceneSwitcher.condition.media.anyOnScene="Cualquier fuente multimedia activada"
AdvSceneSwitcher.condition.media.allOnScene="Todas las fuentes de medios activadas"
@@ -175,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.stop="Grabación detenida"
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.entry="No hay entradas de teclado o ratón durante {{duration}}"
AdvSceneSwitcher.condition.pluginState="Estado del complemento"
@@ -334,7 +331,6 @@ AdvSceneSwitcher.action.recording.type.start="Iniciar grabación"
AdvSceneSwitcher.action.recording.type.pause="Pausar 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.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
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.type.stop="Detener el búfer de reproducción"
@@ -343,7 +339,6 @@ AdvSceneSwitcher.action.replay.type.save="Guardar búfer de reproducción"
AdvSceneSwitcher.action.streaming="Transmisión"
AdvSceneSwitcher.action.streaming.type.stop="Detener transmisión"
AdvSceneSwitcher.action.streaming.type.start="Iniciar transmisión"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="Ejecutar"
AdvSceneSwitcher.action.sceneVisibility="Visibilidad del elemento de escena"
AdvSceneSwitcher.action.sceneVisibility.type.show="Mostrar"
@@ -375,7 +370,7 @@ AdvSceneSwitcher.action.macro="Macro"
AdvSceneSwitcher.action.macro.type.pause="Pausa"
AdvSceneSwitcher.action.macro.type.unpause="Reanudar"
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.pluginState="Estado del complemento"
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.sourceShow="transición del programa de origen"
AdvSceneSwitcher.action.transition.type.sourceHide="fuente ocultar transición"
AdvSceneSwitcher.action.transition.entry.line1="Modificar {{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.entry.line2="{{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.type="Modificar {{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Establecer el tipo de transición en {{transitions}}"
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Establecer la duración de la transición en {{duration}}segundos"
AdvSceneSwitcher.action.timer="Temporizador"
AdvSceneSwitcher.action.timer.type.pause="Pausa"
AdvSceneSwitcher.action.timer.type.continue="Continuar"

View File

@@ -131,7 +131,6 @@ AdvSceneSwitcher.macroList.deleted="supprimé"
AdvSceneSwitcher.macroList.duplicate="\"%1\" est déjà sélectionné !"
; Macro Logic
AdvSceneSwitcher.logic.none="Ignorer l'entrée"
AdvSceneSwitcher.logic.and="Et"
AdvSceneSwitcher.logic.or="Ou"
AdvSceneSwitcher.logic.andNot="Et pas"
@@ -187,8 +186,6 @@ AdvSceneSwitcher.condition.file="Fichier"
AdvSceneSwitcher.condition.file.type.match="correspond à"
AdvSceneSwitcher.condition.file.type.contentChange="a changé de contenu"
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.source="Source"
AdvSceneSwitcher.condition.media.anyOnScene="Toute source média sur la scène"
@@ -281,8 +278,8 @@ AdvSceneSwitcher.condition.record.state.start="Enregistrement en cours"
AdvSceneSwitcher.condition.record.state.pause="Enregistrement en pause"
AdvSceneSwitcher.condition.record.state.stop="Arrêt de l'enregistrement"
AdvSceneSwitcher.condition.process="Processus"
AdvSceneSwitcher.condition.process.entry="{{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="{{processes}}{{regex}}en cours d'exécution{{focused}}et est au premier plan"
AdvSceneSwitcher.condition.process.layout.focus="Processus au premier plan actuel :{{focusProcess}}"
AdvSceneSwitcher.condition.idle="Inactif"
AdvSceneSwitcher.condition.idle.entry="Aucune entrée de clavier ou de souris pendant{{duration}}"
AdvSceneSwitcher.condition.pluginState="État du plugin"
@@ -553,7 +550,7 @@ AdvSceneSwitcher.action.macro="Macro"
AdvSceneSwitcher.action.macro.type.pause="Pause"
AdvSceneSwitcher.action.macro.type.unpause="Reprendre"
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.disableAction="Désactiver 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.sourceShow="Transition d'affichage 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.entry.line2="{{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.type="Modifier{{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Définir le type de transition sur{{transitions}}"
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Définir la durée de la transition à{{duration}}secondes"
AdvSceneSwitcher.action.timer="Minuterie"
AdvSceneSwitcher.action.timer.type.pause="Mettre en pause"
AdvSceneSwitcher.action.timer.type.continue="Continuer"

View File

@@ -275,7 +275,6 @@ AdvSceneSwitcher.macroList.deleted="削除"
AdvSceneSwitcher.macroList.duplicate="\"%1\" はすでに選択されています!"
# Macro Logic
AdvSceneSwitcher.logic.none="入力無視"
; AdvSceneSwitcher.logic.and="And"
; AdvSceneSwitcher.logic.or="Or"
; AdvSceneSwitcher.logic.andNot="And not"
@@ -331,14 +330,6 @@ AdvSceneSwitcher.condition.window.entry.text="{{checkText}}ウィンドウにテ
AdvSceneSwitcher.condition.window.entry.text.note="このオプションは、ウィンドウに表示されているすべてのテキストに対して機能するとは限りません。\nその場合は、代わりにビデオ条件 OCR チェックの使用を検討してください。"
AdvSceneSwitcher.condition.window.entry.currentFocus="現在のフォーカスウィンドウ:{{focusWindow}}"
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.checkType.state="状態が一致"
AdvSceneSwitcher.condition.media.checkType.time="時間が一致"
@@ -453,8 +444,8 @@ AdvSceneSwitcher.condition.stream.service.tooltip="現在のサービス名: %1"
AdvSceneSwitcher.condition.record.state.duration="録音時間が長くなっています。"
; AdvSceneSwitcher.condition.record.entry="{{condition}}{{duration}}"
AdvSceneSwitcher.condition.process="プロセス"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}が実行中{{focused}}に集中しています"
AdvSceneSwitcher.condition.process.entry.focus="現在のフォアグラウンドプロセス:{{focusProcess}}"
AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}が実行中{{focused}}に集中しています"
AdvSceneSwitcher.condition.process.layout.focus="現在のフォアグラウンドプロセス:{{focusProcess}}"
AdvSceneSwitcher.condition.idle="アイドル"
AdvSceneSwitcher.condition.idle.entry="{{duration}}の間、キーボードまたはマウスの入力がありません"
AdvSceneSwitcher.condition.pluginState="プラグインの状態"
@@ -931,7 +922,7 @@ AdvSceneSwitcher.action.macro.type.pause="一時停止"
AdvSceneSwitcher.action.macro.type.unpause="一時停止解除"
AdvSceneSwitcher.action.macro.type.togglePause="一時停止を切り替え"
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.true="条件が true と評価された場合のみ"
AdvSceneSwitcher.action.macro.type.run.conditions.false="条件が false と評価された場合のみ"
@@ -1020,9 +1011,9 @@ AdvSceneSwitcher.action.transition="トランジション"
; AdvSceneSwitcher.action.transition.type.sceneOverride="シーントランジションオーバーライド"
; AdvSceneSwitcher.action.transition.type.sourceShow="ソース表示トランジション"
; AdvSceneSwitcher.action.transition.type.sourceHide="ソース非表示トランジション"
; AdvSceneSwitcher.action.transition.entry.line1="{{type}}{{scenes}}{{sources}} の変更"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}トランジションタイプを{{transitions}}に設定します"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}移行時間を {{duration}} 秒に設定します"
; AdvSceneSwitcher.action.transition.layout.type="{{type}}{{scenes}}{{sources}} の変更"
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}トランジションタイプを{{transitions}}に設定します"
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}移行時間を {{duration}} 秒に設定します"
AdvSceneSwitcher.action.timer="タイマー"
AdvSceneSwitcher.action.timer.type.pause="一時停止"
AdvSceneSwitcher.action.timer.type.continue="続行"
@@ -1190,8 +1181,15 @@ AdvSceneSwitcher.action.twitch.reward.toggleControl="リワード名/変数選
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Twitchアカウントを選択しないとカテゴリを選択できません"
; AdvSceneSwitcher.action.twitch.layout.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}"
AdvSceneSwitcher.action.twitch.layout.chat="{{channel}}でアカウント{{account}}{{actions}}を使用しています"
AdvSceneSwitcher.action.twitch.layout.user.getInfo="{{userInfoQueryType}}{{userLogin}}{{userId}}でアカウント{{account}}{{actions}}を使用"
AdvSceneSwitcher.action.twitch.layout.reward.getInfo="チャンネル{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}でアカウント{{account}}{{actions}}を使用"
AdvSceneSwitcher.action.twitch.layout.channel.getInfo="チャンネル{{channel}}でアカウント{{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.marker.description="マーカーの説明"
AdvSceneSwitcher.action.twitch.clip.hasDelay="クリップをキャプチャする前にわずかな遅延を追加します"

View File

@@ -239,7 +239,6 @@ AdvSceneSwitcher.macroList.deleted="excluída"
AdvSceneSwitcher.macroList.duplicate="\"%1\" já está selecionada!"
; Macro Logic
AdvSceneSwitcher.logic.none="Ignorar entrada"
AdvSceneSwitcher.logic.and="E"
AdvSceneSwitcher.logic.or="Ou"
AdvSceneSwitcher.logic.andNot="E não"
@@ -295,11 +294,6 @@ AdvSceneSwitcher.condition.file="Arquivo"
AdvSceneSwitcher.condition.file.type.match="corresponde"
AdvSceneSwitcher.condition.file.type.contentChange="conteúdo 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.checkType.state="Estado corresponde"
AdvSceneSwitcher.condition.media.checkType.time="Restrição de tempo corresponde"
@@ -400,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.entry="{{condition}}{{duration}}"
AdvSceneSwitcher.condition.process="Processo"
AdvSceneSwitcher.condition.process.entry="{{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="{{processes}}{{regex}}está em execução{{focused}}e está em foco"
AdvSceneSwitcher.condition.process.layout.focus="Processo atual em primeiro plano:{{focusProcess}}"
AdvSceneSwitcher.condition.idle="Inativo"
AdvSceneSwitcher.condition.idle.entry="Sem entradas de teclado ou mouse por {{duration}}"
AdvSceneSwitcher.condition.pluginState="Estado do plugin"
@@ -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.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.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
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.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.username="Definir nome de usuário"
AdvSceneSwitcher.action.streaming.type.password="Definir senha"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
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.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."
@@ -846,7 +838,7 @@ AdvSceneSwitcher.action.macro="Macro"
AdvSceneSwitcher.action.macro.type.pause="Pausar"
AdvSceneSwitcher.action.macro.type.unpause="Retomar"
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.true="Apenas se as condições forem verdadeiras"
AdvSceneSwitcher.action.macro.type.run.conditions.false="Apenas se as condições forem falsas"
@@ -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.sourceShow="transição de exibiçã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.entry.line2="{{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.type="Modificar{{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}Definir tipo de transição para{{transitions}}"
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Definir duração da transição para{{duration}}segundos"
AdvSceneSwitcher.action.timer="Temporizador"
AdvSceneSwitcher.action.timer.type.pause="Pausar"
AdvSceneSwitcher.action.timer.type.continue="Continuar"
@@ -1065,7 +1057,7 @@ 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.sendMessage="Enviar mensagem de chat"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Não é possível selecionar categoria sem selecionar uma conta Twitch primeiro!"
AdvSceneSwitcher.action.twitch.layout.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.layout.chat="Usando conta{{account}}{{actions}}em{{channel}}"
AdvSceneSwitcher.action.twitch.title.title="Digite o título"
AdvSceneSwitcher.action.twitch.marker.description="Descreva o marcador"

View File

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

View File

@@ -78,7 +78,6 @@ AdvSceneSwitcher.macroTab.expandAll="Hepsini Genişlet"
AdvSceneSwitcher.macroTab.collapseAll="Hepsini Küçült"
; Macro Logic
AdvSceneSwitcher.logic.none="Girişi yoksay"
AdvSceneSwitcher.logic.and="Ve"
AdvSceneSwitcher.logic.or="Ya da"
AdvSceneSwitcher.logic.andNot="ve değil"
@@ -107,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.window="Pencere"
AdvSceneSwitcher.condition.file="Dosya"
AdvSceneSwitcher.condition.file.entry.line1="İçerik{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
AdvSceneSwitcher.condition.file.layout="İçerik{{filePath}}{{conditions}}{{regex}}"
AdvSceneSwitcher.condition.media="Medya"
AdvSceneSwitcher.condition.media.anyOnScene="Herhangi bir medya kaynağı"
AdvSceneSwitcher.condition.media.allOnScene="Tüm medya kaynakları "
@@ -152,7 +149,7 @@ AdvSceneSwitcher.condition.record.state.start="Kayıt Çalışıyor"
AdvSceneSwitcher.condition.record.state.pause="Kayıt durakladı"
AdvSceneSwitcher.condition.record.state.stop="Kayıt durdu"
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.entry="...için klavye veya fare girişi yok {{duration}}"
AdvSceneSwitcher.condition.pluginState="Eklenti durumu"
@@ -265,7 +262,6 @@ AdvSceneSwitcher.action.recording.type.start="Kayıt Başlat"
AdvSceneSwitcher.action.recording.type.pause="Kayıt Duraklat"
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.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
AdvSceneSwitcher.action.replay="Tekrar arabelleği"
AdvSceneSwitcher.action.replay.type.stop="Tekrar arabelleğini durdur"
AdvSceneSwitcher.action.replay.type.start="Tekrar arabelleğini başlat"
@@ -273,7 +269,6 @@ AdvSceneSwitcher.action.replay.type.save="Tekrar arabelleğini kaydet"
AdvSceneSwitcher.action.streaming="Yayın"
AdvSceneSwitcher.action.streaming.type.stop="Yayın durdur"
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.sceneVisibility="Sahne öğesi görünürlüğü"
AdvSceneSwitcher.action.sceneVisibility.type.show="Göster"
@@ -304,7 +299,7 @@ AdvSceneSwitcher.action.macro="Makro"
AdvSceneSwitcher.action.macro.type.pause="Duraklat"
AdvSceneSwitcher.action.macro.type.unpause="Duraklatma"
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.type.stop="Advanced Scene Switcher eklentisini durdurun"
AdvSceneSwitcher.action.pluginState.type.noMatch="Eşleşmeme davranışını değiştirin:"
@@ -341,8 +336,8 @@ AdvSceneSwitcher.action.file.type.write="Yaz"
AdvSceneSwitcher.action.file.type.append="Ekle"
AdvSceneSwitcher.action.file.entry="{{actions}} {{filePath}}:"
AdvSceneSwitcher.action.transition="Geçiş"
AdvSceneSwitcher.action.transition.entry.line2="{{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.transition="{{setTransition}}Geçiş türünü ayarla {{transitions}}"
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}Geçiş süresini şuna ayarla: {{duration}}saniyeler"
AdvSceneSwitcher.action.timer="Zamanlayıcı"
AdvSceneSwitcher.action.timer.type.pause="Duraklat"
AdvSceneSwitcher.action.timer.type.continue="Devam et"

View File

@@ -259,7 +259,6 @@ AdvSceneSwitcher.macroList.deleted="删除"
AdvSceneSwitcher.macroList.duplicate="\"%1\" 已选择!"
; Macro Logic
AdvSceneSwitcher.logic.none="忽略条件"
AdvSceneSwitcher.logic.and="且"
AdvSceneSwitcher.logic.or="或"
AdvSceneSwitcher.logic.andNot="且不"
@@ -318,11 +317,7 @@ AdvSceneSwitcher.condition.file="文件"
AdvSceneSwitcher.condition.file.type.match="内容匹配"
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.file.layout="{{filePath}}{{conditions}}{{regex}}"
AdvSceneSwitcher.condition.media="媒体"
AdvSceneSwitcher.condition.media.checkType.state="状态匹配"
AdvSceneSwitcher.condition.media.checkType.time="时间限制匹配"
@@ -428,8 +423,8 @@ AdvSceneSwitcher.condition.record.state.stop="录制停止"
AdvSceneSwitcher.condition.record.state.duration="录制时间长于"
AdvSceneSwitcher.condition.record.entry="{{condition}}{{duration}}"
AdvSceneSwitcher.condition.process="进程"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}为正在运行中{{focused}}且为焦点"
AdvSceneSwitcher.condition.process.entry.focus="当前焦点进程: {{focusProcess}}"
AdvSceneSwitcher.condition.process.layout="{{processes}}{{regex}}为正在运行中{{focused}}且为焦点"
AdvSceneSwitcher.condition.process.layout.focus="当前焦点进程: {{focusProcess}}"
AdvSceneSwitcher.condition.idle="闲置检测"
AdvSceneSwitcher.condition.idle.entry="{{duration}}内没有键盘或鼠标输入"
AdvSceneSwitcher.condition.pluginState="插件状态"
@@ -813,7 +808,6 @@ AdvSceneSwitcher.action.recording.type.changeOutputFolder="更改输出文件夹
AdvSceneSwitcher.action.recording.type.changeOutputFileFormat="改变文件名的格式"
AdvSceneSwitcher.action.recording.pause.hint="请注意,根据您的录制设置,您可能无法暂停录制"
AdvSceneSwitcher.action.recording.split.hint="注意请先确保在OBS设置中启用自动分割文件!"
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
AdvSceneSwitcher.action.replay="回放缓存"
AdvSceneSwitcher.action.replay.saveWarn="警告:保存过于频繁可能会导致回放缓存实际上未保存!"
AdvSceneSwitcher.action.replay.durationWarn="警告:更改回放缓存时长上限,仅适用于下次回放缓存开启时!"
@@ -830,7 +824,6 @@ AdvSceneSwitcher.action.streaming.type.server="设置服务器"
AdvSceneSwitcher.action.streaming.type.streamKey="设置推流码"
AdvSceneSwitcher.action.streaming.type.username="设置用户名"
AdvSceneSwitcher.action.streaming.type.password="设置密码"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="运行"
AdvSceneSwitcher.action.run.wait.entry="{{wait}}进程退出或最多等待{{timeout}}{{waitHelp}}"
AdvSceneSwitcher.action.run.wait.help.tooltip="请注意,如果不勾选此选项,宏属性将不起作用,因为进程的启动会脱离逻辑的其他部分,因此无法对其进行控制."
@@ -905,7 +898,7 @@ AdvSceneSwitcher.action.macro="宏"
AdvSceneSwitcher.action.macro.type.pause="停用"
AdvSceneSwitcher.action.macro.type.unpause="启用"
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.true="仅当条件结果为真时"
AdvSceneSwitcher.action.macro.type.run.conditions.false="仅当条件结果为假时"
@@ -994,9 +987,9 @@ AdvSceneSwitcher.action.transition.type.scene="场景转场动画"
AdvSceneSwitcher.action.transition.type.sceneOverride="覆盖场景转场动画"
AdvSceneSwitcher.action.transition.type.sourceShow="显示源转场动画"
AdvSceneSwitcher.action.transition.type.sourceHide="隐藏源转场动画"
AdvSceneSwitcher.action.transition.entry.line1="更改 {{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}将转场动画类型设置为 {{transitions}}"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}将转场动画时长设置为 {{duration}}秒"
AdvSceneSwitcher.action.transition.layout.type="更改 {{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}将转场动画类型设置为 {{transitions}}"
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}将转场动画时长设置为 {{duration}}秒"
AdvSceneSwitcher.action.timer="计时器"
AdvSceneSwitcher.action.timer.type.pause="暂停"
AdvSceneSwitcher.action.timer.type.continue="继续"
@@ -1139,10 +1132,17 @@ AdvSceneSwitcher.action.twitch.type.user.getInfo="获取用户信息"
AdvSceneSwitcher.action.twitch.type.reward.getInfo="获取频道积分奖励信息"
AdvSceneSwitcher.action.twitch.reward.toggleControl="切换奖励名称/变量选择控制"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="在未选择 Twitch 帐户的情况下无法选择类别!"
AdvSceneSwitcher.action.twitch.layout.default="{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}"
AdvSceneSwitcher.action.twitch.layout.default="{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}{{nonModDelayDuration}}"
AdvSceneSwitcher.action.twitch.layout.chat="使用账户{{account}}{{actions}}{{channel}}"
AdvSceneSwitcher.action.twitch.layout.user.getInfo="使用账户{{account}}{{actions}}{{userInfoQueryType}}{{userLogin}}{{userId}}"
AdvSceneSwitcher.action.twitch.layout.reward.getInfo="使用账户{{account}}{{actions}} 频道{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
AdvSceneSwitcher.action.twitch.layout.channel.getInfo="使用账户{{account}}{{actions}}频道{{channel}}"
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="使用账户{{account}}{{actions}} 频道{{channel}}"
AdvSceneSwitcher.action.twitch.layout.reward.getInfo.row2="{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
AdvSceneSwitcher.action.twitch.title.title="输入标题"
AdvSceneSwitcher.action.twitch.marker.description="标记描述"
AdvSceneSwitcher.action.twitch.clip.hasDelay="在捕捉视频片段前稍加延迟"

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

View File

@@ -251,6 +251,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="suppressCrashRecoveryDialog">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.suppressCrashRecoveryDialog</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -597,7 +604,7 @@
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QGroupBox" name="groupBox_5">
<widget class="QGroupBox" name="macroListBox">
<property name="title">
<string>AdvSceneSwitcher.macroTab.macros</string>
</property>

View File

@@ -322,8 +322,7 @@ void SwitcherData::SetPreconditions()
{
// Window title
lastTitle = currentTitle;
std::string title;
GetCurrentWindowTitle(title);
auto title = GetCurrentWindowTitle();
for (auto &window : ignoreWindowsSwitches) {
bool equals = (title == window);
bool matches = false;
@@ -343,7 +342,7 @@ void SwitcherData::SetPreconditions()
currentTitle = title;
// Process name
GetForegroundProcessName(currentForegroundProcess);
currentForegroundProcess = GetForegroundProcessName();
// Macro
InvalidateMacroTempVarValues();
@@ -507,6 +506,8 @@ bool SwitcherData::AnySceneTransitionStarted()
******************************************************************************/
extern "C" EXPORT void FreeSceneSwitcher()
{
switcher->Stop();
PlatformCleanup();
RunPluginCleanupSteps();
@@ -628,13 +629,20 @@ static void handleSceneCollectionCleanup()
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);
}
// Note to future self:
// be careful using switcher->m here as there is potential for deadlocks when using
// frontend functions such as obs_frontend_set_current_scene()
static void OBSEvent(enum obs_frontend_event event, void *switcher)
static void OBSEvent(enum obs_frontend_event event, void *)
{
if (!switcher) {
return;
@@ -768,8 +776,6 @@ void HighlightMacroSettingsButton(bool enable)
enable);
}
void SetupActionQueues();
extern "C" EXPORT void InitSceneSwitcher(obs_module_t *module,
translateFunc translate)
{
@@ -781,12 +787,11 @@ extern "C" EXPORT void InitSceneSwitcher(obs_module_t *module,
PlatformInit();
LoadPlugins();
SetupDock();
SetupActionQueues();
RunPluginInitSteps();
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(
obs_module_text("AdvSceneSwitcher.pluginName"));

View File

@@ -66,6 +66,7 @@ public slots:
void on_uiHintsDisable_stateChanged(int state);
void on_disableComboBoxFilter_stateChanged(int state);
void on_warnPluginLoadFailure_stateChanged(int state);
void on_suppressCrashRecoveryDialog_stateChanged(int state);
void on_hideLegacyTabs_stateChanged(int state);
void on_priorityUp_clicked();
void on_priorityDown_clicked();

View File

@@ -1,4 +1,5 @@
#include "advanced-scene-switcher.hpp"
#include "crash-handler.hpp"
#include "file-selection.hpp"
#include "filter-combo-box.hpp"
#include "first-run-wizard.hpp"
@@ -194,6 +195,15 @@ void AdvSceneSwitcher::on_warnPluginLoadFailure_stateChanged(int state)
switcher->warnPluginLoadFailure = state;
}
void AdvSceneSwitcher::on_suppressCrashRecoveryDialog_stateChanged(int state)
{
if (loading) {
return;
}
SetSuppressCrashDialog(state);
}
static bool isLegacyTab(const QString &name)
{
return name == obs_module_text(
@@ -458,7 +468,6 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
// Needs to be loaded before any entries which might rely on scene group
// selections to be available.
loadSceneGroups(obj);
LoadVariables(obj);
RunLoadSteps(obj);
@@ -497,7 +506,6 @@ void SwitcherData::SaveSettings(obs_data_t *obj)
saveSceneGroups(obj);
SaveMacros(obj);
SaveGlobalMacroSettings(obj);
SaveVariables(obj);
saveWindowTitleSwitches(obj);
saveScreenRegionSwitches(obj);
savePauseSwitches(obj);
@@ -946,6 +954,7 @@ void AdvSceneSwitcher::SetupGeneralTab()
FilterComboBox::SetFilterBehaviourEnabled(
!switcher->disableFilterComboboxFilter);
ui->warnPluginLoadFailure->setChecked(switcher->warnPluginLoadFailure);
ui->suppressCrashRecoveryDialog->setChecked(GetSuppressCrashDialog());
ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs);
populatePriorityFunctionList(ui->priorityList);

View File

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

View File

@@ -230,8 +230,7 @@ bool SwitcherData::checkWindowTitleSwitch(OBSWeakSource &scene,
std::string currentWindowTitle = switcher->currentTitle;
bool match = false;
std::vector<std::string> windowList;
GetWindowList(windowList);
const auto windowList = GetWindowList();
for (WindowSwitch &s : windowSwitches) {
if (!s.initialized()) {

View File

@@ -32,6 +32,9 @@
#endif
#include <fstream>
#include <sstream>
#include <climits>
#include <dirent.h>
#include <unistd.h>
#include "kwin-helpers.h"
namespace advss {
@@ -233,9 +236,9 @@ std::string getWindowName(Window window)
return windowTitle;
}
void GetWindowList(std::vector<std::string> &windows)
std::vector<std::string> GetWindowList()
{
windows.resize(0);
std::vector<std::string> windows;
for (auto window : getTopLevelWindows()) {
auto name = getWindowName(window);
if (name.empty()) {
@@ -243,18 +246,7 @@ void GetWindowList(std::vector<std::string> &windows)
}
windows.emplace_back(name);
}
}
void GetWindowList(QStringList &windows)
{
windows.clear();
for (auto window : getTopLevelWindows()) {
auto name = getWindowName(window);
if (name.empty()) {
continue;
}
windows << QString::fromStdString(name);
}
return windows;
}
int getActiveWindow(Window *&window)
@@ -278,29 +270,24 @@ int getActiveWindow(Window *&window)
&bytes, (uint8_t **)&window);
}
void GetCurrentWindowTitle(std::string &title)
std::string GetCurrentWindowTitle()
{
if (KWin) {
title = FocusNotifier::getActiveWindowTitle();
return;
return FocusNotifier::getActiveWindowTitle();
}
Window *data = 0;
if (getActiveWindow(data) != Success || !data) {
return;
return {};
}
if (!data[0]) {
XFree(data);
return;
return {};
}
auto name = getWindowName(data[0]);
XFree(data);
if (name.empty()) {
return;
}
title = name;
return name;
}
bool windowStatesAreSet(const std::string &windowTitle,
@@ -409,16 +396,17 @@ static void getProcessListProcps2(QStringList &processes)
#endif
}
void GetProcessList(QStringList &processes)
QStringList GetProcessList()
{
processes.clear();
QStringList processes;
if (libprocpsSupported) {
getProcessListProcps(processes);
return;
return processes;
}
if (libprocps2Supported) {
getProcessListProcps2(processes);
}
return processes;
}
long getForegroundProcessPid()
@@ -469,24 +457,83 @@ std::string getProcNameFromPid(long pid)
return name;
}
void GetForegroundProcessName(QString &proc)
std::string GetForegroundProcessName()
{
std::string temp;
GetForegroundProcessName(temp);
proc = QString::fromStdString(temp);
auto pid = getForegroundProcessPid();
return getProcNameFromPid(pid);
}
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();
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)
{
std::string current;
GetForegroundProcessName(current);
const auto current = GetForegroundProcessName();
// True if executable switch equals current window
bool equals = (executable.toStdString() == current);

View File

@@ -49,10 +49,12 @@ MacroActionEdit::MacroActionEdit(QWidget *parent,
_section->AddHeaderWidget(_enable);
_section->AddHeaderWidget(_actionSelection);
_section->AddHeaderWidget(_headerInfo);
_section->AddHeaderWidget(_varMappingToggle);
auto actionLayout = new QVBoxLayout;
actionLayout->setContentsMargins({7, 7, 7, 7});
actionLayout->addWidget(_section);
actionLayout->addWidget(_outputMappings);
_contentLayout->addLayout(actionLayout);
auto mainLayout = new QHBoxLayout;
@@ -61,7 +63,6 @@ MacroActionEdit::MacroActionEdit(QWidget *parent,
mainLayout->addWidget(_frame);
setLayout(mainLayout);
_entryData = entryData;
SetupWidgets(true);
actionStateTimer->start(300);
@@ -90,7 +91,10 @@ void MacroActionEdit::SetupWidgets(bool basicSetup)
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &)));
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
SLOT(ShowVariableMappings(bool)));
_section->SetContent(widget, (*_entryData)->GetCollapsed());
SetupVarMappings((*_entryData).get());
SetFocusPolicyOfWidgets();
_allWidgetsAreSetup = true;
@@ -121,7 +125,10 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &)));
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
SLOT(ShowVariableMappings(bool)));
_section->SetContent(widget);
SetupVarMappings((*_entryData).get());
SetFocusPolicyOfWidgets();
}

View File

@@ -4,6 +4,8 @@
#include "macro.hpp"
#include "macro-action-factory.hpp"
#include <chrono>
namespace advss {
const std::string MacroActionMacro::id = "macro";
@@ -118,6 +120,45 @@ bool MacroActionMacro::PerformAction()
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;
}
case Action::GET_INFO: {
SetTempVarValue(
"conditionCount",
std::to_string(macro->Conditions().size()));
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;
}
default:
break;
}
@@ -175,6 +216,12 @@ void MacroActionMacro::LogAction() const
case Action::NESTED_MACRO:
ablog(LOG_INFO, "run nested macro");
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:
break;
}
@@ -202,8 +249,8 @@ bool MacroActionMacro::Save(obs_data_t *obj) const
bool MacroActionMacro::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_action = static_cast<MacroActionMacro::Action>(
obs_data_get_int(obj, "action"));
SetAction(static_cast<MacroActionMacro::Action>(
obs_data_get_int(obj, "action")));
_macro.Load(obj);
_actionSelectionType = static_cast<SelectionType>(
obs_data_get_int(obj, "actionSelectionType"));
@@ -303,6 +350,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 const std::vector<std::pair<MacroActionMacro::Action, std::string>>
@@ -318,7 +415,9 @@ static void populateActionSelection(QComboBox *list)
{MacroActionMacro::Action::NESTED_MACRO,
"AdvSceneSwitcher.action.macro.type.nestedMacro"},
{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,
"AdvSceneSwitcher.action.macro.type.stop"},
{MacroActionMacro::Action::DISABLE_ACTION,
@@ -327,6 +426,8 @@ static void populateActionSelection(QComboBox *list)
"AdvSceneSwitcher.action.macro.type.enableAction"},
{MacroActionMacro::Action::TOGGLE_ACTION,
"AdvSceneSwitcher.action.macro.type.toggleAction"},
{MacroActionMacro::Action::GET_INFO,
"AdvSceneSwitcher.action.macro.type.getInfo"},
};
for (const auto &[value, name] : actions) {
@@ -396,6 +497,10 @@ MacroActionMacroEdit::MacroActionMacroEdit(
_actionSections(new QComboBox(this)),
_skipWhenPaused(new QCheckBox(obs_module_text(
"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(
"AdvSceneSwitcher.action.macro.type.run.setInputs"))),
_inputs(new MacroInputEdit()),
@@ -469,6 +574,7 @@ MacroActionMacroEdit::MacroActionMacroEdit(
layout->addLayout(_setInputsLayout);
layout->addWidget(_inputs);
layout->addWidget(_skipWhenPaused);
layout->addWidget(_noConditionsWarning);
layout->addWidget(_nestedMacro);
setLayout(layout);
_entryData = entryData;
@@ -496,7 +602,7 @@ void MacroActionMacroEdit::UpdateEntryData()
}
_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);
@@ -545,8 +651,8 @@ void MacroActionMacroEdit::MacroChanged(const QString &text)
void MacroActionMacroEdit::ActionChanged(int idx)
{
GUARD_LOADING_AND_LOCK();
_entryData->_action = static_cast<MacroActionMacro::Action>(
_actions->itemData(idx).toInt());
_entryData->SetAction(static_cast<MacroActionMacro::Action>(
_actions->itemData(idx).toInt()));
SetWidgetVisibility();
}
@@ -638,6 +744,7 @@ void MacroActionMacroEdit::InputsChanged(const StringList &inputs)
void MacroActionMacroEdit::SetWidgetVisibility()
{
_entryLayout->removeWidget(_actions);
_entryLayout->removeWidget(_runMacroHelp);
_entryLayout->removeWidget(_actionIndex);
_entryLayout->removeWidget(_macros);
_entryLayout->removeWidget(_actionSections);
@@ -653,6 +760,7 @@ void MacroActionMacroEdit::SetWidgetVisibility()
const std::unordered_map<std::string, QWidget *> placeholders = {
{"{{actions}}", _actions},
{"{{runMacroHelp}}", _runMacroHelp},
{"{{actionIndex}}", _actionIndex},
{"{{macros}}", _macros},
{"{{actionSections}}", _actionSections},
@@ -665,7 +773,7 @@ void MacroActionMacroEdit::SetWidgetVisibility()
};
const auto action = _entryData->_action;
const auto action = _entryData->GetAction();
const char *layoutText = "";
switch (action) {
case MacroActionMacro::Action::PAUSE:
@@ -674,8 +782,12 @@ void MacroActionMacroEdit::SetWidgetVisibility()
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;
@@ -702,7 +814,8 @@ void MacroActionMacroEdit::SetWidgetVisibility()
}
if (action == MacroActionMacro::Action::RUN_ACTIONS ||
action == MacroActionMacro::Action::STOP) {
action == MacroActionMacro::Action::STOP ||
action == MacroActionMacro::Action::RUN_MACRO) {
_macros->HideSelectedMacro();
} else {
_macros->ShowAllMacros();
@@ -747,8 +860,19 @@ void MacroActionMacroEdit::SetWidgetVisibility()
_actionSections->setVisible(
action == MacroActionMacro::Action::RUN_ACTIONS ||
isModifyingActionState);
_skipWhenPaused->setVisible(action ==
MacroActionMacro::Action::RUN_ACTIONS);
_skipWhenPaused->setVisible(
action == MacroActionMacro::Action::RUN_ACTIONS ||
action == MacroActionMacro::Action::RUN_MACRO);
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);

View File

@@ -11,6 +11,7 @@
#include <QCheckBox>
#include <QHBoxLayout>
#include <QLabel>
namespace advss {
@@ -56,11 +57,15 @@ public:
TOGGLE_ACTION,
TOGGLE_PAUSE,
NESTED_MACRO,
GET_INFO,
RUN_MACRO,
};
void SetAction(Action);
Action GetAction() const { return _action; }
enum class SelectionType { INDEX, LABEL, ID };
Action _action = Action::NESTED_MACRO;
SelectionType _actionSelectionType = SelectionType::INDEX;
bool _useElseSection = false;
IntVariable _actionIndex = 1;
@@ -72,9 +77,12 @@ public:
int _customWidgetHeight = 0;
private:
void SetupTempVars();
void RunActions(Macro *actionMacro) const;
void AdjustActionState(Macro *) const;
Action _action = Action::NESTED_MACRO;
static bool _registered;
static const std::string id;
};
@@ -125,6 +133,8 @@ private:
QCheckBox *_reevaluateConditionState;
QComboBox *_actionSections;
QCheckBox *_skipWhenPaused;
QLabel *_noConditionsWarning;
HelpIcon *_runMacroHelp;
QCheckBox *_setInputs;
MacroInputEdit *_inputs;
QHBoxLayout *_entryLayout;

View File

@@ -17,6 +17,15 @@ namespace advss {
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(
MacroActionVariable::id,
{MacroActionVariable::Create, MacroActionVariableEdit::Create,
@@ -1450,6 +1459,7 @@ void MacroActionVariableEdit::SelectionChanged(const TempVariableRef &var)
{
GUARD_LOADING_AND_LOCK();
_entryData->_tempVar = var;
IncrementTempVarInUseGeneration();
SetWidgetVisibility();
}

View File

@@ -23,6 +23,7 @@ public:
bool PostLoad();
std::string GetShortDesc() const;
std::string GetId() const { return id; };
std::vector<TempVariableRef> GetTempVarRefs() const;
static std::shared_ptr<MacroAction> Create(Macro *m);
std::shared_ptr<MacroAction> Copy() const;
void SetSegmentIndexValue(int);

View File

@@ -5,6 +5,7 @@
#include "path-helpers.hpp"
#include "plugin-state-helpers.hpp"
#include "section.hpp"
#include "switch-button.hpp"
#include "ui-helpers.hpp"
#include "utility.hpp"
@@ -102,12 +103,15 @@ MacroConditionEdit::MacroConditionEdit(
QWidget *parent, std::shared_ptr<MacroCondition> *entryData,
bool isRootCondition)
: MacroSegmentEdit(parent),
_enable(new SwitchButton()),
_logicSelection(new QComboBox()),
_conditionSelection(new FilterComboBox()),
_dur(new DurationModifierEdit()),
_entryData(entryData),
_isRoot(isRootCondition)
{
QWidget::connect(_enable, SIGNAL(checked(bool)), this,
SLOT(ConditionEnableChanged(bool)));
QWidget::connect(_logicSelection, SIGNAL(currentIndexChanged(int)),
this, SLOT(LogicSelectionChanged(int)));
QWidget::connect(_conditionSelection,
@@ -122,14 +126,17 @@ MacroConditionEdit::MacroConditionEdit(
Logic::PopulateLogicTypeSelection(_logicSelection, isRootCondition);
populateConditionSelection(_conditionSelection);
_section->AddHeaderWidget(_enable);
_section->AddHeaderWidget(_logicSelection);
_section->AddHeaderWidget(_conditionSelection);
_section->AddHeaderWidget(_headerInfo);
_section->AddHeaderWidget(_dur);
_section->AddHeaderWidget(_varMappingToggle);
QVBoxLayout *conditionLayout = new QVBoxLayout;
conditionLayout->setContentsMargins({7, 7, 7, 7});
conditionLayout->addWidget(_section);
conditionLayout->addWidget(_outputMappings);
_contentLayout->addLayout(conditionLayout);
QHBoxLayout *mainLayout = new QHBoxLayout;
@@ -152,8 +159,17 @@ void MacroConditionEdit::LogicSelectionChanged(int idx)
const auto logic = static_cast<Logic::Type>(
_logicSelection->itemData(idx).toInt());
(*_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
@@ -166,7 +182,9 @@ void MacroConditionEdit::SetLogicSelection()
const auto logic = (*_entryData)->GetLogicType();
_logicSelection->setCurrentIndex(
_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)
@@ -204,7 +222,10 @@ void MacroConditionEdit::SetupWidgets(bool basicSetup)
MacroConditionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &)));
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
SLOT(ShowVariableMappings(bool)));
_section->SetContent(widget, (*_entryData)->GetCollapsed());
SetupVarMappings((*_entryData).get());
SetFocusPolicyOfWidgets();
_allWidgetsAreSetup = true;
@@ -234,10 +255,12 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
{
auto lock = LockContext();
auto logic = (*_entryData)->GetLogicType();
const bool enabled = (*_entryData)->Enabled();
_entryData->reset();
*_entryData = MacroConditionFactory::Create(id, macro);
(*_entryData)->SetIndex(idx);
(*_entryData)->SetLogicType(logic);
(*_entryData)->SetEnabled(enabled);
(*_entryData)->PostLoad();
RunAndClearPostLoadSteps();
}
@@ -245,7 +268,10 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
MacroConditionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &)));
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
SLOT(ShowVariableMappings(bool)));
_section->SetContent(widget);
SetupVarMappings((*_entryData).get());
_dur->setVisible(MacroConditionFactory::UsesDurationModifier(id));
SetFocusPolicyOfWidgets();
}

View File

@@ -8,6 +8,8 @@
namespace advss {
class SwitchButton;
class DurationModifierEdit : public QWidget {
Q_OBJECT
public:
@@ -47,11 +49,13 @@ private slots:
void ConditionSelectionChanged(const QString &text);
void DurationChanged(const Duration &value);
void DurationModifierChanged(DurationModifier::Type m);
void ConditionEnableChanged(bool);
private:
void SetLogicSelection();
std::shared_ptr<MacroSegment> Data() const;
SwitchButton *_enable;
QComboBox *_logicSelection;
FilterComboBox *_conditionSelection;
DurationModifierEdit *_dur;

View File

@@ -6,6 +6,15 @@ namespace advss {
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(
MacroConditionTempVar::id,
{MacroConditionTempVar::Create, MacroConditionTempVarEdit::Create,
@@ -266,6 +275,7 @@ void MacroConditionTempVarEdit::VariableChanged(const TempVariableRef &var)
{
GUARD_LOADING_AND_LOCK();
_entryData->_tempVar = var;
IncrementTempVarInUseGeneration();
}
void MacroConditionTempVarEdit::Variable2Changed(const QString &text)

View File

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

View File

@@ -31,6 +31,10 @@ bool MacroCondition::Load(obs_data_t *obj)
{
MacroSegment::Load(obj);
_logic.Load(obj, "logic");
if (_logic.GetType() == Logic::Type::NONE) {
SetEnabled(false);
_logic.SetType(Logic::Type::AND);
}
_durationModifier.Load(obj);
return true;
}
@@ -50,7 +54,8 @@ void MacroCondition::ValidateLogicSelection(bool isRootCondition,
return;
}
_logic.SetType(Logic::Type::NONE);
_logic.SetType(Logic::Type::AND);
SetEnabled(false);
blog(LOG_WARNING,
"setting invalid logic selection to 'ignore' for macro %s",
context);

View File

@@ -366,7 +366,7 @@ static void runSegmentHighlightChecksHelper(MacroSegmentList *list)
// highlight its segments if required
auto macroAction = dynamic_cast<MacroActionMacro *>(data.get());
if (macroAction &&
macroAction->_action ==
macroAction->GetAction() ==
MacroActionMacro::Action::NESTED_MACRO) {
continue;
}
@@ -1086,6 +1086,11 @@ void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id,
HighlightAction(idx);
ui->actionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged());
QTimer::singleShot(0, this, [this]() {
ui->actionsList->ensureWidgetVisible(
ui->actionsList->WidgetAt(currentActionIdx));
});
}
void MacroEdit::AddMacroAction(int idx)
@@ -1389,6 +1394,11 @@ void MacroEdit::AddMacroElseAction(Macro *macro, int idx, const std::string &id,
HighlightElseAction(idx);
ui->elseActionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged());
QTimer::singleShot(0, this, [this]() {
ui->elseActionsList->ensureWidgetVisible(
ui->elseActionsList->WidgetAt(currentElseActionIdx));
});
}
void MacroEdit::AddMacroElseAction(int idx)
@@ -1591,6 +1601,11 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
HighlightCondition(idx);
ui->conditionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged());
QTimer::singleShot(0, this, [this]() {
ui->conditionsList->ensureWidgetVisible(
ui->conditionsList->WidgetAt(currentConditionIdx));
});
}
void MacroEdit::on_conditionAdd_clicked()

View File

@@ -1,8 +1,10 @@
#include "macro-segment.hpp"
#include "macro.hpp"
#include "mouse-wheel-guard.hpp"
#include "path-helpers.hpp"
#include "section.hpp"
#include "ui-helpers.hpp"
#include "variable.hpp"
#include <QApplication>
#include <QEvent>
@@ -13,20 +15,84 @@
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;
}
std::vector<TempVariable> MacroSegment::GetOwnTempVars() const
{
return _tempVariables;
}
MacroSegment::MacroSegment(Macro *m, bool supportsVariableValue)
: _macro(m),
_supportsVariableValue(supportsVariableValue)
{
}
void MacroSegment::SetVarMappingExpanded(bool expanded)
{
_varMappingExpanded = expanded;
}
bool MacroSegment::Save(obs_data_t *obj) const
{
OBSDataAutoRelease data = obs_data_create();
obs_data_set_bool(data, "collapsed", _collapsed);
obs_data_set_bool(data, "varMappingExpanded", _varMappingExpanded);
obs_data_set_bool(data, "useCustomLabel", _useCustomLabel);
obs_data_set_string(data, "customLabel", _customLabel.c_str());
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);
return true;
}
@@ -35,6 +101,7 @@ bool MacroSegment::Load(obs_data_t *obj)
{
OBSDataAutoRelease data = obs_data_get_obj(obj, "segmentSettings");
_collapsed = obs_data_get_bool(data, "collapsed");
_varMappingExpanded = obs_data_get_bool(data, "varMappingExpanded");
_useCustomLabel = obs_data_get_bool(data, "useCustomLabel");
_customLabel = obs_data_get_string(data, "customLabel");
obs_data_set_default_bool(data, "enabled", true);
@@ -45,6 +112,26 @@ bool MacroSegment::Load(obs_data_t *obj)
_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();
return true;
}
@@ -152,6 +239,16 @@ void MacroSegment::AddTempvar(const std::string &id, const std::string &name,
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,
const std::string &value)
{
@@ -211,11 +308,38 @@ MacroSegmentEdit::MacroSegmentEdit(QWidget *parent)
_headerInfo(new QLabel()),
_frame(new QWidget),
_contentLayout(new QVBoxLayout),
_outputMappings(new TempVarOutputMappingsWidget(this)),
_varMappingToggle(new QPushButton(this)),
_noBorderframe(new QFrame),
_borderFrame(new QFrame),
_dropLineAbove(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->setFixedHeight(11);
_dropLineBelow->setLineWidth(3);
@@ -277,6 +401,21 @@ MacroSegmentEdit::MacroSegmentEdit(QWidget *parent)
_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)
{
if (obj == _headerInfo && ev->type() == QEvent::MouseMove) {

View File

@@ -9,15 +9,23 @@
#include <QWidget>
#include <QFrame>
#include <QPushButton>
#include <QVBoxLayout>
#include <QTimer>
#include <obs-data.h>
#include <memory>
class QLabel;
namespace advss {
class Macro;
class Variable;
struct VarMapping {
TempVariableRef tempVar;
std::weak_ptr<Variable> variable;
};
class EXPORT MacroSegment : public Lockable {
public:
@@ -28,12 +36,15 @@ public:
int GetIndex() const { return _idx; }
void SetCollapsed(bool collapsed) { _collapsed = collapsed; }
bool GetCollapsed() const { return _collapsed; }
void SetVarMappingExpanded(bool expanded);
bool GetVarMappingExpanded() const { return _varMappingExpanded; }
void SetUseCustomLabel(bool enable) { _useCustomLabel = enable; }
bool GetUseCustomLabel() const { return _useCustomLabel; }
void SetCustomLabel(const std::string &label) { _customLabel = label; }
std::string GetCustomLabel() const { return _customLabel; }
virtual bool Save(obs_data_t *obj) const = 0;
virtual bool Load(obs_data_t *obj) = 0;
virtual std::vector<TempVariableRef> GetTempVarRefs() const;
virtual bool PostLoad();
virtual std::string GetShortDesc() const;
virtual std::string GetId() const = 0;
@@ -42,6 +53,10 @@ public:
void SetEnabled(bool);
bool Enabled() 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;
protected:
friend bool SupportsVariableValue(MacroSegment *);
@@ -54,6 +69,7 @@ protected:
void AddTempvar(const std::string &id, const std::string &name,
const std::string &description = "");
bool IsTempVarInUse(const std::string &id) const;
void SetTempVarValue(const std::string &id, const std::string &value);
template<typename T, typename = std::enable_if_t<
@@ -64,6 +80,14 @@ protected:
: 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:
void ClearAvailableTempvars();
std::optional<const TempVariable>
@@ -77,6 +101,7 @@ private:
// UI helper
bool _highlight = false;
bool _collapsed = false;
bool _varMappingExpanded = false;
bool _enabled = true;
// Custom header labels
@@ -89,6 +114,7 @@ private:
int _variableRefs = 0;
std::string _variableValue;
std::vector<TempVariable> _tempVariables;
std::vector<VarMapping> _varMappings;
friend class Macro;
};
@@ -108,8 +134,11 @@ public:
virtual std::shared_ptr<MacroSegment> Data() const = 0;
virtual void SetupWidgets(bool basicSetup = false) = 0;
void SetupVarMappings(MacroSegment *segment);
public slots:
void HeaderInfoChanged(const QString &);
void ShowVariableMappings(bool show);
protected slots:
void Collapsed(bool) const;
@@ -124,6 +153,8 @@ protected:
QLabel *_headerInfo;
QWidget *_frame;
QVBoxLayout *_contentLayout;
TempVarOutputMappingsWidget *_outputMappings;
QPushButton *_varMappingToggle;
bool _allWidgetsAreSetup = false;
private:

View File

@@ -557,6 +557,10 @@ void AdvSceneSwitcher::HighlightOnChange() const
return;
}
if (macro->Paused()) {
return;
}
if (macro->ActionTriggerModePreventedActionsSince(
lastOnChangeHighlightCheckTime)) {
HighlightWidget(ui->actionTriggerMode, Qt::yellow,
@@ -670,18 +674,6 @@ void AdvSceneSwitcher::SetupMacroTab()
SLOT(HighlightOnChange()));
onChangeHighlightTimer.start();
// Reserve more space for macro edit area than for the macro list
ui->macroListMacroEditSplitter->setStretchFactor(0, 1);
ui->macroListMacroEditSplitter->setStretchFactor(1, 4);
if (switcher->saveWindowGeo) {
if (shouldRestoreSplitter(
switcher->macroListMacroEditSplitterPosition)) {
ui->macroListMacroEditSplitter->setSizes(
switcher->macroListMacroEditSplitterPosition);
}
}
SetupMacroSearchWidgets(ui->macroSearchLayout, ui->macroSearchText,
ui->macroSearchClear, ui->macroSearchType,
ui->macroSearchRegex,
@@ -705,6 +697,26 @@ void AdvSceneSwitcher::SetupMacroTab()
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(
{totalWidth / 5, totalWidth * 4 / 5});
});
}
}
void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)

View File

@@ -17,6 +17,17 @@
namespace advss {
static bool setup()
{
AddPluginCleanupStep([]() {
GetTopLevelMacros().clear();
GetTemporaryMacros().clear();
});
return true;
}
static bool setupDone = setup();
Macro::Macro(const std::string &name) : _dockSettings(this)
{
SetName(name);
@@ -113,6 +124,7 @@ static bool checkCondition(const std::shared_ptr<MacroCondition> &condition)
condition->WithLock([&condition, &conditionMatched]() {
conditionMatched = condition->EvaluateCondition();
});
condition->ApplyVarMappings();
const auto endTime = std::chrono::high_resolution_clock::now();
const auto timeSpent = endTime - startTime;
@@ -145,6 +157,15 @@ bool Macro::CheckConditionHelper(
return conditionMatched;
};
if (!condition->Enabled()) {
vblog(LOG_INFO, "ignoring condition '%s' for '%s'",
condition->GetId().c_str(), _name.c_str());
if (!_useShortCircuitEvaluation) {
(void)evaluateCondition();
}
return _matched;
}
const auto logicType = condition->GetLogicType();
if (logicType == Logic::Type::NONE) {
vblog(LOG_INFO, "ignoring condition '%s' for '%s'",
@@ -269,7 +290,7 @@ bool Macro::CheckConditions(bool ignorePause)
const bool hasActionsToExecute = _matched ? (_actions.size() > 0)
: (_elseActions.size() > 0);
if (!_actionModeMatch && hasActionsToExecute) {
if (!_actionModeMatch && hasActionsToExecute && !_paused) {
_lastActionRunModePreventTime =
std::chrono::high_resolution_clock::now();
}
@@ -368,7 +389,7 @@ bool Macro::ShouldRunActions() const
!_paused && (_matched || _elseActions.size() > 0) &&
_actionModeMatch;
if (VerboseLoggingEnabled() && !_actionModeMatch) {
if (VerboseLoggingEnabled() && !_actionModeMatch && !_paused) {
if (_matched && _actions.size() > 0) {
blog(LOG_INFO, "skip actions for Macro %s (on change)",
_name.c_str());
@@ -432,6 +453,7 @@ bool Macro::RunActionsHelper(
action->WithLock([&action, &actionResult]() {
actionResult = action->PerformAction();
});
action->ApplyVarMappings();
actionsExecutedSuccessfully =
actionsExecutedSuccessfully && actionResult;
} else {

View File

@@ -14,10 +14,9 @@
namespace advss {
void GetWindowList(std::vector<std::string> &windows)
std::vector<std::string> GetWindowList()
{
windows.resize(0);
std::vector<std::string> windows;
@autoreleasepool {
CFArrayRef cfApps = CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
@@ -49,21 +48,12 @@ void GetWindowList(std::vector<std::string> &windows)
apps = nil;
CFRelease(cfApps);
}
return windows;
}
void GetWindowList(QStringList &windows)
std::string GetCurrentWindowTitle()
{
windows.clear();
std::vector<std::string> temp;
GetWindowList(temp);
for (auto &w : temp) {
windows << QString::fromStdString(w);
}
}
void GetCurrentWindowTitle(std::string &title)
{
title.resize(0);
std::string title;
@autoreleasepool {
CFArrayRef cfApps = CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
@@ -107,6 +97,7 @@ void GetCurrentWindowTitle(std::string &title)
apps = nil;
CFRelease(cfApps);
}
return title;
}
bool isWindowOriginOnScreen(NSDictionary *app, NSScreen *screen,
@@ -273,9 +264,9 @@ int SecondsSinceLastInput()
return (int)time;
}
void GetProcessList(QStringList &list)
QStringList GetProcessList()
{
list.clear();
QStringList list;
@autoreleasepool {
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
NSArray *array = [ws runningApplications];
@@ -291,11 +282,11 @@ void GetProcessList(QStringList &list)
}
}
}
return list;
}
void GetForegroundProcessName(std::string &proc)
std::string GetForegroundProcessName()
{
proc.resize(0);
@autoreleasepool {
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
NSArray *array = [ws runningApplications];
@@ -308,23 +299,73 @@ void GetForegroundProcessName(std::string &proc)
break;
}
const char *str = name.UTF8String;
proc = std::string(str);
if (str) {
return str;
}
break;
}
}
return {};
}
void GetForegroundProcessName(QString &proc)
std::string GetForegroundProcessPath()
{
std::string temp;
GetForegroundProcessName(temp);
proc = QString::fromStdString(temp);
@autoreleasepool {
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
for (NSRunningApplication *app in [ws runningApplications]) {
if (!app.isActive) {
continue;
}
NSURL *url = app.executableURL;
if (!url) {
break;
}
const char *str = url.path.UTF8String;
if (str) {
return str;
}
break;
}
}
return {};
}
QStringList GetProcessPathsFromName(const QString &name)
{
QStringList paths;
@autoreleasepool {
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
for (NSRunningApplication *app in [ws runningApplications]) {
NSString *appName = app.localizedName;
if (!appName) {
continue;
}
const char *nameStr = appName.UTF8String;
if (!nameStr ||
name != QString::fromUtf8(nameStr)) {
continue;
}
NSURL *url = app.executableURL;
if (!url) {
continue;
}
const char *pathStr = url.path.UTF8String;
if (!pathStr) {
continue;
}
QString path = QString::fromUtf8(pathStr);
if (!paths.contains(path)) {
paths.append(path);
}
}
}
return paths;
}
bool IsInFocus(const QString &executable)
{
std::string current;
GetForegroundProcessName(current);
const auto current = GetForegroundProcessName();
// True if executable switch equals current window
bool equals = (executable.toStdString() == current);

View File

@@ -11,15 +11,16 @@ namespace advss {
enum class HotkeyType;
EXPORT void GetWindowList(std::vector<std::string> &windows);
EXPORT void GetWindowList(QStringList &windows);
EXPORT void GetCurrentWindowTitle(std::string &title);
EXPORT std::vector<std::string> GetWindowList();
EXPORT std::string GetCurrentWindowTitle();
EXPORT bool IsFullscreen(const std::string &title);
EXPORT bool IsMaximized(const std::string &title);
EXPORT std::optional<std::string> GetTextInWindow(const std::string &window);
EXPORT int SecondsSinceLastInput();
EXPORT void GetProcessList(QStringList &processes);
EXPORT void GetForegroundProcessName(std::string &name);
EXPORT QStringList GetProcessList();
EXPORT std::string GetForegroundProcessName();
EXPORT std::string GetForegroundProcessPath();
EXPORT QStringList GetProcessPathsFromName(const QString &name);
EXPORT bool IsInFocus(const QString &executable);
void PlatformInit();
void PlatformCleanup();

View File

@@ -16,14 +16,17 @@ static void setupTab(QTabWidget *);
static ActionQueueTable *tabWidget = nullptr;
void RegisterActionQueueTab()
static bool setup()
{
AddPluginInitStep([]() {
AddPluginInitStep([] {
AddSetupTabCallback("actionQueueTab", ActionQueueTable::Create,
setupTab);
});
return true;
}
static bool setupDone = setup();
static void setTabVisible(QTabWidget *tabWidget, bool visible)
{
SetTabVisibleByName(

View File

@@ -12,20 +12,16 @@ std::deque<std::shared_ptr<Item>> &GetActionQueues()
return queues;
}
void RegisterActionQueueTab();
void SetupActionQueues()
static bool setup()
{
static bool done = false;
if (done) {
return;
}
AddSaveStep(SaveActionQueues);
AddLoadStep(LoadActionQueues);
RegisterActionQueueTab();
done = true;
AddPluginCleanupStep([]() { queues.clear(); });
return true;
}
static bool setupDone = setup();
ActionQueue::ActionQueue() : Item()
{
_lastEmpty = std::chrono::high_resolution_clock::now();

View File

@@ -101,7 +101,6 @@ signals:
};
std::deque<std::shared_ptr<Item>> &GetActionQueues();
void SetupActionQueues();
void SaveActionQueues(obs_data_t *);
void LoadActionQueues(obs_data_t *);
void ImportQueues(obs_data_t *);

View File

@@ -8,7 +8,6 @@
namespace advss {
const std::map<Logic::Type, const char *> Logic::localeMap = {
{Logic::Type::NONE, {"AdvSceneSwitcher.logic.none"}},
{Logic::Type::AND, {"AdvSceneSwitcher.logic.and"}},
{Logic::Type::OR, {"AdvSceneSwitcher.logic.or"}},
{Logic::Type::AND_NOT, {"AdvSceneSwitcher.logic.andNot"}},
@@ -67,7 +66,7 @@ void Logic::PopulateLogicTypeSelection(QComboBox *list, bool isRootCondition)
return typeValue < rootOffset;
}}
: std::function<bool(int)>{[](int typeValue) {
return typeValue >= rootOffset;
return typeValue > rootOffset;
}};
for (const auto &[type, name] : localeMap) {
const int typeValue = static_cast<int>(type);

View File

@@ -2,17 +2,18 @@
#include "log-helper.hpp"
#include "obs-module-helper.hpp"
#include "plugin-state-helpers.hpp"
#include "ui-helpers.hpp"
#include <obs-frontend-api.h>
#include <obs-module.h>
#include <QCheckBox>
#include <QDialog>
#include <QDialogButtonBox>
#include <QDir>
#include <QFile>
#include <QLabel>
#include <QMainWindow>
#include <QTimer>
#include <thread>
#include <QVBoxLayout>
namespace advss {
@@ -25,10 +26,31 @@ static constexpr bool handleUncleanShutdown = true;
#endif
static bool wasCleanShutdown = false;
static bool suppressCrashDialog = false;
static char *sentinelFile = nullptr;
bool GetSuppressCrashDialog()
{
return suppressCrashDialog;
}
void SetSuppressCrashDialog(bool suppress)
{
suppressCrashDialog = suppress;
}
static void setup();
static bool setupDone = []() {
AddPluginInitStep(setup);
AddSaveStep([](obs_data_t *obj) {
obs_data_set_bool(obj, "suppressCrashDialog",
suppressCrashDialog);
});
AddLoadStep([](obs_data_t *obj) {
suppressCrashDialog =
obs_data_get_bool(obj, "suppressCrashDialog");
});
return true;
}();
@@ -38,7 +60,6 @@ static void handleShutdown(enum obs_frontend_event event, void *)
return;
}
char *sentinelFile = obs_module_config_path(sentinel.data());
if (!sentinelFile) {
return;
}
@@ -58,7 +79,8 @@ static void handleShutdown(enum obs_frontend_event event, void *)
static void setup()
{
char *sentinelFile = obs_module_config_path(sentinel.data());
// Freed in handleShutdown()
sentinelFile = obs_module_config_path(sentinel.data());
if (!sentinelFile) {
return;
}
@@ -86,7 +108,6 @@ static void setup()
file.write("running");
file.close();
bfree(sentinelFile);
obs_frontend_add_event_callback(handleShutdown, nullptr);
return;
@@ -108,8 +129,39 @@ static bool wasUncleanShutdown()
static void askForStartupSkip()
{
bool skipStart = DisplayMessage(
obs_module_text("AdvSceneSwitcher.crashDetected"), true, false);
auto mainWindow =
static_cast<QMainWindow *>(obs_frontend_get_main_window());
auto dialog = new QDialog(mainWindow);
dialog->setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
dialog->setWindowFlags(dialog->windowFlags() &
~Qt::WindowContextHelpButtonHint);
auto layout = new QVBoxLayout(dialog);
auto label = new QLabel(
obs_module_text("AdvSceneSwitcher.crashDetected"), dialog);
label->setWordWrap(true);
layout->addWidget(label);
auto checkbox = new QCheckBox(
obs_module_text(
"AdvSceneSwitcher.crashDetected.suppressCheckbox"),
dialog);
layout->addWidget(checkbox);
auto buttonbox = new QDialogButtonBox(
QDialogButtonBox::Yes | QDialogButtonBox::No, dialog);
QObject::connect(buttonbox, &QDialogButtonBox::accepted, dialog,
&QDialog::accept);
QObject::connect(buttonbox, &QDialogButtonBox::rejected, dialog,
&QDialog::reject);
layout->addWidget(buttonbox);
dialog->setLayout(layout);
bool skipStart = dialog->exec() == QDialog::Accepted;
suppressCrashDialog = checkbox->isChecked();
dialog->deleteLater();
if (!skipStart) {
StartPlugin();
}
@@ -121,6 +173,10 @@ bool ShouldSkipPluginStartOnUncleanShutdown()
return false;
}
if (suppressCrashDialog) {
return false;
}
// This function is called while the plugin settings are being loaded.
// Blocking at this stage can cause issues such as OBS failing to start
// or crashing.

View File

@@ -4,4 +4,7 @@ namespace advss {
bool ShouldSkipPluginStartOnUncleanShutdown();
bool GetSuppressCrashDialog();
void SetSuppressCrashDialog(bool suppress);
} // namespace advss

View File

@@ -7,9 +7,11 @@
namespace advss {
FileSelection::FileSelection(FileSelection::Type type, QWidget *parent)
FileSelection::FileSelection(FileSelection::Type type, QWidget *parent,
const QString &browseTitle)
: QWidget(parent),
_type(type),
_browseTitle(browseTitle),
_filePath(new VariableLineEdit(this)),
_browseButton(
new QPushButton(obs_module_text("AdvSceneSwitcher.browse")))
@@ -55,11 +57,14 @@ void FileSelection::BrowseButtonClicked()
QString defaultPath = ValidPathOrDesktop(_filePath->text());
QString path;
if (_type == FileSelection::Type::WRITE) {
path = QFileDialog::getSaveFileName(this, "", defaultPath);
path = QFileDialog::getSaveFileName(this, _browseTitle,
defaultPath);
} else if (_type == FileSelection::Type::READ) {
path = QFileDialog::getOpenFileName(this, "", defaultPath);
path = QFileDialog::getOpenFileName(this, _browseTitle,
defaultPath);
} else {
path = QFileDialog::getExistingDirectory(this, "", defaultPath);
path = QFileDialog::getExistingDirectory(this, _browseTitle,
defaultPath);
}
if (path.isEmpty()) {

View File

@@ -19,7 +19,7 @@ public:
EXPORT
FileSelection(FileSelection::Type type = FileSelection::Type::READ,
QWidget *parent = 0);
QWidget *parent = 0, const QString &browseTitle = "");
EXPORT void SetPath(const StringVariable &);
EXPORT void SetPath(const QString &);
EXPORT QString GetPath() const;
@@ -34,6 +34,7 @@ signals:
private:
Type _type;
QString _browseTitle;
VariableLineEdit *_filePath;
QPushButton *_browseButton;
};

View File

@@ -55,9 +55,7 @@ static void WriteFirstRun(bool value)
static QString DetectFocusedWindow()
{
std::string title;
GetCurrentWindowTitle(title);
return QString::fromStdString(title);
return QString::fromStdString(GetCurrentWindowTitle());
}
// ===========================================================================

View File

@@ -32,6 +32,17 @@ ListControls::ListControls(QWidget *parent, bool reorder) : QToolBar(parent)
}
}
void ListControls::AddWidget(QWidget *widget)
{
addSeparator();
addWidget(widget);
}
void ListControls::AddSeparator()
{
addSeparator();
}
void ListControls::AddActionHelper(const char *theme, const char *className,
const char *tooltip,
const std::function<void()> &signal)

View File

@@ -10,6 +10,8 @@ class ADVSS_EXPORT ListControls final : public QToolBar {
public:
ListControls(QWidget *parent = nullptr, bool reorder = true);
void AddWidget(QWidget *widget);
void AddSeparator();
signals:
void Add();

View File

@@ -1,13 +1,16 @@
#include "list-editor.hpp"
#include "ui-helpers.hpp"
#include <QEvent>
namespace advss {
ListEditor::ListEditor(QWidget *parent, bool reorder)
: QWidget(parent),
_list(new QListWidget()),
_controls(new ListControls(this, reorder)),
_mainLayout(new QVBoxLayout())
_mainLayout(new QVBoxLayout()),
_placeholder(new QLabel(_list->viewport()))
{
QWidget::connect(_controls, SIGNAL(Add()), this, SLOT(Add()));
QWidget::connect(_controls, SIGNAL(Remove()), this, SLOT(Remove()));
@@ -15,6 +18,19 @@ ListEditor::ListEditor(QWidget *parent, bool reorder)
QWidget::connect(_controls, SIGNAL(Down()), this, SLOT(Down()));
QWidget::connect(_list, SIGNAL(itemDoubleClicked(QListWidgetItem *)),
this, SLOT(Clicked(QListWidgetItem *)));
QWidget::connect(_list->model(),
SIGNAL(rowsInserted(QModelIndex, int, int)), this,
SLOT(UpdatePlaceholder()));
QWidget::connect(_list->model(),
SIGNAL(rowsRemoved(QModelIndex, int, int)), this,
SLOT(UpdatePlaceholder()));
QWidget::connect(_list->model(), SIGNAL(modelReset()), this,
SLOT(UpdatePlaceholder()));
_placeholder->setAlignment(Qt::AlignCenter);
_placeholder->setWordWrap(true);
_placeholder->hide();
_list->viewport()->installEventFilter(this);
_mainLayout->setContentsMargins(0, 0, 0, 0);
_mainLayout->addWidget(_list);
@@ -22,6 +38,40 @@ ListEditor::ListEditor(QWidget *parent, bool reorder)
setLayout(_mainLayout);
}
void ListEditor::SetPlaceholderText(const QString &text)
{
_placeholder->setText(text);
UpdatePlaceholder();
}
void ListEditor::SetMinListHeight(int value)
{
_minHeight = value;
}
void ListEditor::SetMaxListHeight(int value)
{
_maxHeight = value;
}
void ListEditor::UpdatePlaceholder()
{
bool visible = !_placeholder->text().isEmpty() && _list->count() == 0;
_placeholder->setVisible(visible);
if (visible) {
_placeholder->setGeometry(_list->viewport()->rect());
}
}
bool ListEditor::eventFilter(QObject *obj, QEvent *event)
{
if (obj == _list->viewport() && event->type() == QEvent::Resize &&
_placeholder->isVisible()) {
_placeholder->setGeometry(_list->viewport()->rect());
}
return QWidget::eventFilter(obj, event);
}
void ListEditor::showEvent(QShowEvent *e)
{
QWidget::showEvent(e);
@@ -64,6 +114,24 @@ int ListEditor::GetIndexOfSignal() const
void ListEditor::UpdateListSize()
{
SetHeightToContentHeight(_list);
if (_list->count() == 0 && !_placeholder->text().isEmpty()) {
auto height = _list->fontMetrics().height() * 3;
_list->setMinimumHeight(height);
_list->setMaximumHeight(height);
}
if (_minHeight >= 0 && _list->minimumHeight() != _minHeight) {
_list->setMinimumHeight(_minHeight);
}
if (_maxHeight >= 0 && _list->maximumHeight() != _maxHeight) {
if (_list->minimumHeight() > _maxHeight) {
_list->setMinimumHeight(_maxHeight);
}
_list->setMaximumHeight(_maxHeight);
}
adjustSize();
updateGeometry();
}

View File

@@ -2,6 +2,7 @@
#include "export-symbol-helper.hpp"
#include "list-controls.hpp"
#include <QLabel>
#include <QListWidget>
#include <QLayout>
@@ -13,9 +14,13 @@ class ADVSS_EXPORT ListEditor : public QWidget {
public:
ListEditor(QWidget *parent = nullptr, bool reorder = true);
int count() const { return _list->count(); };
void SetPlaceholderText(const QString &text);
void SetMinListHeight(int);
void SetMaxListHeight(int);
protected:
void showEvent(QShowEvent *);
bool eventFilter(QObject *, QEvent *);
private slots:
virtual void Add() = 0;
@@ -23,6 +28,7 @@ private slots:
virtual void Up(){};
virtual void Down(){};
virtual void Clicked(QListWidgetItem *) {}
void UpdatePlaceholder();
protected:
void UpdateListSize();
@@ -31,6 +37,11 @@ protected:
QListWidget *_list;
ListControls *_controls;
QVBoxLayout *_mainLayout;
private:
QLabel *_placeholder;
int _minHeight = -1;
int _maxHeight = -1;
};
} // namespace advss

View File

@@ -9,19 +9,15 @@ namespace advss {
std::variant<double, std::string> EvalMathExpression(const std::string &expr)
{
static bool setupDone = false;
static exprtk::symbol_table<double> symbolTable;
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_real_distribution<double> dis(0.0, 1.0);
static auto randomFunc = []() {
thread_local std::mt19937 gen(std::random_device{}());
thread_local std::uniform_real_distribution<double> dis(0.0,
1.0);
return dis(gen);
};
if (!setupDone) {
symbolTable.add_function("random", randomFunc);
setupDone = true;
}
exprtk::symbol_table<double> symbolTable;
symbolTable.add_function("random", randomFunc);
exprtk::expression<double> expression;
expression.register_symbol_table(symbolTable);

View File

@@ -75,6 +75,18 @@ static std::vector<std::function<void()>> &getStopSteps()
return steps;
}
static std::vector<std::function<void(obs_data_t *)>> &getEarlySaveSteps()
{
static std::vector<std::function<void(obs_data_t *)>> steps;
return steps;
}
static std::vector<std::function<void(obs_data_t *)>> &getEarlyLoadSteps()
{
static std::vector<std::function<void(obs_data_t *)>> steps;
return steps;
}
static std::vector<std::function<void(obs_data_t *)>> &getSaveSteps()
{
static std::vector<std::function<void(obs_data_t *)>> steps;
@@ -109,6 +121,18 @@ void LoadPluginSettings(obs_data_t *obj)
GetSwitcher()->LoadSettings(obj);
}
void AddEarlySaveStep(std::function<void(obs_data_t *)> step)
{
std::lock_guard<std::mutex> lock(mutex);
getEarlySaveSteps().emplace_back(step);
}
void AddEarlyLoadStep(std::function<void(obs_data_t *)> step)
{
std::lock_guard<std::mutex> lock(mutex);
getEarlyLoadSteps().emplace_back(step);
}
void AddSaveStep(std::function<void(obs_data_t *)> step)
{
std::lock_guard<std::mutex> lock(mutex);
@@ -136,6 +160,9 @@ void AddIntervalResetStep(std::function<void()> step)
void RunSaveSteps(obs_data_t *obj)
{
std::lock_guard<std::mutex> lock(mutex);
for (const auto &func : getEarlySaveSteps()) {
func(obj);
}
for (const auto &func : getSaveSteps()) {
func(obj);
}
@@ -144,6 +171,9 @@ void RunSaveSteps(obs_data_t *obj)
void RunLoadSteps(obs_data_t *obj)
{
std::lock_guard<std::mutex> lock(mutex);
for (const auto &func : getEarlyLoadSteps()) {
func(obj);
}
for (const auto &func : getLoadSteps()) {
func(obj);
}
@@ -310,7 +340,7 @@ bool HighlightUIElementsEnabled()
bool OBSIsShuttingDown()
{
return GetSwitcher() && GetSwitcher()->obsIsShuttingDown;
return !GetSwitcher() || GetSwitcher()->obsIsShuttingDown;
}
bool InitialLoadIsComplete()

View File

@@ -8,6 +8,8 @@ namespace advss {
void SavePluginSettings(obs_data_t *);
EXPORT void LoadPluginSettings(obs_data_t *);
void AddEarlySaveStep(std::function<void(obs_data_t *)>);
void AddEarlyLoadStep(std::function<void(obs_data_t *)>);
EXPORT void AddSaveStep(std::function<void(obs_data_t *)>);
EXPORT void AddLoadStep(std::function<void(obs_data_t *)>);
EXPORT void AddPostLoadStep(std::function<void()>);

View File

@@ -173,10 +173,9 @@ void PopulateTransitionSelection(QComboBox *sel, bool addCurrent, bool addAny,
void PopulateWindowSelection(QComboBox *sel, bool addSelect)
{
std::vector<std::string> windows;
GetWindowList(windows);
const auto windows = GetWindowList();
for (std::string &window : windows) {
for (const std::string &window : windows) {
sel->addItem(window.c_str());
}
@@ -257,8 +256,7 @@ void PopulateMediaSelection(QComboBox *sel, bool addSelect)
void PopulateProcessSelection(QComboBox *sel, bool addSelect)
{
QStringList processes;
GetProcessList(processes);
auto processes = GetProcessList();
processes.sort();
for (QString &process : processes) {
sel->addItem(process);

View File

@@ -66,4 +66,15 @@ void Lockable::WithLock(const std::function<void()> &func)
func();
}
SuspendLock::SuspendLock(Lockable &lockable)
: _mtx(static_cast<std::mutex &>(lockable._mtx))
{
_mtx.unlock();
}
SuspendLock::~SuspendLock()
{
_mtx.lock();
}
} // namespace advss

View File

@@ -51,6 +51,24 @@ public:
private:
PerInstanceMutex _mtx;
friend class SuspendLock;
};
// RAII guard that temporarily releases a Lockable's per-segment lock.
// Use this inside PerformAction() / CheckCondition() to unblock the UI
// during long-running operations while still running on the original segment.
// The caller MUST be holding the lock (i.e. be inside WithLock) when
// constructing this object; the lock is re-acquired on destruction.
class EXPORT SuspendLock {
public:
SuspendLock(Lockable &lockable);
~SuspendLock();
SuspendLock(const SuspendLock &) = delete;
SuspendLock &operator=(const SuspendLock &) = delete;
private:
std::mutex &_mtx;
};
} // namespace advss

View File

@@ -5,14 +5,26 @@
#include "macro-edit.hpp"
#include "macro-segment.hpp"
#include "plugin-state-helpers.hpp"
#include "sync-helpers.hpp"
#include "ui-helpers.hpp"
#include "utility.hpp"
#include "variable.hpp"
#include <QVariant>
#include <QAbstractItemView>
#include <QHBoxLayout>
#include <QLabel>
#include <QPropertyAnimation>
#include <QPushButton>
#include <QToolButton>
#include <QVariant>
#include <QVBoxLayout>
#include <atomic>
Q_DECLARE_METATYPE(advss::TempVariableRef);
static std::atomic<uint64_t> tempVarInUseGeneration{0};
namespace advss {
TempVariable::TempVariable(const std::string &id, const std::string &name,
@@ -120,6 +132,60 @@ TempVariableRef TempVariable::GetRef() const
return ref;
}
static bool refsContain(const std::vector<TempVariableRef> &refs,
const TempVariableRef &ref)
{
for (const auto &r : refs) {
if (r == ref) {
return true;
}
}
return false;
}
template<typename T>
static bool segmentsReferTo(const T &segments, const TempVariableRef &ref)
{
for (const auto &segment : segments) {
if (refsContain(segment->GetTempVarRefs(), ref)) {
return true;
}
}
return false;
}
bool TempVariable::IsInUse() const
{
const auto currentGen =
tempVarInUseGeneration.load(std::memory_order_relaxed);
if (_isInUseCacheGeneration == currentGen) {
return _isInUseCache;
}
bool inUse = false;
const auto ref = GetRef();
if (ref.HasValidID()) {
for (const auto &macro : GetAllMacros()) {
if (segmentsReferTo(macro->Conditions(), ref) ||
segmentsReferTo(macro->Actions(), ref) ||
segmentsReferTo(macro->ElseActions(), ref)) {
inUse = true;
break;
}
}
}
_isInUseCache = inUse;
_isInUseCacheGeneration = currentGen;
return inUse;
}
void IncrementTempVarInUseGeneration()
{
tempVarInUseGeneration.fetch_add(1, std::memory_order_relaxed);
}
TempVariableRef::SegmentType TempVariableRef::GetType() const
{
auto segment = _segment.lock();
@@ -665,13 +731,302 @@ TempVarSignalManager *TempVarSignalManager::Instance()
void NotifyUIAboutTempVarChange(MacroSegment *segment)
{
obs_queue_task(
OBS_TASK_UI,
IncrementTempVarInUseGeneration();
QueueUITask(
[](void *segment) {
TempVarSignalManager::Instance()->SegmentTempVarsChanged(
(MacroSegment *)segment);
},
segment, false);
segment);
}
TempVarOutputMappingsWidget::TempVarOutputMappingsWidget(QWidget *parent)
: QWidget(parent),
_rowsLayout(new QVBoxLayout()),
_addButton(new QPushButton(
obs_module_text("AdvSceneSwitcher.tempVar.outputMappings.add"),
this)),
_animation(new QPropertyAnimation(this, "maximumHeight", this))
{
_rowsLayout->setContentsMargins(0, 0, 0, 0);
_rowsLayout->setSpacing(2);
auto rowsContainer = new QWidget(this);
rowsContainer->setLayout(_rowsLayout);
auto label = new QLabel(
obs_module_text("AdvSceneSwitcher.tempVar.outputMappings"),
this);
auto mainLayout = new QVBoxLayout();
mainLayout->addWidget(label);
mainLayout->addWidget(rowsContainer);
mainLayout->addWidget(_addButton);
setLayout(mainLayout);
_animation->setDuration(300);
_animation->setEasingCurve(QEasingCurve::InOutQuad);
connect(_animation, &QPropertyAnimation::finished, this,
&TempVarOutputMappingsWidget::AnimationFinished);
hide();
connect(_addButton, &QPushButton::clicked, this,
&TempVarOutputMappingsWidget::Add);
connect(TempVarSignalManager::Instance(),
SIGNAL(SegmentTempVarsChanged(MacroSegment *)), this,
SLOT(SegmentTempVarsChanged(MacroSegment *)));
}
void TempVarOutputMappingsWidget::SetSegment(MacroSegment *segment)
{
_segment = segment;
_panelExpanded = false;
Rebuild();
}
void TempVarOutputMappingsWidget::Add()
{
if (!_segment) {
return;
}
auto mappings = _segment->GetVarMappings();
mappings.push_back(VarMapping{});
{
auto lock = LockContext();
_segment->SetVarMappings(mappings);
}
Rebuild();
}
void TempVarOutputMappingsWidget::Remove(int rowIdx)
{
if (!_segment) {
return;
}
auto mappings = _segment->GetVarMappings();
if (rowIdx < 0 || rowIdx >= (int)mappings.size()) {
return;
}
mappings.erase(mappings.begin() + rowIdx);
{
auto lock = LockContext();
_segment->SetVarMappings(mappings);
}
Rebuild();
}
void TempVarOutputMappingsWidget::SegmentTempVarsChanged(MacroSegment *segment)
{
if (segment != _segment) {
return;
}
// Re-populate each temp var combobox in case temp vars were added/removed
_loading = true;
const auto &mappings = _segment->GetVarMappings();
for (int i = 0; i < (int)_rows.size(); i++) {
auto combo = _rows[i].tempVarCombo;
const QSignalBlocker blocker(combo);
combo->clear();
PopulateTempVarCombo(combo);
if (i < (int)mappings.size() &&
mappings[i].tempVar.HasValidID()) {
QVariant v;
v.setValue(mappings[i].tempVar);
combo->setCurrentIndex(combo->findData(v));
}
}
_loading = false;
UpdateVisibility();
}
void TempVarOutputMappingsWidget::WriteBackMappings()
{
if (_loading || !_segment) {
return;
}
std::vector<VarMapping> mappings;
for (const auto &row : _rows) {
VarMapping m;
const int idx = row.tempVarCombo->currentIndex();
if (idx >= 0) {
m.tempVar = row.tempVarCombo->itemData(idx)
.value<TempVariableRef>();
}
auto item = row.varSelection->GetCurrentItem();
if (!item) {
continue;
}
m.variable = GetWeakVariableByName(item->Name());
mappings.push_back(std::move(m));
}
auto lock = LockContext();
_segment->SetVarMappings(mappings);
}
void TempVarOutputMappingsWidget::Rebuild()
{
_loading = true;
// Remove all existing row widgets from layout and delete them
for (auto &row : _rows) {
_rowsLayout->removeWidget(row.container);
delete row.container;
}
_rows.clear();
if (!_segment) {
_loading = false;
UpdateVisibility();
return;
}
const auto &mappings = _segment->GetVarMappings();
for (int i = 0; i < (int)mappings.size(); i++) {
const auto &mapping = mappings[i];
auto container = new QWidget(this);
auto rowLayout = new QHBoxLayout();
rowLayout->setContentsMargins(0, 0, 0, 0);
auto tempVarCombo = new FilterComboBox(
container,
obs_module_text("AdvSceneSwitcher.tempVar.select"));
tempVarCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
tempVarCombo->setMaximumWidth(350);
tempVarCombo->setDuplicatesEnabled(true);
PopulateTempVarCombo(tempVarCombo);
if (mapping.tempVar.HasValidID()) {
QVariant v;
v.setValue(mapping.tempVar);
tempVarCombo->setCurrentIndex(
tempVarCombo->findData(v));
}
auto arrowLabel = new QLabel(container);
{
QIcon icon;
const auto path = (GetThemeTypeName() == "Light")
? "theme:Light/right.svg"
: "theme:Dark/right.svg";
icon.addFile(QString::fromUtf8(path), QSize(),
QIcon::Normal, QIcon::Off);
arrowLabel->setPixmap(icon.pixmap(16, 16));
}
auto varSel = new VariableSelection(container);
varSel->SetVariable(mapping.variable);
auto removeBtn = new QToolButton(container);
removeBtn->setProperty("themeID", QVariant(QString::fromUtf8(
"removeIconSmall")));
removeBtn->setProperty(
"class", QVariant(QString::fromUtf8("icon-trash")));
removeBtn->setToolTip(obs_module_text(
"AdvSceneSwitcher.tempVar.outputMappings.remove"));
rowLayout->addWidget(tempVarCombo, 1);
rowLayout->addWidget(arrowLabel);
rowLayout->addWidget(varSel, 1);
rowLayout->addWidget(removeBtn);
container->setLayout(rowLayout);
_rowsLayout->addWidget(container);
_rows.push_back({container, tempVarCombo, varSel});
const int capturedIdx = i;
connect(removeBtn, &QPushButton::clicked, this,
[this, capturedIdx]() { Remove(capturedIdx); });
connect(tempVarCombo,
QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &TempVarOutputMappingsWidget::WriteBackMappings);
connect(varSel, &VariableSelection::SelectionChanged, this,
[this](const QString &) { WriteBackMappings(); });
}
_loading = false;
UpdateVisibility();
}
bool TempVarOutputMappingsWidget::IsExpandable() const
{
return _segment && !_segment->GetOwnTempVars().empty();
}
void TempVarOutputMappingsWidget::SetPanelExpanded(bool expanded)
{
_panelExpanded = expanded;
UpdateHeight();
}
void TempVarOutputMappingsWidget::SetSectionCollapsed(bool collapsed)
{
_sectionCollapsed = collapsed;
UpdateHeight();
}
void TempVarOutputMappingsWidget::AnimateTo(int targetHeight)
{
if (_animation->state() == QAbstractAnimation::Running) {
_animation->stop();
}
if (targetHeight > 0 && !isVisible()) {
setMinimumHeight(0);
setMaximumHeight(0);
show();
}
const int currentMax = maximumHeight();
const int currentHeight = (currentMax >= QWIDGETSIZE_MAX) ? height()
: currentMax;
_animationTargetHeight = targetHeight;
_animation->setStartValue(currentHeight);
_animation->setEndValue(targetHeight);
_animation->start();
}
void TempVarOutputMappingsWidget::AnimationFinished()
{
if (_animationTargetHeight > 0) {
setMaximumHeight(QWIDGETSIZE_MAX);
} else {
hide();
setMaximumHeight(QWIDGETSIZE_MAX);
}
}
void TempVarOutputMappingsWidget::UpdateHeight()
{
const bool shouldShow = _panelExpanded && !_sectionCollapsed &&
IsExpandable();
if (shouldShow) {
AnimateTo(sizeHint().height());
} else {
AnimateTo(0);
}
}
void TempVarOutputMappingsWidget::UpdateVisibility()
{
const bool expandable = IsExpandable();
emit ExpandableChanged(expandable);
UpdateHeight();
}
void TempVarOutputMappingsWidget::PopulateTempVarCombo(
FilterComboBox *combo) const
{
if (!_segment) {
return;
}
for (const auto &var : _segment->GetOwnTempVars()) {
QVariant v;
v.setValue(var.GetRef());
combo->addItem(QString::fromStdString(var.Name()), v);
}
}
} // namespace advss

View File

@@ -5,11 +5,12 @@
#include <obs-data.h>
#include <mutex>
#include <optional>
#include <QEnterEvent>
#include <QEvent>
#include <QStringList>
#include <string>
class QPropertyAnimation;
class QPushButton;
class QVBoxLayout;
namespace advss {
class Macro;
@@ -17,6 +18,9 @@ class MacroEdit;
class MacroSegment;
class TempVariableRef;
class TempVariableSelection;
class TempVarOutputMappingsWidget;
class VariableSelection;
struct VarMapping;
// TempVariables are variables that are local to a given macro.
// They can be created and used by macro segments.
@@ -44,6 +48,7 @@ public:
void SetValue(const std::string &val);
void InvalidateValue();
TempVariableRef GetRef() const;
EXPORT bool IsInUse() const;
private:
std::string _id = "";
@@ -53,6 +58,8 @@ private:
mutable std::mutex _lastValuesMutex;
std::vector<std::string> _lastValues;
bool _valueIsValid = false;
mutable bool _isInUseCache = false;
mutable uint64_t _isInUseCacheGeneration = UINT64_MAX;
std::weak_ptr<MacroSegment> _segment;
friend TempVariableSelection;
@@ -122,5 +129,50 @@ private:
};
void NotifyUIAboutTempVarChange(MacroSegment *);
EXPORT void IncrementTempVarInUseGeneration();
class ADVSS_EXPORT TempVarOutputMappingsWidget : public QWidget {
Q_OBJECT
public:
TempVarOutputMappingsWidget(QWidget *parent);
void SetSegment(MacroSegment *segment);
void SetPanelExpanded(bool expanded);
void SetSectionCollapsed(bool collapsed);
bool IsExpandable() const;
signals:
void ExpandableChanged(bool);
private slots:
void Add();
void Remove(int rowIdx);
void SegmentTempVarsChanged(MacroSegment *segment);
void WriteBackMappings();
void AnimationFinished();
private:
void Rebuild();
void UpdateVisibility();
void UpdateHeight();
void AnimateTo(int targetHeight);
void PopulateTempVarCombo(FilterComboBox *combo) const;
struct MappingRow {
QWidget *container;
FilterComboBox *tempVarCombo;
VariableSelection *varSelection;
};
MacroSegment *_segment = nullptr;
QVBoxLayout *_rowsLayout;
QPushButton *_addButton;
std::vector<MappingRow> _rows;
bool _loading = false;
bool _panelExpanded = false;
bool _sectionCollapsed = false;
int _animationTargetHeight = 0;
QPropertyAnimation *_animation;
};
} // namespace advss

View File

@@ -9,11 +9,12 @@ void StringVariable::Resolve() const
_resolvedValue = _value;
return;
}
if (_lastResolve == GetLastVariableChangeTime()) {
const auto lastChange = GetLastVariableChangeTime();
if (_lastResolve == lastChange) {
return;
}
_resolvedValue = SubstitueVariables(_value);
_lastResolve = GetLastVariableChangeTime();
_lastResolve = lastChange;
}
StringVariable::operator std::string() const
@@ -81,7 +82,7 @@ std::string SubstitueVariables(std::string str)
const auto &variable = std::dynamic_pointer_cast<Variable>(v);
const std::string pattern = "${" + variable->Name() + "}";
if (ReplaceAll(str, pattern, variable->Value(false))) {
variable->UpdateLastUsed();
variable->MarkAsUsed();
}
}
return str;

View File

@@ -63,7 +63,6 @@ static void load(obs_data_t *data)
{
tabSettings.Load(data, "tabSettings");
dockSettings.Load(data, "dockSettings");
enableDock(obs_data_get_bool(data, "addVariablesDock"));
}

View File

@@ -1,9 +1,12 @@
#include "variable.hpp"
#include "math-helpers.hpp"
#include "obs-module-helper.hpp"
#include "plugin-state-helpers.hpp"
#include "ui-helpers.hpp"
#include "utility.hpp"
#include <obs.hpp>
#include <QGridLayout>
namespace advss {
@@ -12,16 +15,32 @@ static std::deque<std::shared_ptr<Item>> variables;
// Keep track of the last time a variable was changed to save some work when
// when resolving strings containing variables, etc.
static std::mutex lastVariableChangeMutex;
static std::chrono::high_resolution_clock::time_point lastVariableChange{};
static bool setup()
{
AddEarlySaveStep(SaveVariables);
AddEarlyLoadStep(LoadVariables);
AddPluginCleanupStep([]() { variables.clear(); });
return true;
}
static bool setupDone = setup();
static void setLastVariableChangeTime()
{
std::lock_guard<std::mutex> lock(lastVariableChangeMutex);
lastVariableChange = std::chrono::high_resolution_clock::now();
}
Variable::Variable() : Item()
{
lastVariableChange = std::chrono::high_resolution_clock::now();
setLastVariableChangeTime();
}
Variable::~Variable()
{
lastVariableChange = std::chrono::high_resolution_clock::now();
setLastVariableChangeTime();
}
void Variable::Load(obs_data_t *obj)
@@ -37,7 +56,7 @@ void Variable::Load(obs_data_t *obj)
SetValue(_defaultValue);
}
lastVariableChange = std::chrono::high_resolution_clock::now();
setLastVariableChangeTime();
}
void Variable::Save(obs_data_t *obj) const
@@ -45,8 +64,11 @@ void Variable::Save(obs_data_t *obj) const
Item::Save(obj);
obs_data_set_int(obj, "saveAction", static_cast<int>(_saveAction));
if (_saveAction == SaveAction::SAVE) {
obs_data_set_string(obj, "value", _value.c_str());
{
std::lock_guard<std::mutex> lock(_mutex);
if (_saveAction == SaveAction::SAVE) {
obs_data_set_string(obj, "value", _value.c_str());
}
}
obs_data_set_string(obj, "defaultValue", _defaultValue.c_str());
@@ -62,6 +84,18 @@ std::string Variable::Value(bool updateLastUsed) const
return _value;
}
std::string Variable::GetPreviousValue() const
{
std::lock_guard<std::mutex> lock(_mutex);
return _previousValue;
}
int Variable::GetValueChangeCount() const
{
std::lock_guard<std::mutex> lock(_mutex);
return _valueChangeCount;
}
std::optional<double> Variable::DoubleValue() const
{
return GetDouble(Value());
@@ -79,8 +113,11 @@ void Variable::SetValue(const std::string &value)
_value = value;
UpdateLastUsed();
UpdateLastChanged();
lastVariableChange = std::chrono::high_resolution_clock::now();
if (_previousValue != _value) {
_lastChanged = std::chrono::high_resolution_clock::now();
++_valueChangeCount;
}
setLastVariableChangeTime();
}
void Variable::SetValue(double value)
@@ -90,6 +127,7 @@ void Variable::SetValue(double value)
std::optional<uint64_t> Variable::GetSecondsSinceLastUse() const
{
std::lock_guard<std::mutex> lock(_mutex);
if (_lastUsed.time_since_epoch().count() == 0) {
return {};
}
@@ -101,6 +139,7 @@ std::optional<uint64_t> Variable::GetSecondsSinceLastUse() const
std::optional<uint64_t> Variable::GetSecondsSinceLastChange() const
{
std::lock_guard<std::mutex> lock(_mutex);
if (_lastChanged.time_since_epoch().count() == 0) {
return {};
}
@@ -116,12 +155,10 @@ void Variable::UpdateLastUsed() const
_lastUsed = std::chrono::high_resolution_clock::now();
}
void Variable::UpdateLastChanged()
void Variable::MarkAsUsed() const
{
if (_previousValue != _value) {
_lastChanged = std::chrono::high_resolution_clock::now();
++_valueChangeCount;
}
std::lock_guard<std::mutex> lock(_mutex);
UpdateLastUsed();
}
static void populateSaveActionSelection(QComboBox *list)
@@ -206,7 +243,7 @@ bool VariableSettingsDialog::AskForSettings(QWidget *parent, Variable &settings)
dialog._defaultValue->toPlainText().toStdString();
settings._saveAction =
static_cast<Variable::SaveAction>(dialog._save->currentIndex());
lastVariableChange = std::chrono::high_resolution_clock::now();
setLastVariableChangeTime();
return true;
}
@@ -393,34 +430,31 @@ static bool variableWithNameExists(const std::string &name)
void SaveVariables(obs_data_t *obj)
{
obs_data_array_t *variablesArray = obs_data_array_create();
OBSDataArrayAutoRelease variablesArray = obs_data_array_create();
for (const auto &v : variables) {
obs_data_t *array_obj = obs_data_create();
OBSDataAutoRelease array_obj = obs_data_create();
v->Save(array_obj);
obs_data_array_push_back(variablesArray, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "variables", variablesArray);
obs_data_array_release(variablesArray);
}
void LoadVariables(obs_data_t *obj)
{
variables.clear();
obs_data_array_t *variablesArray = obs_data_get_array(obj, "variables");
OBSDataArrayAutoRelease variablesArray =
obs_data_get_array(obj, "variables");
size_t count = obs_data_array_count(variablesArray);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(variablesArray, i);
OBSDataAutoRelease array_obj =
obs_data_array_item(variablesArray, i);
auto var = Variable::Create();
variables.emplace_back(var);
variables.back()->Load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(variablesArray);
}
static void signalImportedVariables(void *varsPtr)
@@ -435,16 +469,15 @@ static void signalImportedVariables(void *varsPtr)
void ImportVariables(obs_data_t *data)
{
obs_data_array_t *array = obs_data_get_array(data, "variables");
OBSDataArrayAutoRelease array = obs_data_get_array(data, "variables");
size_t count = obs_data_array_count(array);
auto importedVars = new std::vector<std::shared_ptr<Item>>;
for (size_t i = 0; i < count; i++) {
obs_data_t *arrayElement = obs_data_array_item(array, i);
OBSDataAutoRelease arrayElement = obs_data_array_item(array, i);
auto var = Variable::Create();
var->Load(arrayElement);
obs_data_release(arrayElement);
if (variableWithNameExists(var->Name())) {
continue;
@@ -454,13 +487,12 @@ void ImportVariables(obs_data_t *data)
importedVars->emplace_back(var);
}
obs_data_array_release(array);
QueueUITask(signalImportedVariables, importedVars);
}
std::chrono::high_resolution_clock::time_point GetLastVariableChangeTime()
{
std::lock_guard<std::mutex> lock(lastVariableChangeMutex);
return lastVariableChange;
}

View File

@@ -34,16 +34,15 @@ public:
EXPORT std::string Value(bool updateLastUsed = true) const;
EXPORT std::optional<double> DoubleValue() const;
EXPORT std::optional<int> IntValue() const;
std::string GetPreviousValue() const { return _previousValue; };
std::string GetPreviousValue() const;
std::string GetDefaultValue() const { return _defaultValue; }
EXPORT void SetValue(const std::string &value);
void SetValue(double value);
SaveAction GetSaveAction() const { return _saveAction; }
int GetValueChangeCount() const { return _valueChangeCount; }
int GetValueChangeCount() const;
std::optional<uint64_t> GetSecondsSinceLastUse() const;
std::optional<uint64_t> GetSecondsSinceLastChange() const;
void UpdateLastUsed() const;
void UpdateLastChanged();
void MarkAsUsed() const;
private:
SaveAction _saveAction = SaveAction::DONT_SAVE;
@@ -55,6 +54,8 @@ private:
mutable std::chrono::high_resolution_clock::time_point _lastChanged;
mutable std::mutex _mutex;
void UpdateLastUsed() const;
friend VariableSelection;
friend VariableSettingsDialog;
};

View File

@@ -1,10 +1,12 @@
#include "platform-funcs.hpp"
#include "plugin-state-helpers.hpp"
#include <windows.h>
#include <UIAutomation.h>
#include <util/platform.h>
#include <TlHelp32.h>
#include <Psapi.h>
#include <memory>
#include <locale>
#include <codecvt>
#include <string>
@@ -16,6 +18,9 @@
#include <QWidget>
#include <mutex>
#define ADVSS_WIDEN_(x) L##x
#define ADVSS_WIDEN(x) ADVSS_WIDEN_(x)
namespace advss {
#define MAX_SEARCH 1000
@@ -133,9 +138,9 @@ const std::vector<std::string> getOBSWindows()
return lastDoneHelper->windows;
}
void GetWindowList(std::vector<std::string> &windows)
std::vector<std::string> GetWindowList()
{
windows.resize(0);
std::vector<std::string> windows;
EnumWindowsWithMetro(GetTitleCB, reinterpret_cast<LPARAM>(&windows));
// Also add OBS windows
@@ -147,20 +152,10 @@ void GetWindowList(std::vector<std::string> &windows)
// Add entry for OBS Studio itself - see GetCurrentWindowTitle()
windows.emplace_back("OBS");
return windows;
}
void GetWindowList(QStringList &windows)
{
windows.clear();
std::vector<std::string> w;
GetWindowList(w);
for (auto window : w) {
windows << QString::fromStdString(window);
}
}
void GetCurrentWindowTitle(std::string &title)
std::string GetCurrentWindowTitle()
{
HWND window = GetForegroundWindow();
DWORD pid;
@@ -178,15 +173,15 @@ void GetCurrentWindowTitle(std::string &title)
//
// So instead rely on Qt to get the title of the active window.
if (GetCurrentProcessId() == pid) {
auto window = QApplication::activeWindow();
if (window) {
title = window->windowTitle().toStdString();
} else {
title = "OBS";
auto obsWindow = QApplication::activeWindow();
if (obsWindow) {
return obsWindow->windowTitle().toStdString();
}
return;
return "OBS";
}
std::string title;
GetWindowTitle(window, title);
return title;
}
static HWND getHWNDfromTitle(const std::string &title)
@@ -350,22 +345,22 @@ bool IsFullscreen(const std::string &title)
return false;
}
void GetProcessList(QStringList &processes)
QStringList GetProcessList()
{
QStringList processes;
HANDLE procSnapshot;
PROCESSENTRY32 procEntry;
procSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (procSnapshot == INVALID_HANDLE_VALUE) {
return;
return processes;
}
procEntry.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(procSnapshot, &procEntry)) {
CloseHandle(procSnapshot);
return;
return processes;
}
do {
@@ -383,9 +378,10 @@ void GetProcessList(QStringList &processes)
} while (Process32Next(procSnapshot, &procEntry));
CloseHandle(procSnapshot);
return processes;
}
static void GetForegroundProcessName(QString &proc)
static QString getForegroundProcessNameStr()
{
// only checks if the current foreground window is from the same executable,
// may return true for any window from a program
@@ -396,31 +392,90 @@ static void GetForegroundProcessName(QString &proc)
HANDLE process = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
if (process == NULL) {
return;
return {};
}
WCHAR executablePath[600];
GetModuleFileNameEx(process, 0, executablePath, 600);
CloseHandle(process);
proc = QString::fromWCharArray(executablePath)
.split(QRegularExpression("(/|\\\\)"))
.back();
return QString::fromWCharArray(executablePath)
.split(QRegularExpression("(/|\\\\)"))
.back();
}
void GetForegroundProcessName(std::string &proc)
std::string GetForegroundProcessName()
{
QString temp;
GetForegroundProcessName(temp);
proc = temp.toStdString();
return getForegroundProcessNameStr().toStdString();
}
std::string GetForegroundProcessPath()
{
HWND foregroundWindow = GetForegroundWindow();
DWORD processId = 0;
GetWindowThreadProcessId(foregroundWindow, &processId);
HANDLE process = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
if (process == NULL) {
return {};
}
WCHAR executablePath[600];
GetModuleFileNameEx(process, 0, executablePath, 600);
CloseHandle(process);
return QString::fromWCharArray(executablePath).toStdString();
}
QStringList GetProcessPathsFromName(const QString &name)
{
QStringList paths;
HANDLE procSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (procSnapshot == INVALID_HANDLE_VALUE) {
return paths;
}
PROCESSENTRY32 procEntry;
procEntry.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(procSnapshot, &procEntry)) {
CloseHandle(procSnapshot);
return paths;
}
do {
QString exeName = QString::fromWCharArray(procEntry.szExeFile);
if (exeName != name) {
continue;
}
HANDLE process =
OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE, procEntry.th32ProcessID);
if (process == NULL) {
continue;
}
WCHAR executablePath[600];
if (GetModuleFileNameEx(process, 0, executablePath, 600)) {
QString path = QString::fromWCharArray(executablePath);
if (!paths.contains(path)) {
paths.append(path);
}
}
CloseHandle(process);
} while (Process32Next(procSnapshot, &procEntry));
CloseHandle(procSnapshot);
return paths;
}
bool IsInFocus(const QString &executable)
{
// only checks if the current foreground window is from the same executable,
// may return true for any window from a program
QString foregroundProc;
GetForegroundProcessName(foregroundProc);
const auto foregroundProc = getForegroundProcessNameStr();
// True if executable switch equals current window
bool equals = (executable == foregroundProc);
@@ -450,9 +505,86 @@ int SecondsSinceLastInput()
return (getTime() - getLastInputTime()) / 1000;
}
static void addPluginFolderToSymbolPath()
{
// This runs after OBS_FRONTEND_EVENT_FINISHED_LOADING, which fires after
// obs_load_all_modules() completes. By that point OBS has already called
// reset_win32_symbol_paths() -> SymInitializeW(), so DbgHelp is
// initialized and we can append our plugins subfolder (where the PDB
// files live) to the existing search path.
HMODULE dbghelp = LoadLibraryW(L"DbgHelp");
if (!dbghelp) {
return;
}
typedef BOOL(WINAPI * SymGetSearchPathW_t)(HANDLE, PWSTR, DWORD);
typedef BOOL(WINAPI * SymSetSearchPathW_t)(HANDLE, PCWSTR);
typedef BOOL(WINAPI * SymRefreshModuleList_t)(HANDLE);
auto symGetSearchPathW = reinterpret_cast<SymGetSearchPathW_t>(
GetProcAddress(dbghelp, "SymGetSearchPathW"));
auto symSetSearchPathW = reinterpret_cast<SymSetSearchPathW_t>(
GetProcAddress(dbghelp, "SymSetSearchPathW"));
auto symRefreshModuleList = reinterpret_cast<SymRefreshModuleList_t>(
GetProcAddress(dbghelp, "SymRefreshModuleList"));
if (!symGetSearchPathW || !symSetSearchPathW || !symRefreshModuleList) {
FreeLibrary(dbghelp);
return;
}
HMODULE hModule = NULL;
if (!GetModuleHandleExW(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCWSTR>(addPluginFolderToSymbolPath),
&hModule)) {
FreeLibrary(dbghelp);
return;
}
wchar_t dllDir[MAX_PATH];
if (!GetModuleFileNameW(hModule, dllDir, MAX_PATH)) {
FreeLibrary(dbghelp);
return;
}
wchar_t *lastSep = wcsrchr(dllDir, L'\\');
if (!lastSep) {
FreeLibrary(dbghelp);
return;
}
*lastSep = L'\0';
wchar_t pluginsPath[MAX_PATH];
wcsncpy_s(pluginsPath, MAX_PATH, dllDir, _TRUNCATE);
wcsncat_s(pluginsPath, MAX_PATH, L"\\" ADVSS_WIDEN(ADVSS_PLUGIN_FOLDER),
_TRUNCATE);
constexpr DWORD currentPathLen = 4096;
constexpr DWORD newPathLen = 8192;
auto currentPath = std::make_unique<wchar_t[]>(currentPathLen);
auto newPath = std::make_unique<wchar_t[]>(newPathLen);
symGetSearchPathW(GetCurrentProcess(), currentPath.get(),
currentPathLen);
if (currentPath[0] != L'\0') {
_snwprintf_s(newPath.get(), newPathLen, _TRUNCATE, L"%s;%s",
currentPath.get(), pluginsPath);
} else {
wcsncpy_s(newPath.get(), newPathLen, pluginsPath, _TRUNCATE);
}
symSetSearchPathW(GetCurrentProcess(), newPath.get());
symRefreshModuleList(GetCurrentProcess());
FreeLibrary(dbghelp);
}
void PlatformInit()
{
CoInitialize(NULL);
AddFinishedLoadingStep(addPluginFolderToSymbolPath);
}
void PlatformCleanup()

View File

@@ -25,6 +25,8 @@ target_sources(
macro-action-obs-settings.hpp
macro-action-osc.cpp
macro-action-osc.hpp
macro-action-play-audio.cpp
macro-action-play-audio.hpp
macro-action-plugin-state.cpp
macro-action-plugin-state.hpp
macro-action-profile.cpp
@@ -57,6 +59,8 @@ target_sources(
macro-action-sequence.hpp
macro-action-source.cpp
macro-action-source.hpp
macro-action-source-interaction.cpp
macro-action-source-interaction.hpp
macro-action-streaming.cpp
macro-action-streaming.hpp
macro-action-studio-mode.cpp
@@ -144,7 +148,17 @@ target_sources(
target_sources(
${PROJECT_NAME}
PRIVATE utils/audio-helpers.cpp
PRIVATE utils/source-interaction-recorder.cpp
utils/source-interaction-recorder.hpp
utils/source-interaction-step.cpp
utils/source-interaction-step.hpp
utils/source-interaction-step-edit.cpp
utils/source-interaction-step-edit.hpp
utils/source-interaction-step-list.cpp
utils/source-interaction-step-list.hpp
utils/source-preview-widget.cpp
utils/source-preview-widget.hpp
utils/audio-helpers.cpp
utils/audio-helpers.hpp
utils/connection-manager.cpp
utils/connection-manager.hpp

View File

@@ -9,6 +9,15 @@ namespace advss {
const std::string MacroActionFilter::id = "filter";
std::vector<TempVariableRef> MacroActionFilter::GetTempVarRefs() const
{
auto refs = MacroSegment::GetTempVarRefs();
if (_tempVar.HasValidID()) {
refs.push_back(_tempVar);
}
return refs;
}
bool MacroActionFilter::_registered = MacroActionFactory::Register(
MacroActionFilter::id,
{MacroActionFilter::Create, MacroActionFilterEdit::Create,
@@ -428,6 +437,7 @@ void MacroActionFilterEdit::SelectionChanged(const TempVariableRef &var)
{
GUARD_LOADING_AND_LOCK();
_entryData->_tempVar = var;
IncrementTempVarInUseGeneration();
}
void MacroActionFilterEdit::SelectionChanged(const SourceSetting &setting)

View File

@@ -41,6 +41,8 @@ public:
SettingsInputMethod _settingsInputMethod =
SettingsInputMethod::INDIVIDUAL_MANUAL;
std::vector<TempVariableRef> GetTempVarRefs() const;
SourceSelection _source;
FilterSelection _filter;
Action _action = Action::ENABLE;

View File

@@ -95,7 +95,7 @@ static void waitHelper(std::unique_lock<std::mutex> *lock, Macro *macro,
}
}
void MacroActionMedia::PerformActionHelper(obs_source_t *source) const
void MacroActionMedia::PerformActionHelper(obs_source_t *source)
{
obs_media_state state = obs_source_media_get_state(source);
@@ -130,6 +130,7 @@ void MacroActionMedia::PerformActionHelper(obs_source_t *source) const
SeekToPercentage(source);
break;
case Action::WAIT_FOR_PLAYBACK_STOP: {
SuspendLock suspendLock(*this);
std::unique_lock<std::mutex> lock(*GetMutex());
waitHelper(&lock, GetMacro(), source);
break;

View File

@@ -45,7 +45,7 @@ public:
SceneSelection _scene;
private:
void PerformActionHelper(obs_source_t *) const;
void PerformActionHelper(obs_source_t *);
void SeekToPercentage(obs_source_t *source) const;
static bool _registered;

View File

@@ -0,0 +1,446 @@
#include "macro-action-play-audio.hpp"
#include "audio-helpers.hpp"
#include "layout-helpers.hpp"
#include "macro-helpers.hpp"
#include "sync-helpers.hpp"
#include <chrono>
#include <QFileInfo>
#include <QLabel>
namespace advss {
// Use a high output channel index that is unlikely to be claimed by OBS or
// other plugins. OBS supports channels 0-63; channel 0 is the main scene.
static constexpr uint32_t kPlaybackOutputChannel = 63;
const std::string MacroActionPlayAudio::id = "play_audio";
bool MacroActionPlayAudio::_registered = MacroActionFactory::Register(
MacroActionPlayAudio::id,
{MacroActionPlayAudio::Create, MacroActionPlayAudioEdit::Create,
"AdvSceneSwitcher.action.playAudio"});
static void deactivatePlayback(obs_source_t *source, bool wantsOutput)
{
if (wantsOutput) {
obs_set_output_source(kPlaybackOutputChannel, nullptr);
} else {
obs_source_dec_active(source);
}
}
static void waitForPlaybackToEnd(Macro *macro, obs_source_t *source,
int64_t maxMs = 0)
{
using namespace std::chrono_literals;
std::unique_lock<std::mutex> lock(*GetMutex());
SetMacroAbortWait(false);
// The media source needs time to open and decode before reaching
// PLAYING state. Poll until it starts (or the macro is stopped).
while (!MacroWaitShouldAbort() && !MacroIsStopped(macro)) {
if (obs_source_media_get_state(source) ==
OBS_MEDIA_STATE_PLAYING) {
break;
}
GetMacroWaitCV().wait_for(lock, 10ms);
}
// Now wait for playback to end. Require two consecutive non-playing
// samples to avoid false positives on brief state transitions.
// If maxMs > 0, also stop once that many milliseconds have elapsed.
const auto playbackStart = std::chrono::steady_clock::now();
static const int kStopThreshold = 2;
int stoppedCount = 0;
while (!MacroWaitShouldAbort() && !MacroIsStopped(macro)) {
if (maxMs > 0) {
const auto elapsed =
std::chrono::duration_cast<
std::chrono::milliseconds>(
std::chrono::steady_clock::now() -
playbackStart)
.count();
if (elapsed >= maxMs) {
obs_source_media_stop(source);
break;
}
}
if (obs_source_media_get_state(source) !=
OBS_MEDIA_STATE_PLAYING) {
if (++stoppedCount >= kStopThreshold) {
break;
}
} else {
stoppedCount = 0;
}
GetMacroWaitCV().wait_for(lock, 10ms);
}
}
bool MacroActionPlayAudio::PerformAction()
{
std::string path = _filePath;
if (path.empty()) {
return true;
}
if (!QFileInfo::exists(QString::fromStdString(path))) {
blog(LOG_WARNING, "audio file not found: \"%s\"", path.c_str());
return true;
}
OBSDataAutoRelease settings = obs_data_create();
obs_data_set_string(settings, "local_file", path.c_str());
obs_data_set_bool(settings, "is_local_file", true);
obs_data_set_bool(settings, "looping", false);
// Disable automatic restart on activate so we control start explicitly.
obs_data_set_bool(settings, "restart_on_activate", false);
obs_data_set_bool(settings, "close_when_inactive", true);
OBSSourceAutoRelease source = obs_source_create_private(
"ffmpeg_source", "advss_play_audio", settings);
if (!source) {
blog(LOG_WARNING,
"Failed to create ffmpeg_source for audio playback of \"%s\"",
path.c_str());
return true;
}
const float vol =
DecibelToPercent(static_cast<float>(_volumeDB.GetValue()));
obs_source_set_volume(source, vol);
obs_source_set_monitoring_type(source, _monitorType);
// Fall back to monitor-only if all output tracks are deselected —
// there is no point routing through the output channel if the mixer
// mask would silence every track.
const bool wantsOutput =
(_monitorType != OBS_MONITORING_TYPE_MONITOR_ONLY) &&
(_audioMixers != 0);
if (!wantsOutput &&
_monitorType == OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT) {
obs_source_set_monitoring_type(
source, OBS_MONITORING_TYPE_MONITOR_ONLY);
}
if (wantsOutput) {
obs_source_set_audio_mixers(source, _audioMixers);
// Route through a private scene so we can position the scene
// item far off-screen. This keeps the item "visible" (so audio
// is still mixed) while ensuring its video never intersects the
// output frame.
OBSSceneAutoRelease audioScene =
obs_scene_create_private("advss_audio_scene");
obs_sceneitem_t *item = obs_scene_add(audioScene, source);
if (item) {
struct vec2 pos = {-99999.0f, -99999.0f};
obs_sceneitem_set_pos(item, &pos);
}
obs_set_output_source(kPlaybackOutputChannel,
obs_scene_get_source(audioScene));
// audioScene released here; the output channel holds the
// remaining reference and keeps the scene alive.
} else {
obs_source_set_audio_mixers(source, 0);
obs_source_inc_active(source);
}
if (_useStartOffset && _startOffset.Milliseconds() > 0) {
obs_source_media_set_time(source, _startOffset.Milliseconds());
}
obs_source_media_play_pause(source, false);
const int64_t maxMs = _useDuration ? _playbackDuration.Milliseconds()
: 0;
if (_waitForCompletion) {
SetMacroAbortWait(false);
{
SuspendLock suspendLock(*this);
waitForPlaybackToEnd(GetMacro(), source, maxMs);
}
deactivatePlayback(source, wantsOutput);
return true;
}
// Keep the source alive in a background thread that cleans up
// once playback finishes. Grab an extra strong reference so the
// source survives beyond this stack frame.
auto rawSource = obs_source_get_ref(source);
auto macro = GetMacro();
std::thread cleanupThread([rawSource, wantsOutput, macro, maxMs]() {
waitForPlaybackToEnd(macro, rawSource, maxMs);
deactivatePlayback(rawSource, wantsOutput);
obs_source_release(rawSource);
});
AddMacroHelperThread(macro, std::move(cleanupThread));
return true;
}
void MacroActionPlayAudio::LogAction() const
{
ablog(LOG_INFO,
"playing audio file \"%s\" at %.1f dB (monitoring type %d, wait %d)",
_filePath.UnresolvedValue().c_str(),
static_cast<double>(_volumeDB.GetFixedValue()), _monitorType,
(int)_waitForCompletion);
}
bool MacroActionPlayAudio::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
_filePath.Save(obj, "filePath");
_volumeDB.Save(obj, "volumeDB");
obs_data_set_int(obj, "monitorType", _monitorType);
obs_data_set_int(obj, "audioMixers", _audioMixers);
obs_data_set_bool(obj, "useStartOffset", _useStartOffset);
_startOffset.Save(obj, "startOffset");
obs_data_set_bool(obj, "useDuration", _useDuration);
_playbackDuration.Save(obj, "playbackDuration");
obs_data_set_bool(obj, "waitForCompletion", _waitForCompletion);
return true;
}
bool MacroActionPlayAudio::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_filePath.Load(obj, "filePath");
_volumeDB.Load(obj, "volumeDB");
_monitorType = static_cast<obs_monitoring_type>(
obs_data_get_int(obj, "monitorType"));
_audioMixers =
static_cast<uint32_t>(obs_data_get_int(obj, "audioMixers"));
_useStartOffset = obs_data_get_bool(obj, "useStartOffset");
_startOffset.Load(obj, "startOffset");
_useDuration = obs_data_get_bool(obj, "useDuration");
_playbackDuration.Load(obj, "playbackDuration");
_waitForCompletion = obs_data_get_bool(obj, "waitForCompletion");
return true;
}
std::string MacroActionPlayAudio::GetShortDesc() const
{
return _filePath.UnresolvedValue();
}
std::shared_ptr<MacroAction> MacroActionPlayAudio::Create(Macro *m)
{
return std::make_shared<MacroActionPlayAudio>(m);
}
std::shared_ptr<MacroAction> MacroActionPlayAudio::Copy() const
{
return std::make_shared<MacroActionPlayAudio>(*this);
}
void MacroActionPlayAudio::ResolveVariablesToFixedValues()
{
_filePath.ResolveVariables();
_volumeDB.ResolveVariables();
_startOffset.ResolveVariables();
_playbackDuration.ResolveVariables();
}
MacroActionPlayAudioEdit::MacroActionPlayAudioEdit(
QWidget *parent, std::shared_ptr<MacroActionPlayAudio> entryData)
: QWidget(parent),
_filePath(new FileSelection(
FileSelection::Type::READ, this,
obs_module_text(
"AdvSceneSwitcher.action.playAudio.file.browse"))),
_volumeDB(new VariableDoubleSpinBox),
_monitorTypes(new QComboBox),
_tracksContainer(new QWidget),
_useStartOffset(new QCheckBox(this)),
_startOffset(new DurationSelection(this, true, 0.0)),
_useDuration(new QCheckBox(this)),
_playbackDuration(new DurationSelection(this, true, 0.0)),
_waitForCompletion(new QCheckBox(
obs_module_text("AdvSceneSwitcher.action.playAudio.wait")))
{
_volumeDB->setMinimum(-100.0);
_volumeDB->setMaximum(0.0);
_volumeDB->setSuffix("dB");
_volumeDB->setSpecialValueText("-inf");
if (obs_audio_monitoring_available()) {
PopulateMonitorTypeSelection(_monitorTypes);
} else {
_monitorTypes->addItem(obs_module_text(
"AdvSceneSwitcher.action.playAudio.monitorUnavailable"));
_monitorTypes->setEnabled(false);
}
for (int i = 0; i < 6; ++i) {
_tracks[i] = new QCheckBox(QString::number(i + 1));
QWidget::connect(_tracks[i], SIGNAL(stateChanged(int)), this,
SLOT(TrackChanged()));
}
QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this,
SLOT(FilePathChanged(const QString &)));
QWidget::connect(
_volumeDB,
SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
this, SLOT(VolumeDBChanged(const NumberVariable<double> &)));
QWidget::connect(_monitorTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(MonitorTypeChanged(int)));
QWidget::connect(_useStartOffset, SIGNAL(stateChanged(int)), this,
SLOT(UseStartOffsetChanged(int)));
QWidget::connect(_useDuration, SIGNAL(stateChanged(int)), this,
SLOT(UseDurationChanged(int)));
QWidget::connect(_startOffset,
SIGNAL(DurationChanged(const Duration &)), this,
SLOT(StartOffsetChanged(const Duration &)));
QWidget::connect(_playbackDuration,
SIGNAL(DurationChanged(const Duration &)), this,
SLOT(PlaybackDurationChanged(const Duration &)));
QWidget::connect(_waitForCompletion, SIGNAL(stateChanged(int)), this,
SLOT(WaitChanged(int)));
auto tracksLayout = new QHBoxLayout;
tracksLayout->setContentsMargins(0, 0, 0, 0);
tracksLayout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.action.playAudio.tracks")));
for (auto *cb : _tracks) {
tracksLayout->addWidget(cb);
}
tracksLayout->addStretch();
_tracksContainer->setLayout(tracksLayout);
auto volumeLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.playAudio.layout.volume"),
volumeLayout, {{"{{volumeDB}}", _volumeDB}});
auto monitorLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.playAudio.layout.monitor"),
monitorLayout, {{"{{monitorTypes}}", _monitorTypes}});
auto startOffsetLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.playAudio.layout.startOffset"),
startOffsetLayout,
{{"{{useStartOffset}}", _useStartOffset},
{"{{startOffset}}", _startOffset}});
auto playbackDurationLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.playAudio.layout.playbackDuration"),
playbackDurationLayout,
{{"{{useDuration}}", _useDuration},
{"{{playbackDuration}}", _playbackDuration}});
auto mainLayout = new QVBoxLayout;
mainLayout->addWidget(_filePath);
mainLayout->addLayout(volumeLayout);
mainLayout->addLayout(monitorLayout);
mainLayout->addWidget(_tracksContainer);
mainLayout->addLayout(startOffsetLayout);
mainLayout->addLayout(playbackDurationLayout);
mainLayout->addWidget(_waitForCompletion);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionPlayAudioEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_filePath->SetPath(_entryData->_filePath);
_volumeDB->SetValue(_entryData->_volumeDB);
_monitorTypes->setCurrentIndex(
static_cast<int>(_entryData->_monitorType));
for (int i = 0; i < 6; ++i) {
_tracks[i]->setChecked(_entryData->_audioMixers & (1u << i));
}
_tracksContainer->setVisible(_entryData->_monitorType !=
OBS_MONITORING_TYPE_MONITOR_ONLY);
_useStartOffset->setChecked(_entryData->_useStartOffset);
_startOffset->SetDuration(_entryData->_startOffset);
_startOffset->setEnabled(_entryData->_useStartOffset);
_useDuration->setChecked(_entryData->_useDuration);
_playbackDuration->SetDuration(_entryData->_playbackDuration);
_playbackDuration->setEnabled(_entryData->_useDuration);
_waitForCompletion->setChecked(_entryData->_waitForCompletion);
}
void MacroActionPlayAudioEdit::FilePathChanged(const QString &path)
{
GUARD_LOADING_AND_LOCK();
_entryData->_filePath = path.toStdString();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionPlayAudioEdit::VolumeDBChanged(
const NumberVariable<double> &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_volumeDB = value;
}
void MacroActionPlayAudioEdit::MonitorTypeChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_monitorType = static_cast<obs_monitoring_type>(value);
_tracksContainer->setVisible(value != OBS_MONITORING_TYPE_MONITOR_ONLY);
}
void MacroActionPlayAudioEdit::TrackChanged()
{
GUARD_LOADING_AND_LOCK();
uint32_t mixers = 0;
for (int i = 0; i < 6; ++i) {
if (_tracks[i]->isChecked()) {
mixers |= (1u << i);
}
}
_entryData->_audioMixers = mixers;
}
void MacroActionPlayAudioEdit::UseStartOffsetChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_useStartOffset = value;
_startOffset->setEnabled(_entryData->_useStartOffset);
}
void MacroActionPlayAudioEdit::StartOffsetChanged(const Duration &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_startOffset = value;
}
void MacroActionPlayAudioEdit::UseDurationChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_useDuration = value;
_playbackDuration->setEnabled(_entryData->_useDuration);
}
void MacroActionPlayAudioEdit::PlaybackDurationChanged(const Duration &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_playbackDuration = value;
}
void MacroActionPlayAudioEdit::WaitChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_waitForCompletion = value;
}
} // namespace advss

View File

@@ -0,0 +1,91 @@
#pragma once
#include "macro-action-edit.hpp"
#include "duration-control.hpp"
#include "file-selection.hpp"
#include "variable-spinbox.hpp"
#include <obs.hpp>
#include <array>
#include <QCheckBox>
#include <QComboBox>
#include <QWidget>
namespace advss {
class MacroActionPlayAudio : public MacroAction {
public:
MacroActionPlayAudio(Macro *m) : MacroAction(m) {}
bool PerformAction();
void LogAction() const;
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetShortDesc() const;
std::string GetId() const { return id; }
static std::shared_ptr<MacroAction> Create(Macro *m);
std::shared_ptr<MacroAction> Copy() const;
void ResolveVariablesToFixedValues();
StringVariable _filePath =
obs_module_text("AdvSceneSwitcher.enterPath");
DoubleVariable _volumeDB = 0.0;
obs_monitoring_type _monitorType = OBS_MONITORING_TYPE_MONITOR_ONLY;
uint32_t _audioMixers = 0x3F; // tracks 1-6, all on by default
bool _useStartOffset = false;
Duration _startOffset;
bool _useDuration = false;
Duration _playbackDuration;
bool _waitForCompletion = false;
static bool _registered;
static const std::string id;
};
class MacroActionPlayAudioEdit : public QWidget {
Q_OBJECT
public:
MacroActionPlayAudioEdit(
QWidget *parent,
std::shared_ptr<MacroActionPlayAudio> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionPlayAudioEdit(
parent, std::dynamic_pointer_cast<MacroActionPlayAudio>(
action));
}
private slots:
void FilePathChanged(const QString &path);
void VolumeDBChanged(const NumberVariable<double> &value);
void MonitorTypeChanged(int value);
void TrackChanged();
void UseStartOffsetChanged(int);
void StartOffsetChanged(const Duration &);
void UseDurationChanged(int);
void PlaybackDurationChanged(const Duration &);
void WaitChanged(int value);
signals:
void HeaderInfoChanged(const QString &);
private:
FileSelection *_filePath;
VariableDoubleSpinBox *_volumeDB;
QComboBox *_monitorTypes;
QWidget *_tracksContainer;
std::array<QCheckBox *, 6> _tracks;
QCheckBox *_useStartOffset;
DurationSelection *_startOffset;
QCheckBox *_useDuration;
DurationSelection *_playbackDuration;
QCheckBox *_waitForCompletion;
std::shared_ptr<MacroActionPlayAudio> _entryData;
bool _loading = true;
};
} // namespace advss

View File

@@ -47,20 +47,28 @@ getNextMacros(std::vector<MacroRef> &macros, MacroRef &lastRandomMacroRef,
bool MacroActionRandom::PerformAction()
{
if (_macros.size() == 0) {
SetTempVarValue("macro", "");
return true;
}
auto macros = getNextMacros(_macros, lastRandomMacro, _allowRepeat);
if (macros.size() == 0) {
SetTempVarValue("macro", "");
return true;
}
if (macros.size() == 1) {
lastRandomMacro = macros[0];
SetTempVarValue("macro", GetMacroName(macros[0].get()));
return RunMacroActions(macros[0].get());
}
srand((unsigned int)time(0));
size_t idx = std::rand() % (macros.size());
lastRandomMacro = macros[idx];
SetTempVarValue("macro", GetMacroName(macros[idx].get()));
return RunMacroActions(macros[idx].get());
}
@@ -85,6 +93,11 @@ bool MacroActionRandom::Load(obs_data_t *obj)
return true;
}
bool MacroActionRandom::PostLoad()
{
return MacroAction::PostLoad() && MultiMacroRefAction::PostLoad();
}
std::shared_ptr<MacroAction> MacroActionRandom::Create(Macro *m)
{
return std::make_shared<MacroActionRandom>(m);
@@ -95,6 +108,16 @@ std::shared_ptr<MacroAction> MacroActionRandom::Copy() const
return std::make_shared<MacroActionRandom>(*this);
}
void MacroActionRandom::SetupTempVars()
{
MacroAction::SetupTempVars();
AddTempvar(
"macro",
obs_module_text("AdvSceneSwitcher.tempVar.random.macro"),
obs_module_text(
"AdvSceneSwitcher.tempVar.random.macro.description"));
}
MacroActionRandomEdit::MacroActionRandomEdit(
QWidget *parent, std::shared_ptr<MacroActionRandom> entryData)
: QWidget(parent),

View File

@@ -15,6 +15,7 @@ public:
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;
@@ -22,7 +23,10 @@ public:
bool _allowRepeat = false;
private:
void SetupTempVars();
MacroRef lastRandomMacro;
static bool _registered;
static const std::string id;
};

View File

@@ -22,12 +22,18 @@ const static std::map<MacroActionRecord::Action, std::string> actionTypes = {
"AdvSceneSwitcher.action.recording.type.pause"},
{MacroActionRecord::Action::UNPAUSE,
"AdvSceneSwitcher.action.recording.type.unpause"},
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(28, 0, 0)
{MacroActionRecord::Action::SPLIT,
"AdvSceneSwitcher.action.recording.type.split"},
#endif
{MacroActionRecord::Action::FOLDER,
"AdvSceneSwitcher.action.recording.type.changeOutputFolder"},
{MacroActionRecord::Action::FILE_FORMAT,
"AdvSceneSwitcher.action.recording.type.changeOutputFileFormat"},
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(30, 2, 0)
{MacroActionRecord::Action::ADD_CHAPTER,
"AdvSceneSwitcher.action.recording.type.addChapter"},
#endif
};
bool MacroActionRecord::PerformAction()
@@ -87,6 +93,13 @@ bool MacroActionRecord::PerformAction()
}
break;
}
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(30, 2, 0)
case Action::ADD_CHAPTER:
if (!obs_frontend_recording_add_chapter(_chapterName.c_str())) {
blog(LOG_WARNING, "failed to add recoding chapter!");
}
break;
#endif
default:
break;
}
@@ -110,6 +123,7 @@ bool MacroActionRecord::Save(obs_data_t *obj) const
obs_data_set_int(obj, "action", static_cast<int>(_action));
_folder.Save(obj, "folder");
_fileFormat.Save(obj, "format");
_chapterName.Save(obj, "chapterName");
return true;
}
@@ -119,6 +133,7 @@ bool MacroActionRecord::Load(obs_data_t *obj)
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
_folder.Load(obj, "folder");
_fileFormat.Load(obj, "format");
_chapterName.Load(obj, "chapterName");
return true;
}
@@ -136,6 +151,7 @@ void MacroActionRecord::ResolveVariablesToFixedValues()
{
_folder.ResolveVariables();
_fileFormat.ResolveVariables();
_chapterName.ResolveVariables();
}
static inline void populateActionSelection(QComboBox *list)
@@ -154,7 +170,8 @@ MacroActionRecordEdit::MacroActionRecordEdit(
_splitHint(new QLabel(obs_module_text(
"AdvSceneSwitcher.action.recording.split.hint"))),
_recordFolder(new FileSelection(FileSelection::Type::FOLDER, this)),
_recordFileFormat(new VariableLineEdit(this))
_recordFileFormat(new VariableLineEdit(this)),
_chapterName(new VariableLineEdit(this))
{
populateActionSelection(_actions);
@@ -164,6 +181,8 @@ MacroActionRecordEdit::MacroActionRecordEdit(
this, SLOT(FolderChanged(const QString &)));
QWidget::connect(_recordFileFormat, SIGNAL(editingFinished()), this,
SLOT(FormatStringChanged()));
QWidget::connect(_chapterName, SIGNAL(editingFinished()), this,
SLOT(ChapterNameChanged()));
auto mainLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.recording.entry"),
@@ -172,7 +191,8 @@ MacroActionRecordEdit::MacroActionRecordEdit(
{"{{pauseHint}}", _pauseHint},
{"{{splitHint}}", _splitHint},
{"{{recordFolder}}", _recordFolder},
{"{{recordFileFormat}}", _recordFileFormat}});
{"{{recordFileFormat}}", _recordFileFormat},
{"{{chapterName}}", _chapterName}});
setLayout(mainLayout);
_entryData = entryData;
@@ -188,6 +208,7 @@ void MacroActionRecordEdit::UpdateEntryData()
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_recordFolder->SetPath(_entryData->_folder);
_recordFileFormat->setText(_entryData->_fileFormat);
_chapterName->setText(_entryData->_chapterName);
SetWidgetVisibility();
}
@@ -209,6 +230,12 @@ void MacroActionRecordEdit::FormatStringChanged()
_entryData->_fileFormat = _recordFileFormat->text().toStdString();
}
void MacroActionRecordEdit::ChapterNameChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_chapterName = _chapterName->text().toStdString();
}
void MacroActionRecordEdit::SetWidgetVisibility()
{
_pauseHint->setVisible(isPauseAction(_entryData->_action));
@@ -218,6 +245,8 @@ void MacroActionRecordEdit::SetWidgetVisibility()
MacroActionRecord::Action::FOLDER);
_recordFileFormat->setVisible(_entryData->_action ==
MacroActionRecord::Action::FILE_FORMAT);
_chapterName->setVisible(_entryData->_action ==
MacroActionRecord::Action::ADD_CHAPTER);
}
void MacroActionRecordEdit::ActionChanged(int value)

View File

@@ -29,11 +29,13 @@ public:
SPLIT,
FOLDER,
FILE_FORMAT,
ADD_CHAPTER,
};
Action _action = Action::STOP;
StringVariable _folder = QDir::homePath().toStdString() + "/Videos";
StringVariable _fileFormat = "%CCYY-%MM-%DD %hh-%mm-%ss";
StringVariable _chapterName = "";
private:
static bool _registered;
@@ -60,6 +62,7 @@ private slots:
void ActionChanged(int value);
void FolderChanged(const QString &);
void FormatStringChanged();
void ChapterNameChanged();
protected:
QComboBox *_actions;
@@ -67,6 +70,7 @@ protected:
QLabel *_splitHint;
FileSelection *_recordFolder;
VariableLineEdit *_recordFileFormat;
VariableLineEdit *_chapterName;
std::shared_ptr<MacroActionRecord> _entryData;
private:

View File

@@ -15,9 +15,20 @@ bool MacroActionRun::_registered = MacroActionFactory::Register(
bool MacroActionRun::PerformAction()
{
if (_wait) {
_procConfig.StartProcessAndWait(_timeout.Milliseconds());
SetTempVarValues();
// Snapshot config before releasing the lock for the blocking wait
auto procConfig = _procConfig;
const auto timeout = _timeout.Milliseconds();
{
SuspendLock suspendLock(*this);
procConfig.StartProcessAndWait(timeout);
}
SetTempVarValue("process.id", procConfig.GetProcessId());
SetTempVarValue("process.exitCode",
procConfig.GetProcessExitCode());
SetTempVarValue("process.stream.output",
procConfig.GetProcessOutputStream());
SetTempVarValue("process.stream.error",
procConfig.GetProcessErrorStream());
return true;
}

View File

@@ -82,13 +82,22 @@ void MacroActionSequence::ResolveVariablesToFixedValues()
_resetIndex.ResolveVariables();
}
void MacroActionSequence::SetAction(Action action)
{
_action = action;
SetupTempVars();
}
bool MacroActionSequence::RunSequence()
{
if (_macros.size() == 0) {
SetTempVarValue("macro", "");
return true;
}
auto macro = GetNextMacro().GetMacro();
SetTempVarValue("macro", GetMacroName(macro.get()));
if (!macro.get()) {
return true;
}
@@ -96,18 +105,21 @@ bool MacroActionSequence::RunSequence()
return RunMacroActions(macro.get());
}
bool MacroActionSequence::SetSequenceIndex() const
bool MacroActionSequence::SetSequenceIndex()
{
auto macro = _macro.GetMacro();
if (!macro) {
SetTempVarValue("nextMacro", "");
return true;
}
auto actions = GetMacroActions(macro.get());
if (!actions) {
SetTempVarValue("nextMacro", "");
return true;
}
std::string nextMacroName;
for (const auto &action : *actions) {
if (action->GetId() != id) {
continue;
@@ -121,10 +133,34 @@ bool MacroActionSequence::SetSequenceIndex() const
// -2 is needed since the _lastIndex starts at -1 and the reset
// index starts at 1
sequenceAction->_lastIdx = _resetIndex - 2;
nextMacroName = GetMacroName(
sequenceAction->GetNextMacro(false).GetMacro().get());
}
SetTempVarValue("nextMacro", nextMacroName);
return true;
}
void MacroActionSequence::SetupTempVars()
{
MacroAction::SetupTempVars();
if (_action == Action::RUN_SEQUENCE) {
AddTempvar(
"macro",
obs_module_text(
"AdvSceneSwitcher.tempVar.sequence.macro"),
obs_module_text(
"AdvSceneSwitcher.tempVar.sequence.macro.description"));
} else if (_action == Action::SET_INDEX) {
AddTempvar(
"nextMacro",
obs_module_text(
"AdvSceneSwitcher.tempVar.sequence.nextMacro"),
obs_module_text(
"AdvSceneSwitcher.tempVar.sequence.nextMacro.description"));
}
}
bool MacroActionSequence::PerformAction()
{
if (_action == Action::RUN_SEQUENCE) {
@@ -162,7 +198,7 @@ bool MacroActionSequence::Load(obs_data_t *obj)
LoadMacroList(obj, _macros);
_restart = obs_data_get_bool(obj, "restart");
_macro.Load(obj);
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
SetAction(static_cast<Action>(obs_data_get_int(obj, "action")));
_resetIndex.Load(obj, "resetIndex");
return true;
}
@@ -256,7 +292,7 @@ void MacroActionSequenceEdit::UpdateEntryData()
_macroList->SetContent(_entryData->_macros);
_restart->setChecked(_entryData->_restart);
_resetIndex->SetValue(_entryData->_resetIndex);
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_actions->setCurrentIndex(static_cast<int>(_entryData->GetAction()));
_macros->SetCurrentMacro(_entryData->_macro);
SetWidgetVisibility();
adjustSize();
@@ -361,7 +397,7 @@ void MacroActionSequenceEdit::UpdateStatusLine()
void MacroActionSequenceEdit::ActionChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_action = static_cast<MacroActionSequence::Action>(value);
_entryData->SetAction(static_cast<MacroActionSequence::Action>(value));
SetWidgetVisibility();
}
@@ -385,10 +421,10 @@ void MacroActionSequenceEdit::SetWidgetVisibility()
ClearLayout(_layout);
const auto action = _entryData->GetAction();
PlaceWidgets(
obs_module_text(
_entryData->_action ==
MacroActionSequence::Action::RUN_SEQUENCE
action == MacroActionSequence::Action::RUN_SEQUENCE
? "AdvSceneSwitcher.action.sequence.entry.run"
: "AdvSceneSwitcher.action.sequence.entry.setIndex"),
_layout,
@@ -396,15 +432,14 @@ void MacroActionSequenceEdit::SetWidgetVisibility()
{"{{macros}}", _macros},
{"{{index}}", _resetIndex}});
_macroList->setVisible(_entryData->_action ==
_macroList->setVisible(action ==
MacroActionSequence::Action::RUN_SEQUENCE);
_restart->setVisible(_entryData->_action ==
_restart->setVisible(action ==
MacroActionSequence::Action::RUN_SEQUENCE);
_statusLine->setVisible(_entryData->_action ==
_statusLine->setVisible(action ==
MacroActionSequence::Action::RUN_SEQUENCE);
_macros->setVisible(_entryData->_action ==
MacroActionSequence::Action::SET_INDEX);
_resetIndex->setVisible(_entryData->_action ==
_macros->setVisible(action == MacroActionSequence::Action::SET_INDEX);
_resetIndex->setVisible(action ==
MacroActionSequence::Action::SET_INDEX);
adjustSize();

View File

@@ -34,7 +34,10 @@ public:
RUN_SEQUENCE,
SET_INDEX,
};
Action _action = Action::RUN_SEQUENCE;
Action GetAction() const { return _action; }
void SetAction(Action);
bool _restart = true;
IntVariable _resetIndex = 1;
@@ -43,7 +46,11 @@ public:
private:
bool RunSequence();
bool SetSequenceIndex() const;
bool SetSequenceIndex();
void SetupTempVars();
Action _action = Action::RUN_SEQUENCE;
static bool _registered;
static const std::string id;

View File

@@ -0,0 +1,292 @@
#include "macro-action-source-interaction.hpp"
#include "source-interaction-recorder.hpp"
#include "layout-helpers.hpp"
#include "selection-helpers.hpp"
#include "sync-helpers.hpp"
#include "log-helper.hpp"
#include <obs.hpp>
#include <obs-interaction.h>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QVBoxLayout>
namespace advss {
const std::string MacroActionSourceInteraction::id = "source_interaction";
bool MacroActionSourceInteraction::_registered = MacroActionFactory::Register(
MacroActionSourceInteraction::id,
{MacroActionSourceInteraction::Create,
MacroActionSourceInteractionEdit::Create,
"AdvSceneSwitcher.action.sourceInteraction"});
bool MacroActionSourceInteraction::PerformAction()
{
OBSSourceAutoRelease source =
obs_weak_source_get_source(_source.GetSource());
if (!source) {
blog(LOG_WARNING, "source interaction: source not found");
return true;
}
uint32_t flags = obs_source_get_output_flags(source);
if (!(flags & OBS_SOURCE_INTERACTION)) {
blog(LOG_WARNING,
"source interaction: source \"%s\" does not support interaction",
obs_source_get_name(source));
return true;
}
for (const auto &step : _steps) {
PerformInteractionStep(source, step);
}
return true;
}
void MacroActionSourceInteraction::LogAction() const
{
ablog(LOG_INFO, "performed source interaction on \"%s\" (%d steps)",
_source.ToString(true).c_str(), (int)_steps.size());
}
bool MacroActionSourceInteraction::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
_source.Save(obj);
OBSDataArrayAutoRelease arr = obs_data_array_create();
for (const auto &step : _steps) {
OBSDataAutoRelease stepObj = obs_data_create();
step.Save(stepObj);
obs_data_array_push_back(arr, stepObj);
}
obs_data_set_array(obj, "steps", arr);
return true;
}
bool MacroActionSourceInteraction::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_source.Load(obj);
_steps.clear();
OBSDataArrayAutoRelease arr = obs_data_get_array(obj, "steps");
size_t count = obs_data_array_count(arr);
for (size_t i = 0; i < count; i++) {
OBSDataAutoRelease stepObj = obs_data_array_item(arr, i);
SourceInteractionStep step;
step.Load(stepObj);
_steps.push_back(step);
}
return true;
}
std::string MacroActionSourceInteraction::GetShortDesc() const
{
return _source.ToString();
}
void MacroActionSourceInteraction::ResolveVariablesToFixedValues()
{
for (auto &step : _steps) {
step.x.ResolveVariables();
step.y.ResolveVariables();
step.clickCount.ResolveVariables();
step.wheelDeltaX.ResolveVariables();
step.wheelDeltaY.ResolveVariables();
step.nativeVkey.ResolveVariables();
step.text.ResolveVariables();
step.waitMs.ResolveVariables();
}
}
std::shared_ptr<MacroAction> MacroActionSourceInteraction::Create(Macro *m)
{
return std::make_shared<MacroActionSourceInteraction>(m);
}
std::shared_ptr<MacroAction> MacroActionSourceInteraction::Copy() const
{
return std::make_shared<MacroActionSourceInteraction>(*this);
}
static QStringList getInteractableSourceNames()
{
QStringList names;
obs_enum_sources(
[](void *param, obs_source_t *source) -> bool {
uint32_t flags = obs_source_get_output_flags(source);
if (flags & OBS_SOURCE_INTERACTION) {
auto list = static_cast<QStringList *>(param);
const char *name = obs_source_get_name(source);
if (name) {
list->append(QString(name));
}
}
return true;
},
&names);
names.sort();
return names;
}
MacroActionSourceInteractionEdit::MacroActionSourceInteractionEdit(
QWidget *parent,
std::shared_ptr<MacroActionSourceInteraction> entryData)
: QWidget(parent),
_sources(new SourceSelectionWidget(this, getInteractableSourceNames,
true)),
_stepList(new SourceInteractionStepList(this)),
_recordButton(new QPushButton(obs_module_text(
"AdvSceneSwitcher.action.sourceInteraction.record"))),
_noSelectionLabel(new QLabel(obs_module_text(
"AdvSceneSwitcher.action.sourceInteraction.noSelection")))
{
_stepList->AddControlWidget(_recordButton);
auto sourceRow = new QHBoxLayout;
sourceRow->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.action.sourceInteraction.source")));
sourceRow->addWidget(_sources);
sourceRow->addStretch();
_noSelectionLabel->setAlignment(Qt::AlignCenter);
_noSelectionLabel->hide();
auto mainLayout = new QVBoxLayout(this);
mainLayout->addLayout(sourceRow);
mainLayout->addWidget(_stepList);
mainLayout->addWidget(_noSelectionLabel);
setLayout(mainLayout);
connect(_sources, SIGNAL(SourceChanged(const SourceSelection &)), this,
SLOT(SourceChanged(const SourceSelection &)));
connect(_stepList, &SourceInteractionStepList::StepsChanged, this,
&MacroActionSourceInteractionEdit::OnStepsChanged);
connect(_stepList, &SourceInteractionStepList::RowSelected, this,
&MacroActionSourceInteractionEdit::SetCurrentStepEditor);
connect(_recordButton, &QPushButton::clicked, this,
&MacroActionSourceInteractionEdit::OpenRecorder);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionSourceInteractionEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_sources->SetSource(_entryData->_source);
_stepList->SetSteps(_entryData->_steps);
_noSelectionLabel->setVisible(_stepList->count() > 0);
}
void MacroActionSourceInteractionEdit::SourceChanged(
const SourceSelection &source)
{
GUARD_LOADING_AND_LOCK();
_entryData->_source = source;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionSourceInteractionEdit::OnStepsChanged(
const std::vector<SourceInteractionStep> &steps)
{
GUARD_LOADING_AND_LOCK();
_entryData->_steps = steps;
}
void MacroActionSourceInteractionEdit::StepChanged(
const SourceInteractionStep &step)
{
GUARD_LOADING_AND_LOCK();
int row = _stepList->CurrentRow();
if (row < 0 || row >= (int)_entryData->_steps.size()) {
return;
}
_entryData->_steps[row] = step;
_stepList->UpdateStep(row, step);
}
void MacroActionSourceInteractionEdit::OpenRecorder()
{
if (!_entryData) {
return;
}
OBSSourceAutoRelease source =
obs_weak_source_get_source(_entryData->_source.GetSource());
if (!source) {
QMessageBox::warning(
this,
obs_module_text(
"AdvSceneSwitcher.action.sourceInteraction.record.title"),
obs_module_text(
"AdvSceneSwitcher.action.sourceInteraction.record.invalidSource"));
return;
}
auto dlg = new SourceInteractionRecorder(
window(), _entryData->_source.GetSource());
connect(dlg, &SourceInteractionRecorder::StepsRecorded, this,
&MacroActionSourceInteractionEdit::AcceptRecorded);
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->show();
}
void MacroActionSourceInteractionEdit::AcceptRecorded(
const std::vector<SourceInteractionStep> &steps)
{
if (!_entryData) {
return;
}
{
auto lock = _entryData->Lock();
_entryData->_steps.insert(_entryData->_steps.end(),
steps.begin(), steps.end());
}
_stepList->SetSteps(_entryData->_steps);
}
void MacroActionSourceInteractionEdit::showEvent(QShowEvent *event)
{
const QSignalBlocker b(this);
UpdateEntryData();
}
void MacroActionSourceInteractionEdit::SetCurrentStepEditor(int row)
{
if (_stepEditor) {
delete _stepEditor;
_stepEditor = nullptr;
}
if (!_entryData || row < 0 || row >= (int)_entryData->_steps.size()) {
_noSelectionLabel->setVisible(_stepList->count() > 0);
return;
}
_noSelectionLabel->setVisible(false);
_stepEditor =
new SourceInteractionStepEdit(this, _entryData->_steps[row]);
static_cast<QVBoxLayout *>(layout())->addWidget(_stepEditor);
connect(_stepEditor, &SourceInteractionStepEdit::StepChanged, this,
&MacroActionSourceInteractionEdit::StepChanged);
adjustSize();
updateGeometry();
}
} // namespace advss

View File

@@ -0,0 +1,81 @@
#pragma once
#include "macro-action-edit.hpp"
#include "source-selection.hpp"
#include "source-interaction-step.hpp"
#include "source-interaction-step-list.hpp"
#include "source-interaction-step-edit.hpp"
#include <QLabel>
#include <QPushButton>
namespace advss {
class MacroActionSourceInteraction : public MacroAction {
public:
MacroActionSourceInteraction(Macro *m) : MacroAction(m) {}
static std::shared_ptr<MacroAction> Create(Macro *m);
std::shared_ptr<MacroAction> Copy() const;
bool PerformAction();
void LogAction() const;
void ResolveVariablesToFixedValues() override;
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetId() const { return id; }
std::string GetShortDesc() const;
SourceSelection _source;
std::vector<SourceInteractionStep> _steps;
private:
static bool _registered;
static const std::string id;
};
class MacroActionSourceInteractionEdit : public QWidget {
Q_OBJECT
public:
MacroActionSourceInteractionEdit(
QWidget *parent,
std::shared_ptr<MacroActionSourceInteraction> entryData =
nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionSourceInteractionEdit(
parent,
std::dynamic_pointer_cast<MacroActionSourceInteraction>(
action));
}
private slots:
void SourceChanged(const SourceSelection &);
void OnStepsChanged(const std::vector<SourceInteractionStep> &);
void StepChanged(const SourceInteractionStep &);
void OpenRecorder();
void AcceptRecorded(const std::vector<SourceInteractionStep> &);
signals:
void HeaderInfoChanged(const QString &);
protected:
void showEvent(QShowEvent *event) override;
private:
void SetCurrentStepEditor(int row);
SourceSelectionWidget *_sources;
SourceInteractionStepList *_stepList;
QPushButton *_recordButton;
SourceInteractionStepEdit *_stepEditor = nullptr;
QLabel *_noSelectionLabel;
std::shared_ptr<MacroActionSourceInteraction> _entryData;
bool _loading = true;
};
} // namespace advss

View File

@@ -13,6 +13,15 @@ namespace advss {
const std::string MacroActionSource::id = "source";
std::vector<TempVariableRef> MacroActionSource::GetTempVarRefs() const
{
auto refs = MacroSegment::GetTempVarRefs();
if (_tempVar.HasValidID()) {
refs.push_back(_tempVar);
}
return refs;
}
bool MacroActionSource::_registered = MacroActionFactory::Register(
MacroActionSource::id,
{MacroActionSource::Create, MacroActionSourceEdit::Create,
@@ -45,6 +54,10 @@ const static std::map<MacroActionSource::Action, std::string> actionTypes = {
"AdvSceneSwitcher.action.source.type.closeFilterDialog"},
{MacroActionSource::Action::CLOSE_PROPERTIES_DIALOG,
"AdvSceneSwitcher.action.source.type.closePropertiesDialog"},
{MacroActionSource::Action::GET_SETTING,
"AdvSceneSwitcher.action.source.type.getSetting"},
{MacroActionSource::Action::GET_SETTINGS,
"AdvSceneSwitcher.action.source.type.getSettings"},
};
const static std::map<obs_deinterlace_mode, std::string> deinterlaceModes = {
@@ -273,6 +286,22 @@ bool MacroActionSource::PerformAction()
"OBSBasicProperties");
});
break;
case Action::GET_SETTING: {
const auto value =
GetSourceSettingValue(_source.GetSource(), _setting);
if (value) {
SetTempVarValue("setting", *value);
}
break;
}
case Action::GET_SETTINGS: {
const auto settings =
GetSourceSettings(_source.GetSource(), true);
if (settings) {
SetTempVarValue("settings", *settings);
}
break;
}
default:
break;
}
@@ -360,6 +389,31 @@ void MacroActionSource::ResolveVariablesToFixedValues()
_manualSettingValue.ResolveVariables();
}
void MacroActionSource::SetupTempVars()
{
MacroAction::SetupTempVars();
switch (_action) {
case Action::GET_SETTING:
AddTempvar("setting",
obs_module_text(
"AdvSceneSwitcher.tempVar.source.setting"));
break;
case Action::GET_SETTINGS:
AddTempvar("settings",
obs_module_text(
"AdvSceneSwitcher.tempVar.source.settings"));
break;
default:
break;
}
}
void MacroActionSource::SetAction(Action action)
{
_action = action;
SetupTempVars();
}
static inline void populateActionSelection(QComboBox *list)
{
for (auto &[actionType, name] : actionTypes) {
@@ -502,7 +556,7 @@ void MacroActionSourceEdit::UpdateEntryData()
const auto weakSource = _entryData->_source.GetSource();
_settingsButtons->SetSelection(weakSource, _entryData->_button);
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_actions->setCurrentIndex(static_cast<int>(_entryData->GetAction()));
_sources->SetSource(_entryData->_source);
_sourceSettings->SetSelection(weakSource, _entryData->_setting);
_settingsString->setPlainText(_entryData->_settingsString);
@@ -537,7 +591,7 @@ void MacroActionSourceEdit::SourceChanged(const SourceSelection &source)
void MacroActionSourceEdit::ActionChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_action = static_cast<MacroActionSource::Action>(value);
_entryData->SetAction(static_cast<MacroActionSource::Action>(value));
SetWidgetVisibility();
}
@@ -607,6 +661,7 @@ void MacroActionSourceEdit::SelectionChanged(const TempVariableRef &var)
{
GUARD_LOADING_AND_LOCK();
_entryData->_tempVar = var;
IncrementTempVarInUseGeneration();
}
void MacroActionSourceEdit::SettingsInputMethodChanged(int idx)
@@ -664,19 +719,29 @@ static QString GetIndividualListEntryName()
void MacroActionSourceEdit::SetWidgetVisibility()
{
const auto action = _entryData->GetAction();
const bool isSetSettings = action ==
MacroActionSource::Action::SETTINGS;
const bool isGetSetting = action ==
MacroActionSource::Action::GET_SETTING;
const bool isGetSettings = action ==
MacroActionSource::Action::GET_SETTINGS;
SetLayoutVisible(_settingsLayout,
_entryData->_action ==
MacroActionSource::Action::SETTINGS);
isSetSettings || isGetSetting || isGetSettings);
_settingsInputMethods->setVisible(isSetSettings);
_sourceSettings->setVisible(
_entryData->_action == MacroActionSource::Action::SETTINGS &&
_entryData->_settingsInputMethod !=
MacroActionSource::SettingsInputMethod::JSON_STRING);
(isSetSettings &&
_entryData->_settingsInputMethod !=
MacroActionSource::SettingsInputMethod::JSON_STRING) ||
isGetSetting);
_settingsString->setVisible(
_entryData->_action == MacroActionSource::Action::SETTINGS &&
isSetSettings &&
_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::JSON_STRING);
_getSettings->setVisible(
_entryData->_action == MacroActionSource::Action::SETTINGS &&
isSetSettings &&
_entryData->_settingsInputMethod !=
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_TEMPVAR);
@@ -685,13 +750,12 @@ void MacroActionSourceEdit::SetWidgetVisibility()
GetIndividualListEntryName(),
_entryData->_setting.IsList());
_tempVars->setVisible(_entryData->_action ==
MacroActionSource::Action::SETTINGS &&
_tempVars->setVisible(isSetSettings &&
_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_TEMPVAR);
if (_entryData->_action == MacroActionSource::Action::SETTINGS &&
if (isSetSettings &&
(_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::INDIVIDUAL_MANUAL ||
_entryData->_settingsInputMethod ==
@@ -704,35 +768,33 @@ void MacroActionSourceEdit::SetWidgetVisibility()
_manualSettingValue->hide();
}
const bool showWarning =
_entryData->_action == MacroActionSource::Action::ENABLE ||
_entryData->_action == MacroActionSource::Action::DISABLE;
const bool showWarning = action == MacroActionSource::Action::ENABLE ||
action == MacroActionSource::Action::DISABLE;
_warning->setVisible(showWarning);
_settingsButtons->setVisible(
_entryData->_action ==
MacroActionSource::Action::SETTINGS_BUTTON);
action == MacroActionSource::Action::SETTINGS_BUTTON);
_deinterlaceMode->setVisible(
_entryData->_action ==
MacroActionSource::Action::DEINTERLACE_MODE);
action == MacroActionSource::Action::DEINTERLACE_MODE);
_deinterlaceOrder->setVisible(
_entryData->_action ==
MacroActionSource::Action::DEINTERLACE_FIELD_ORDER);
action == MacroActionSource::Action::DEINTERLACE_FIELD_ORDER);
_refreshSettingSelection->setVisible(
(_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_MANUAL ||
_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_LIST_ENTRY) &&
((isSetSettings &&
(_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_MANUAL ||
_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_LIST_ENTRY)) ||
isGetSetting) &&
_entryData->_source.GetType() ==
SourceSelection::Type::VARIABLE);
_acceptDialog->setVisible(
_entryData->_action ==
MacroActionSource::Action::CLOSE_FILTER_DIALOG ||
_entryData->_action ==
MacroActionSource::Action::CLOSE_PROPERTIES_DIALOG);
action == MacroActionSource::Action::CLOSE_FILTER_DIALOG ||
action == MacroActionSource::Action::CLOSE_PROPERTIES_DIALOG);
emit ShowVariableMappings(isGetSetting || isGetSettings);
adjustSize();
updateGeometry();

View File

@@ -15,26 +15,22 @@ namespace advss {
class MacroActionSource : public MacroAction {
public:
MacroActionSource(Macro *m) : MacroAction(m) {}
bool PerformAction();
void LogAction() const;
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetShortDesc() const;
std::string GetId() const { return id; };
static std::shared_ptr<MacroAction> Create(Macro *m);
std::shared_ptr<MacroAction> Copy() const;
void ResolveVariablesToFixedValues();
SourceSelection _source;
SourceSettingButton _button;
StringVariable _settingsString = "";
StringVariable _manualSettingValue = "";
obs_deinterlace_mode _deinterlaceMode = OBS_DEINTERLACE_MODE_DISABLE;
obs_deinterlace_field_order _deinterlaceOrder =
OBS_DEINTERLACE_FIELD_ORDER_TOP;
TempVariableRef _tempVar;
SourceSetting _setting;
bool _acceptDialog = false;
bool PerformAction();
void LogAction() const;
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetShortDesc() const;
std::string GetId() const { return id; };
void ResolveVariablesToFixedValues();
void SetupTempVars();
std::vector<TempVariableRef> GetTempVarRefs() const;
enum class Action {
ENABLE,
@@ -50,8 +46,23 @@ public:
CLOSE_INTERACTION_DIALOG,
CLOSE_FILTER_DIALOG,
CLOSE_PROPERTIES_DIALOG,
GET_SETTING,
GET_SETTINGS,
};
Action _action = Action::SETTINGS;
void SetAction(Action);
Action GetAction() const { return _action; }
SourceSelection _source;
SourceSettingButton _button;
StringVariable _settingsString = "";
StringVariable _manualSettingValue = "";
obs_deinterlace_mode _deinterlaceMode = OBS_DEINTERLACE_MODE_DISABLE;
obs_deinterlace_field_order _deinterlaceOrder =
OBS_DEINTERLACE_FIELD_ORDER_TOP;
TempVariableRef _tempVar;
SourceSetting _setting;
bool _acceptDialog = false;
enum class SettingsInputMethod {
INDIVIDUAL_MANUAL,
@@ -63,6 +74,8 @@ public:
SettingsInputMethod::INDIVIDUAL_MANUAL;
private:
Action _action = Action::SETTINGS;
static bool _registered;
static const std::string id;
};
@@ -100,6 +113,7 @@ private slots:
signals:
void HeaderInfoChanged(const QString &);
void ShowVariableMappings(bool show);
private:
void SetWidgetVisibility();

View File

@@ -4,6 +4,7 @@
#include "ui-helpers.hpp"
#include <obs-frontend-api.h>
#include <obs.hpp>
#include <util/config-file.h>
namespace advss {
@@ -183,6 +184,8 @@ MacroActionStreamEdit::MacroActionStreamEdit(
_keyFrameInterval(new VariableSpinBox()),
_stringValue(new VariableLineEdit(this)),
_showPassword(new QPushButton()),
_getCurrentValue(new QPushButton(obs_module_text(
"AdvSceneSwitcher.action.streaming.getCurrentValue"))),
_layout(new QHBoxLayout())
{
_keyFrameInterval->setMinimum(0);
@@ -208,13 +211,17 @@ MacroActionStreamEdit::MacroActionStreamEdit(
SLOT(ShowPassword()));
QWidget::connect(_showPassword, SIGNAL(released()), this,
SLOT(HidePassword()));
QWidget::connect(_getCurrentValue, SIGNAL(clicked()), this,
SLOT(GetCurrentValueClicked()));
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.streaming.entry"),
_layout,
{{"{{actions}}", _actions},
{"{{keyFrameInterval}}", _keyFrameInterval},
{"{{stringValue}}", _stringValue},
{"{{showPassword}}", _showPassword}});
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.action.streaming.layout"),
_layout,
{{"{{actions}}", _actions},
{"{{keyFrameInterval}}", _keyFrameInterval},
{"{{stringValue}}", _stringValue},
{"{{showPassword}}", _showPassword},
{"{{getCurrentValue}}", _getCurrentValue}});
setLayout(_layout);
_entryData = entryData;
@@ -291,6 +298,62 @@ void MacroActionStreamEdit::SetWidgetVisibility()
_stringValue->setEchoMode(QLineEdit::Normal);
_showPassword->hide();
}
_getCurrentValue->setVisible(
action == MacroActionStream::Action::KEYFRAME_INTERVAL ||
action == MacroActionStream::Action::SERVER ||
action == MacroActionStream::Action::STREAM_KEY ||
action == MacroActionStream::Action::USERNAME ||
action == MacroActionStream::Action::PASSWORD);
}
void MacroActionStreamEdit::GetCurrentValueClicked()
{
if (!_entryData) {
return;
}
switch (_entryData->_action) {
case MacroActionStream::Action::KEYFRAME_INTERVAL: {
const auto configPath =
GetPathInProfileDir("streamEncoder.json");
OBSDataAutoRelease settings =
obs_data_create_from_json_file_safe(configPath.c_str(),
"bak");
if (!settings) {
break;
}
_keyFrameInterval->SetFixedValue(
(int)obs_data_get_int(settings, "keyint_sec"));
break;
}
case MacroActionStream::Action::SERVER:
case MacroActionStream::Action::STREAM_KEY:
case MacroActionStream::Action::USERNAME:
case MacroActionStream::Action::PASSWORD: {
static const std::map<MacroActionStream::Action, const char *>
settingsKeys = {
{MacroActionStream::Action::SERVER, "server"},
{MacroActionStream::Action::STREAM_KEY, "key"},
{MacroActionStream::Action::USERNAME,
"username"},
{MacroActionStream::Action::PASSWORD,
"password"},
};
auto service = obs_frontend_get_streaming_service();
OBSDataAutoRelease settings = obs_service_get_settings(service);
if (!settings) {
obs_service_release(service);
break;
}
const char *val = obs_data_get_string(
settings, settingsKeys.at(_entryData->_action));
if (val) {
_stringValue->setText(QString(val));
}
break;
}
default:
break;
}
}
void MacroActionStreamEdit::ActionChanged(int value)

View File

@@ -67,12 +67,14 @@ private slots:
void StringValueChanged();
void ShowPassword();
void HidePassword();
void GetCurrentValueClicked();
protected:
QComboBox *_actions;
VariableSpinBox *_keyFrameInterval;
VariableLineEdit *_stringValue;
QPushButton *_showPassword;
QPushButton *_getCurrentValue;
std::shared_ptr<MacroActionStream> _entryData;
private:

View File

@@ -3,6 +3,7 @@
#include "transition-helpers.hpp"
#include <obs-frontend-api.h>
#include <algorithm>
namespace advss {
@@ -22,6 +23,10 @@ const static std::map<MacroActionTransition::Type, std::string> actionTypes = {
"AdvSceneSwitcher.action.transition.type.sourceShow"},
{MacroActionTransition::Type::SOURCE_HIDE,
"AdvSceneSwitcher.action.transition.type.sourceHide"},
{MacroActionTransition::Type::TBAR,
"AdvSceneSwitcher.action.transition.type.tbar"},
{MacroActionTransition::Type::RELEASE_TBAR,
"AdvSceneSwitcher.action.transition.type.releaseTbar"},
};
void MacroActionTransition::SetSceneTransition()
@@ -76,6 +81,13 @@ static void obs_sceneitem_set_transition_duration(obs_sceneitem_t *item,
}
#endif
void MacroActionTransition::SetTbarPosition()
{
const double percent = std::clamp(_tbarPosition.GetValue(), 0.0, 100.0);
const int tbarValue = (int)std::round(percent / 100.0 * 1023.0);
obs_frontend_set_tbar_position(tbarValue);
}
void MacroActionTransition::SetSourceTransition(bool show)
{
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
@@ -110,6 +122,12 @@ bool MacroActionTransition::PerformAction()
case Type::SOURCE_HIDE:
SetSourceTransition(false);
break;
case Type::TBAR:
SetTbarPosition();
break;
case Type::RELEASE_TBAR:
obs_frontend_release_tbar();
break;
}
return true;
}
@@ -119,22 +137,29 @@ void MacroActionTransition::LogAction() const
std::string msgBegin;
switch (_type) {
case Type::SCENE:
msgBegin += "set scene transition";
msgBegin = "set scene transition";
break;
case Type::SCENE_OVERRIDE:
msgBegin += "set scene override transition of " +
_scene.ToString(true);
msgBegin = "set scene override transition of " +
_scene.ToString(true);
break;
case Type::SOURCE_SHOW:
msgBegin += "set source show transition of " +
_source.ToString(true) + " on scene " +
_scene.ToString(true);
msgBegin = "set source show transition of " +
_source.ToString(true) + " on scene " +
_scene.ToString(true);
break;
case Type::SOURCE_HIDE:
msgBegin += "set source hide transition of " +
_source.ToString(true) + " on scene " +
_scene.ToString(true);
msgBegin = "set source hide transition of " +
_source.ToString(true) + " on scene " +
_scene.ToString(true);
break;
case Type::TBAR:
ablog(LOG_INFO, "set T-Bar position to %.2f%%",
_tbarPosition.GetValue());
return;
case Type::RELEASE_TBAR:
ablog(LOG_INFO, "release T-Bar");
return;
}
if (_setDuration) {
ablog(LOG_INFO, "%s duration to %s", msgBegin.c_str(),
@@ -156,6 +181,7 @@ bool MacroActionTransition::Save(obs_data_t *obj) const
_transition.Save(obj);
obs_data_set_bool(obj, "setDuration", _setDuration);
obs_data_set_bool(obj, "setType", _setTransitionType);
_tbarPosition.Save(obj, "tbarPosition");
return true;
}
@@ -169,23 +195,25 @@ bool MacroActionTransition::Load(obs_data_t *obj)
_transition.Load(obj);
_setDuration = obs_data_get_bool(obj, "setDuration");
_setTransitionType = obs_data_get_bool(obj, "setType");
_tbarPosition.Load(obj, "tbarPosition");
return true;
}
std::string MacroActionTransition::GetShortDesc() const
{
std::string msgBegin;
switch (_type) {
case Type::SCENE:
return _transition.ToString();
case Type::SCENE_OVERRIDE:
return _scene.ToString() + " - " + _transition.ToString();
case Type::SOURCE_SHOW:
return _scene.ToString() + " - " + _source.ToString() + " - " +
_transition.ToString();
case Type::SOURCE_HIDE:
return _scene.ToString() + " - " + _source.ToString() + " - " +
_transition.ToString();
case Type::TBAR:
return std::to_string(_tbarPosition.GetValue()) + "%";
case Type::RELEASE_TBAR:
return "";
}
return "";
}
@@ -205,6 +233,7 @@ void MacroActionTransition::ResolveVariablesToFixedValues()
_source.ResolveVariables();
_scene.ResolveVariables();
_duration.ResolveVariables();
_tbarPosition.ResolveVariables();
}
static inline void populateActionSelection(QComboBox *list)
@@ -236,9 +265,16 @@ MacroActionTransitionEdit::MacroActionTransitionEdit(
_setDuration(new QCheckBox),
_transitions(new TransitionSelectionWidget(this, false)),
_duration(new DurationSelection(this, false)),
_tbarPosition(new VariableDoubleSpinBox),
_transitionLayout(new QHBoxLayout),
_durationLayout(new QHBoxLayout)
_durationLayout(new QHBoxLayout),
_tbarLayout(new QHBoxLayout)
{
_tbarPosition->setMinimum(0.0);
_tbarPosition->setMaximum(100.0);
_tbarPosition->setDecimals(2);
_tbarPosition->setSuffix("%");
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
@@ -260,6 +296,11 @@ MacroActionTransitionEdit::MacroActionTransitionEdit(
SLOT(SetTransitionChanged(int)));
QWidget::connect(_setDuration, SIGNAL(stateChanged(int)), this,
SLOT(SetDurationChanged(int)));
QWidget::connect(
_tbarPosition,
SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
this,
SLOT(TbarPositionChanged(const NumberVariable<double> &)));
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{type}}", _actions},
@@ -269,22 +310,29 @@ MacroActionTransitionEdit::MacroActionTransitionEdit(
{"{{duration}}", _duration},
{"{{setTransition}}", _setTransition},
{"{{setDuration}}", _setDuration},
{"{{tbarPosition}}", _tbarPosition},
};
auto typeLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.transition.entry.line1"),
"AdvSceneSwitcher.action.transition.layout.type"),
typeLayout, widgetPlaceholders);
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.transition.layout.transition"),
_transitionLayout, widgetPlaceholders);
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.transition.layout.duration"),
_durationLayout, widgetPlaceholders);
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.transition.entry.line2"),
_transitionLayout, widgetPlaceholders);
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.transition.entry.line3"),
_durationLayout, widgetPlaceholders);
"AdvSceneSwitcher.action.transition.layout.tbar"),
_tbarLayout, widgetPlaceholders);
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(typeLayout);
mainLayout->addLayout(_transitionLayout);
mainLayout->addLayout(_durationLayout);
mainLayout->addLayout(_tbarLayout);
setLayout(mainLayout);
_entryData = entryData;
@@ -300,13 +348,14 @@ void MacroActionTransitionEdit::UpdateEntryData()
_actions->setCurrentIndex(static_cast<int>(_entryData->_type));
_scenes->SetScene(_entryData->_scene);
_sources->SetSceneItem((_entryData->_source));
_sources->SetSceneItem(_entryData->_source);
_setDuration->setChecked(_entryData->_setDuration);
_duration->SetDuration(_entryData->_duration);
_setTransition->setChecked(_entryData->_setTransitionType);
_transitions->SetTransition(_entryData->_transition);
_transitions->setEnabled(_entryData->_setTransitionType);
_duration->setEnabled(_entryData->_setDuration);
_tbarPosition->SetValue(_entryData->_tbarPosition);
SetWidgetVisibility();
}
@@ -353,11 +402,19 @@ void MacroActionTransitionEdit::DurationChanged(const Duration &dur)
void MacroActionTransitionEdit::SetWidgetVisibility()
{
const bool isTbar = _entryData->_type ==
MacroActionTransition::Type::TBAR;
const bool isReleaseTbar = _entryData->_type ==
MacroActionTransition::Type::RELEASE_TBAR;
_sources->setVisible(
_entryData->_type == MacroActionTransition::Type::SOURCE_HIDE ||
_entryData->_type == MacroActionTransition::Type::SOURCE_SHOW);
_scenes->setVisible(_entryData->_type !=
MacroActionTransition::Type::SCENE);
MacroActionTransition::Type::SCENE &&
!isTbar && !isReleaseTbar);
SetLayoutVisible(_transitionLayout, !isTbar && !isReleaseTbar);
SetLayoutVisible(_durationLayout, !isTbar && !isReleaseTbar);
SetLayoutVisible(_tbarLayout, isTbar);
adjustSize();
}
@@ -381,4 +438,13 @@ void MacroActionTransitionEdit::SetDurationChanged(int state)
_duration->setEnabled(state);
}
void MacroActionTransitionEdit::TbarPositionChanged(
const NumberVariable<double> &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_tbarPosition = value;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
} // namespace advss

View File

@@ -4,6 +4,7 @@
#include "transition-selection.hpp"
#include "scene-selection.hpp"
#include "scene-item-selection.hpp"
#include "variable-spinbox.hpp"
#include <QCheckBox>
#include <QHBoxLayout>
@@ -28,6 +29,8 @@ public:
SCENE_OVERRIDE,
SOURCE_SHOW,
SOURCE_HIDE,
TBAR,
RELEASE_TBAR,
};
Type _type = Type::SCENE;
@@ -37,11 +40,13 @@ public:
bool _setTransitionType = true;
TransitionSelection _transition;
Duration _duration;
DoubleVariable _tbarPosition = 0.0;
private:
void SetSceneTransition();
void SetTransitionOverride();
void SetSourceTransition(bool);
void SetTbarPosition();
static bool _registered;
static const std::string id;
@@ -72,6 +77,7 @@ private slots:
void SetDurationChanged(int state);
void TransitionChanged(const TransitionSelection &);
void DurationChanged(const Duration &seconds);
void TbarPositionChanged(const NumberVariable<double> &);
signals:
void HeaderInfoChanged(const QString &);
@@ -83,8 +89,10 @@ protected:
QCheckBox *_setDuration;
TransitionSelectionWidget *_transitions;
DurationSelection *_duration;
VariableDoubleSpinBox *_tbarPosition;
QHBoxLayout *_transitionLayout;
QHBoxLayout *_durationLayout;
QHBoxLayout *_tbarLayout;
std::shared_ptr<MacroActionTransition> _entryData;
private:

View File

@@ -57,8 +57,11 @@ bool MacroActionWait::PerformAction()
std::chrono::milliseconds((int)(sleepDuration * 1000));
SetMacroAbortWait(false);
std::unique_lock<std::mutex> lock(*GetMutex());
waitHelper(&lock, GetMacro(), time);
{
SuspendLock suspendLock(*this);
std::unique_lock<std::mutex> lock(*GetMutex());
waitHelper(&lock, GetMacro(), time);
}
return !MacroWaitShouldAbort();
}

View File

@@ -42,8 +42,7 @@ void CloseWindow(const std::string &) {}
std::optional<std::string> MacroActionWindow::GetMatchingWindow() const
{
std::vector<std::string> windowList;
GetWindowList(windowList);
const auto windowList = GetWindowList();
if (!_regex.Enabled()) {
if (std::find(windowList.begin(), windowList.end(),

View File

@@ -710,6 +710,12 @@ void MacroConditionDateEdit::UpdateEntryData()
SetWidgetStatus();
}
void MacroConditionDateEdit::showEvent(QShowEvent *event)
{
const QSignalBlocker b(this);
UpdateEntryData();
}
void MacroConditionDateEdit::SetupSimpleView()
{
SetLayoutVisible(_simpleLayout, true);

View File

@@ -107,6 +107,16 @@ signals:
void HeaderInfoChanged(const QString &);
protected:
void showEvent(QShowEvent *event) override;
private:
void SetupSimpleView();
void SetupAdvancedView();
void SetupPatternView();
void SetWidgetStatus();
void ShowFirstDateSelection(bool visible);
void ShowSecondDateSelection(bool visible);
QComboBox *_weekCondition;
DayOfWeekSelector *_days;
QCheckBox *_ignoreWeekTime;
@@ -134,16 +144,9 @@ protected:
QHBoxLayout *_repeatUpdateLayout;
QHBoxLayout *_patternLayout;
std::shared_ptr<MacroConditionDate> _entryData;
private:
void SetupSimpleView();
void SetupAdvancedView();
void SetupPatternView();
void SetWidgetStatus();
void ShowFirstDateSelection(bool visible);
void ShowSecondDateSelection(bool visible);
QTimer _timer;
std::shared_ptr<MacroConditionDate> _entryData;
bool _loading = true;
};

View File

@@ -1,12 +1,10 @@
#include "macro-condition-file.hpp"
#include "curl-helper.hpp"
#include "layout-helpers.hpp"
#include "plugin-state-helpers.hpp"
#include "utility.hpp"
#include <QFileDialog>
#include <QTextStream>
#include <regex>
#include <QFileInfo>
namespace advss {
@@ -19,29 +17,6 @@ bool MacroConditionFile::_registered = MacroConditionFactory::Register(
static std::hash<std::string> strHash;
static size_t WriteCallback(void *contents, size_t size, size_t nmemb,
void *userp)
{
((std::string *)userp)->append((char *)contents, size * nmemb);
return size * nmemb;
}
static std::string getRemoteData(std::string &url)
{
std::string readBuffer;
CurlHelper::SetOpt(CURLOPT_URL, url.c_str());
CurlHelper::SetOpt(CURLOPT_WRITEFUNCTION, WriteCallback);
CurlHelper::SetOpt(CURLOPT_WRITEDATA, &readBuffer);
// Set timeout to at least one second
int timeout = GetIntervalValue() / 1000;
if (timeout == 0) {
timeout = 1;
}
CurlHelper::SetOpt(CURLOPT_TIMEOUT, 1);
CurlHelper::Perform();
return readBuffer;
}
void MacroConditionFile::SetCondition(Condition condition)
{
_condition = condition;
@@ -50,14 +25,6 @@ void MacroConditionFile::SetCondition(Condition condition)
bool MacroConditionFile::MatchFileContent(QString &filedata)
{
if (_onlyMatchIfChanged) {
size_t newHash = strHash(filedata.toUtf8().constData());
if (newHash == _lastHash) {
return false;
}
_lastHash = newHash;
}
if (_regex.Enabled()) {
return _regex.Matches(filedata, QString::fromStdString(_text));
}
@@ -66,31 +33,13 @@ bool MacroConditionFile::MatchFileContent(QString &filedata)
return CompareIgnoringLineEnding(text, filedata);
}
bool MacroConditionFile::CheckRemoteFileContent()
{
std::string path = _file;
std::string data = getRemoteData(path);
SetVariableValue(data);
SetTempVarValue("content", data);
QString qdata = QString::fromStdString(data);
return MatchFileContent(qdata);
}
bool MacroConditionFile::CheckLocalFileContent()
bool MacroConditionFile::CheckFileContent()
{
QFile file(QString::fromStdString(_file));
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
if (_useTime) {
QDateTime newLastMod = QFileInfo(file).lastModified();
if (_lastMod == newLastMod) {
return false;
}
_lastMod = newLastMod;
}
QString filedata = QTextStream(&file).readAll();
SetVariableValue(filedata.toStdString());
SetTempVarValue("content", filedata.toStdString());
@@ -103,42 +52,30 @@ bool MacroConditionFile::CheckLocalFileContent()
bool MacroConditionFile::CheckChangeContent()
{
QString filedata;
switch (_fileType) {
case FileType::LOCAL: {
std::string path = _file;
QFile file(QString::fromStdString(path));
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
filedata = QTextStream(&file).readAll();
file.close();
} break;
case FileType::REMOTE: {
std::string path = _file;
std::string data = getRemoteData(path);
QString filedata = QString::fromStdString(data);
} break;
default:
break;
std::string path = _file;
QFile file(QString::fromStdString(path));
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
filedata = QTextStream(&file).readAll();
file.close();
SetTempVarValue("content", filedata.toStdString());
size_t newHash = strHash(filedata.toUtf8().constData());
const bool contentChanged = newHash != _lastHash;
const bool contentChanged = !_firstContentCheck &&
(newHash != _lastHash);
_lastHash = newHash;
_firstContentCheck = false;
return contentChanged;
}
bool MacroConditionFile::CheckChangeDate()
{
if (_fileType == FileType::REMOTE) {
return false;
}
QFile file(QString::fromStdString(_file));
QDateTime newLastMod = QFileInfo(file).lastModified();
SetVariableValue(newLastMod.toString().toStdString());
const bool dateChanged = _lastMod != newLastMod;
const bool dateChanged = _lastMod.isValid() && (_lastMod != newLastMod);
_lastMod = newLastMod;
SetTempVarValue("date", newLastMod.toString(Qt::ISODate).toStdString());
return dateChanged;
@@ -157,10 +94,6 @@ void MacroConditionFile::SetupTempVars()
"AdvSceneSwitcher.tempVar.file.content"));
}
if (_fileType == FileType::REMOTE) {
return;
}
AddTempvar(
"basename",
obs_module_text("AdvSceneSwitcher.tempVar.file.basename"),
@@ -199,11 +132,7 @@ bool MacroConditionFile::CheckCondition()
bool ret = false;
switch (_condition) {
case Condition::MATCH:
if (_fileType == FileType::REMOTE) {
ret = CheckRemoteFileContent();
break;
}
ret = CheckLocalFileContent();
ret = CheckFileContent();
break;
case Condition::CONTENT_CHANGE:
ret = CheckChangeContent();
@@ -264,10 +193,7 @@ bool MacroConditionFile::Save(obs_data_t *obj) const
_regex.Save(obj);
_file.Save(obj, "file");
_text.Save(obj, "text");
obs_data_set_int(obj, "fileType", static_cast<int>(_fileType));
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
obs_data_set_bool(obj, "useTime", _useTime);
obs_data_set_bool(obj, "onlyMatchIfChanged", _onlyMatchIfChanged);
return true;
}
@@ -275,18 +201,10 @@ bool MacroConditionFile::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_regex.Load(obj);
// TODO: remove in future version
if (obs_data_has_user_value(obj, "useRegex")) {
_regex.CreateBackwardsCompatibleRegex(
obs_data_get_bool(obj, "useRegex"));
}
_file.Load(obj, "file");
_text.Load(obj, "text");
_fileType = static_cast<FileType>(obs_data_get_int(obj, "fileType"));
SetCondition(
static_cast<Condition>(obs_data_get_int(obj, "condition")));
_useTime = obs_data_get_bool(obj, "useTime");
_onlyMatchIfChanged = obs_data_get_bool(obj, "onlyMatchIfChanged");
return true;
}
@@ -295,13 +213,6 @@ std::string MacroConditionFile::GetShortDesc() const
return _file.UnresolvedValue();
}
static void populateFileTypes(QComboBox *list)
{
list->addItem(obs_module_text("AdvSceneSwitcher.condition.file.local"));
list->addItem(
obs_module_text("AdvSceneSwitcher.condition.file.remote"));
}
static void populateConditions(QComboBox *list)
{
list->addItem(
@@ -321,21 +232,13 @@ static void populateConditions(QComboBox *list)
MacroConditionFileEdit::MacroConditionFileEdit(
QWidget *parent, std::shared_ptr<MacroConditionFile> entryData)
: QWidget(parent),
_fileTypes(new QComboBox()),
_conditions(new QComboBox()),
_filePath(new FileSelection()),
_matchText(new VariableTextEdit(this)),
_regex(new RegexConfigWidget(parent)),
_checkModificationDate(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.fileTab.checkfileContentTime"))),
_checkFileContent(new QCheckBox(
obs_module_text("AdvSceneSwitcher.fileTab.checkfileContent")))
_regex(new RegexConfigWidget(parent))
{
populateFileTypes(_fileTypes);
populateConditions(_conditions);
QWidget::connect(_fileTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(FileTypeChanged(int)));
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this,
@@ -345,42 +248,20 @@ MacroConditionFileEdit::MacroConditionFileEdit(
QWidget::connect(_regex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(RegexChanged(const RegexConfig &)));
QWidget::connect(_checkModificationDate, SIGNAL(stateChanged(int)),
this, SLOT(CheckModificationDateChanged(int)));
QWidget::connect(_checkFileContent, SIGNAL(stateChanged(int)), this,
SLOT(OnlyMatchIfChangedChanged(int)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{fileType}}", _fileTypes},
{"{{conditions}}", _conditions},
{"{{filePath}}", _filePath},
{"{{matchText}}", _matchText},
{"{{useRegex}}", _regex},
{"{{checkModificationDate}}", _checkModificationDate},
{"{{checkFileContent}}", _checkFileContent},
};
auto widgetLayout = new QHBoxLayout;
widgetLayout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.file.layout"),
widgetLayout,
{{"{{conditions}}", _conditions},
{"{{filePath}}", _filePath},
{"{{regex}}", _regex}});
QVBoxLayout *mainLayout = new QVBoxLayout;
QHBoxLayout *line1Layout = new QHBoxLayout;
QHBoxLayout *line2Layout = new QHBoxLayout;
QHBoxLayout *line3Layout = new QHBoxLayout;
line1Layout->setContentsMargins(0, 0, 0, 0);
line2Layout->setContentsMargins(0, 0, 0, 0);
line3Layout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.file.entry.line1"),
line1Layout, widgetPlaceholders);
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.file.entry.line2"),
line2Layout, widgetPlaceholders, false);
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.file.entry.line3"),
line3Layout, widgetPlaceholders);
mainLayout->addLayout(line1Layout);
mainLayout->addLayout(line2Layout);
mainLayout->addLayout(line3Layout);
auto layout = new QVBoxLayout;
layout->addLayout(widgetLayout);
layout->addWidget(_matchText);
setLayout(mainLayout);
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
@@ -393,47 +274,15 @@ void MacroConditionFileEdit::UpdateEntryData()
return;
}
_fileTypes->setCurrentIndex(static_cast<int>(_entryData->_fileType));
_conditions->setCurrentIndex(
static_cast<int>(_entryData->GetCondition()));
_filePath->SetPath(_entryData->_file);
_matchText->setPlainText(_entryData->_text);
_regex->SetRegexConfig(_entryData->_regex);
_checkModificationDate->setChecked(_entryData->_useTime);
_checkFileContent->setChecked(_entryData->_onlyMatchIfChanged);
// TODO: Remove in future version
if (!_entryData->_useTime) {
_checkModificationDate->hide();
}
if (!_entryData->_onlyMatchIfChanged) {
_checkFileContent->hide();
}
SetWidgetVisibility();
}
void MacroConditionFileEdit::FileTypeChanged(int index)
{
if (_loading || !_entryData) {
return;
}
MacroConditionFile::FileType type =
static_cast<MacroConditionFile::FileType>(index);
if (type == MacroConditionFile::FileType::LOCAL) {
_filePath->Button()->setDisabled(false);
_checkModificationDate->setDisabled(false);
} else {
_filePath->Button()->setDisabled(true);
_checkModificationDate->setDisabled(true);
}
auto lock = LockContext();
_entryData->_fileType = type;
}
void MacroConditionFileEdit::ConditionChanged(int index)
{
GUARD_LOADING_AND_LOCK();
@@ -467,18 +316,6 @@ void MacroConditionFileEdit::RegexChanged(const RegexConfig &conf)
updateGeometry();
}
void MacroConditionFileEdit::CheckModificationDateChanged(int state)
{
GUARD_LOADING_AND_LOCK();
_entryData->_useTime = state;
}
void MacroConditionFileEdit::OnlyMatchIfChangedChanged(int state)
{
GUARD_LOADING_AND_LOCK();
_entryData->_onlyMatchIfChanged = state;
}
void MacroConditionFileEdit::SetWidgetVisibility()
{
if (!_entryData) {
@@ -489,20 +326,6 @@ void MacroConditionFileEdit::SetWidgetVisibility()
MacroConditionFile::Condition::MATCH);
_regex->setVisible(_entryData->GetCondition() ==
MacroConditionFile::Condition::MATCH);
_checkModificationDate->setVisible(
_entryData->_useTime &&
_entryData->GetCondition() ==
MacroConditionFile::Condition::MATCH);
_checkFileContent->setVisible(
_entryData->_onlyMatchIfChanged &&
_entryData->GetCondition() ==
MacroConditionFile::Condition::MATCH);
// TODO: Remove remote file support in future version in favor of HTTP
// action.
// Hide the option for now, if it is not used already.
_fileTypes->setVisible(_entryData->_fileType ==
MacroConditionFile::FileType::REMOTE);
adjustSize();
updateGeometry();

View File

@@ -7,9 +7,6 @@
#include <QWidget>
#include <QComboBox>
#include <QDateTime>
#include <QLineEdit>
#include <QPushButton>
#include <QCheckBox>
namespace advss {
@@ -26,11 +23,6 @@ public:
return std::make_shared<MacroConditionFile>(m);
}
enum class FileType {
LOCAL,
REMOTE,
};
enum class Condition {
MATCH,
CONTENT_CHANGE,
@@ -46,15 +38,9 @@ public:
StringVariable _text = obs_module_text("AdvSceneSwitcher.enterText");
RegexConfig _regex;
// TODO: Remove in future version
bool _useTime = false;
bool _onlyMatchIfChanged = false;
FileType _fileType = FileType::LOCAL;
private:
bool MatchFileContent(QString &filedata);
bool CheckRemoteFileContent();
bool CheckLocalFileContent();
bool CheckFileContent();
bool CheckChangeContent();
bool CheckChangeDate();
void SetupTempVars();
@@ -62,6 +48,7 @@ private:
Condition _condition = Condition::MATCH;
QDateTime _lastMod;
size_t _lastHash = 0;
bool _firstContentCheck = true;
std::string _lastFile;
std::string _basename;
std::string _basenameComplete;
@@ -92,29 +79,22 @@ public:
}
private slots:
void FileTypeChanged(int index);
void ConditionChanged(int index);
void PathChanged(const QString &text);
void MatchTextChanged();
void RegexChanged(const RegexConfig &);
void CheckModificationDateChanged(int state);
void OnlyMatchIfChangedChanged(int state);
signals:
void HeaderInfoChanged(const QString &);
protected:
QComboBox *_fileTypes;
private:
void SetWidgetVisibility();
QComboBox *_conditions;
FileSelection *_filePath;
VariableTextEdit *_matchText;
RegexConfigWidget *_regex;
QCheckBox *_checkModificationDate;
QCheckBox *_checkFileContent;
std::shared_ptr<MacroConditionFile> _entryData;
private:
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -16,54 +16,100 @@ bool MacroConditionProcess::_registered = MacroConditionFactory::Register(
bool MacroConditionProcess::CheckCondition()
{
QStringList runningProcesses;
QString proc = QString::fromStdString(_process);
GetProcessList(runningProcesses);
std::string foregroundProcessName;
GetForegroundProcessName(foregroundProcessName);
const auto foregroundProcessName = GetForegroundProcessName();
SetVariableValue(foregroundProcessName);
if (!_regex.Enabled()) {
if (runningProcesses.contains(proc) &&
(!_checkFocus || IsInFocus(proc))) {
SetTempVarValue("name", proc.toStdString());
return true;
}
return false;
}
const QString proc = QString::fromStdString(_process);
int matchIndex = -1;
bool foundMatch = false;
for (const auto &process : runningProcesses) {
matchIndex++;
if (_regex.Matches(process, proc)) {
foundMatch = true;
break;
if (_checkFocus) {
// Check name and path against the same foreground process
// instance to avoid false positives when multiple processes
// share the same name
const auto foregroundPath = GetForegroundProcessPath();
const QString foregroundName =
QString::fromStdString(foregroundProcessName);
SetTempVarValue("name", foregroundProcessName);
SetTempVarValue("path", foregroundPath);
bool nameMatches =
_regex.Enabled() ? _regex.Matches(foregroundName, proc)
: (foregroundName == proc);
if (!nameMatches) {
return false;
}
}
if (!foundMatch) {
return false;
}
if (!_checkFocus) {
SetTempVarValue("name",
runningProcesses.at(matchIndex).toStdString());
if (_checkPath) {
const QString pathPattern =
QString::fromStdString(_processPath);
const QString qForegroundPath =
QString::fromStdString(foregroundPath);
bool pathMatches =
_pathRegex.Enabled()
? _pathRegex.Matches(qForegroundPath,
pathPattern)
: qForegroundPath == pathPattern;
if (!pathMatches) {
return false;
}
}
return true;
}
if (!IsInFocus(proc)) {
return false;
const auto runningProcesses = GetProcessList();
for (const auto &process : runningProcesses) {
bool nameMatches = _regex.Enabled()
? _regex.Matches(process, proc)
: (process == proc);
if (!nameMatches) {
continue;
}
if (!_checkPath) {
SetTempVarValue("name", process.toStdString());
return true;
}
const auto paths = GetProcessPathsFromName(process);
const QString pathPattern =
QString::fromStdString(_processPath);
bool foundMatchingPath = false;
for (const auto &path : paths) {
bool pathMatches =
_pathRegex.Enabled()
? _pathRegex.Matches(path, pathPattern)
: path == pathPattern;
if (pathMatches) {
SetTempVarValue("path", path.toStdString());
foundMatchingPath = true;
break;
}
}
if (!foundMatchingPath) {
continue;
}
SetTempVarValue("name", process.toStdString());
return true;
}
SetTempVarValue("name", foregroundProcessName);
return true;
return false;
}
bool MacroConditionProcess::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
_process.Save(obj, "process");
obs_data_set_bool(obj, "focus", _checkFocus);
_regex.Save(obj);
obs_data_set_int(obj, "version", 1);
obs_data_set_bool(obj, "focus", _checkFocus);
obs_data_set_bool(obj, "checkPath", _checkPath);
_processPath.Save(obj, "processPath");
_pathRegex.Save(obj, "pathRegex");
obs_data_set_int(obj, "version", 2);
return true;
}
@@ -78,6 +124,9 @@ bool MacroConditionProcess::Load(obs_data_t *obj)
} else {
_regex.Load(obj);
}
_checkPath = obs_data_get_bool(obj, "checkPath");
_processPath.Load(obj, "processPath");
_pathRegex.Load(obj, "pathRegex");
return true;
}
@@ -91,6 +140,8 @@ void MacroConditionProcess::SetupTempVars()
MacroCondition::SetupTempVars();
AddTempvar("name",
obs_module_text("AdvSceneSwitcher.tempVar.process.name"));
AddTempvar("path",
obs_module_text("AdvSceneSwitcher.tempVar.process.path"));
}
MacroConditionProcessEdit::MacroConditionProcessEdit(
@@ -100,13 +151,20 @@ MacroConditionProcessEdit::MacroConditionProcessEdit(
_regex(new RegexConfigWidget(this)),
_focused(new QCheckBox()),
_focusProcess(new QLabel()),
_focusLayout(new QHBoxLayout())
_focusLayout(new QHBoxLayout()),
_checkPath(new QCheckBox()),
_processPath(new VariableLineEdit(this)),
_pathRegex(new RegexConfigWidget(this)),
_pathLayout(new QHBoxLayout())
{
_processSelection->setEditable(true);
_processSelection->setMaxVisibleItems(20);
_processSelection->setToolTip(
obs_module_text("AdvSceneSwitcher.tooltip.availableVariables"));
_processPath->setToolTip(
obs_module_text("AdvSceneSwitcher.tooltip.availableVariables"));
QWidget::connect(_processSelection,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(ProcessChanged(const QString &)));
@@ -115,6 +173,13 @@ MacroConditionProcessEdit::MacroConditionProcessEdit(
SLOT(RegexChanged(const RegexConfig &)));
QWidget::connect(_focused, SIGNAL(stateChanged(int)), this,
SLOT(FocusChanged(int)));
QWidget::connect(_checkPath, SIGNAL(stateChanged(int)), this,
SLOT(CheckPathChanged(int)));
QWidget::connect(_processPath, SIGNAL(textChanged(const QString &)),
this, SLOT(ProcessPathChanged(const QString &)));
QWidget::connect(_pathRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(PathRegexChanged(const RegexConfig &)));
QWidget::connect(&_timer, SIGNAL(timeout()), this,
SLOT(UpdateFocusProcess()));
@@ -125,18 +190,25 @@ MacroConditionProcessEdit::MacroConditionProcessEdit(
{"{{regex}}", _regex},
{"{{focused}}", _focused},
{"{{focusProcess}}", _focusProcess},
{"{{checkPath}}", _checkPath},
{"{{path}}", _processPath},
{"{{pathRegex}}", _pathRegex},
};
auto entryLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.process.entry"),
obs_module_text("AdvSceneSwitcher.condition.process.layout"),
entryLayout, widgetPlaceholders);
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.process.entry.focus"),
"AdvSceneSwitcher.condition.process.layout.focus"),
_focusLayout, widgetPlaceholders);
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.process.layout.path"),
_pathLayout, widgetPlaceholders);
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLayout);
mainLayout->addLayout(_focusLayout);
mainLayout->addLayout(_pathLayout);
setLayout(mainLayout);
_entryData = entryData;
@@ -178,11 +250,31 @@ void MacroConditionProcessEdit::FocusChanged(int state)
SetWidgetVisibility();
}
void MacroConditionProcessEdit::CheckPathChanged(int state)
{
GUARD_LOADING_AND_LOCK();
_entryData->_checkPath = state;
SetWidgetVisibility();
}
void MacroConditionProcessEdit::ProcessPathChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_processPath = text.toStdString();
}
void MacroConditionProcessEdit::PathRegexChanged(const RegexConfig &conf)
{
GUARD_LOADING_AND_LOCK();
_entryData->_pathRegex = conf;
adjustSize();
updateGeometry();
}
void MacroConditionProcessEdit::UpdateFocusProcess()
{
std::string name;
GetForegroundProcessName(name);
_focusProcess->setText(QString::fromStdString(name));
_focusProcess->setText(
QString::fromStdString(GetForegroundProcessName()));
}
void MacroConditionProcessEdit::SetWidgetVisibility()
@@ -191,6 +283,13 @@ void MacroConditionProcessEdit::SetWidgetVisibility()
return;
}
SetLayoutVisible(_focusLayout, _entryData->_checkFocus);
_processPath->setVisible(_entryData->_checkPath);
_pathRegex->setVisible(_entryData->_checkPath);
if (_entryData->_checkPath) {
RemoveStretchIfPresent(_pathLayout);
} else {
AddStretchIfNecessary(_pathLayout);
}
adjustSize();
updateGeometry();
}
@@ -205,6 +304,9 @@ void MacroConditionProcessEdit::UpdateEntryData()
_entryData->_process.UnresolvedValue().c_str());
_regex->SetRegexConfig(_entryData->_regex);
_focused->setChecked(_entryData->_checkFocus);
_checkPath->setChecked(_entryData->_checkPath);
_processPath->setText(_entryData->_processPath);
_pathRegex->SetRegexConfig(_entryData->_pathRegex);
SetWidgetVisibility();
}

View File

@@ -1,7 +1,7 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "regex-config.hpp"
#include "variable-string.hpp"
#include "variable-line-edit.hpp"
#include <QCheckBox>
@@ -21,8 +21,11 @@ public:
}
StringVariable _process;
bool _checkFocus = true;
RegexConfig _regex = RegexConfig::PartialMatchRegexConfig();
bool _checkFocus = true;
bool _checkPath = false;
StringVariable _processPath;
RegexConfig _pathRegex = RegexConfig::PartialMatchRegexConfig();
private:
void SetupTempVars();
@@ -54,6 +57,9 @@ private slots:
void ProcessChanged(const QString &text);
void RegexChanged(const RegexConfig &);
void FocusChanged(int state);
void CheckPathChanged(int state);
void ProcessPathChanged(const QString &text);
void PathRegexChanged(const RegexConfig &);
void UpdateFocusProcess();
signals:
void HeaderInfoChanged(const QString &);
@@ -66,6 +72,10 @@ private:
QCheckBox *_focused;
QLabel *_focusProcess;
QHBoxLayout *_focusLayout;
QCheckBox *_checkPath;
VariableLineEdit *_processPath;
RegexConfigWidget *_pathRegex;
QHBoxLayout *_pathLayout;
QTimer _timer;
std::shared_ptr<MacroConditionProcess> _entryData;
bool _loading = true;

View File

@@ -135,8 +135,7 @@ static bool foregroundWindowChanged()
bool MacroConditionWindow::CheckCondition()
{
std::vector<std::string> windowList;
GetWindowList(windowList);
const auto windowList = GetWindowList();
bool match = false;
if (_windowRegex.Enabled()) {
match = WindowRegexMatches(windowList);

View File

@@ -23,6 +23,7 @@ bool setup()
{
AddSaveStep(saveConnections);
AddLoadStep(loadConnections);
AddPluginCleanupStep([]() { connections.clear(); });
return true;
}

View File

@@ -0,0 +1,237 @@
#include "source-interaction-recorder.hpp"
#include "obs-module-helper.hpp"
#include <obs.hpp>
#include <obs-interaction.h>
#include <QEvent>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>
namespace advss {
SourceInteractionRecorder::SourceInteractionRecorder(QWidget *parent,
obs_weak_source_t *source)
: QDialog(parent),
_source(source),
_preview(new SourcePreviewWidget(this, source)),
_stepList(new SourceInteractionStepList(this)),
_startStopButton(new QPushButton(
obs_module_text(
"AdvSceneSwitcher.action.sourceInteraction.record.start"),
this))
{
setWindowTitle(obs_module_text(
"AdvSceneSwitcher.action.sourceInteraction.record.title"));
_preview->setMouseTracking(true);
_preview->setFocusPolicy(Qt::StrongFocus);
_preview->installEventFilter(this);
_stepList->HideControls();
_stepList->SetMinListHeight(50);
auto buttonRow = new QHBoxLayout;
buttonRow->addWidget(_startStopButton);
buttonRow->addStretch();
auto acceptButton = new QPushButton(
obs_module_text(
"AdvSceneSwitcher.action.sourceInteraction.record.accept"),
this);
buttonRow->addWidget(acceptButton);
auto layout = new QVBoxLayout(this);
layout->addWidget(_preview, 1);
layout->addWidget(_stepList);
layout->addLayout(buttonRow);
setLayout(layout);
connect(_startStopButton, &QPushButton::clicked, this,
&SourceInteractionRecorder::StartStop);
connect(acceptButton, &QPushButton::clicked, this, [this]() {
if (_recording) {
StartStop();
}
emit StepsRecorded(_steps);
accept();
});
}
SourceInteractionRecorder::~SourceInteractionRecorder() {}
void SourceInteractionRecorder::StartStop()
{
_recording = !_recording;
if (_recording) {
_steps.clear();
_stepList->Clear();
_firstEvent = true;
_startStopButton->setText(obs_module_text(
"AdvSceneSwitcher.action.sourceInteraction.record.stop"));
} else {
_startStopButton->setText(obs_module_text(
"AdvSceneSwitcher.action.sourceInteraction.record.start"));
}
}
void SourceInteractionRecorder::AppendStep(const SourceInteractionStep &step)
{
if (!_firstEvent) {
auto now = std::chrono::steady_clock::now();
int ms = static_cast<int>(
std::chrono::duration_cast<std::chrono::milliseconds>(
now - _lastEventTime)
.count());
if (ms > 10) {
SourceInteractionStep wait;
wait.type = SourceInteractionStep::Type::WAIT;
wait.waitMs.SetValue(ms);
_steps.push_back(wait);
_stepList->Insert(
QString::fromStdString(wait.ToString()));
}
}
_firstEvent = false;
_lastEventTime = std::chrono::steady_clock::now();
_steps.push_back(step);
_stepList->Insert(QString::fromStdString(step.ToString()));
_stepList->ScrollToBottom();
}
bool SourceInteractionRecorder::eventFilter(QObject *obj, QEvent *event)
{
if (obj != _preview || !_recording) {
return QDialog::eventFilter(obj, event);
}
switch (event->type()) {
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
return HandleMouseClick(static_cast<QMouseEvent *>(event));
case QEvent::MouseMove:
return HandleMouseMove(static_cast<QMouseEvent *>(event));
case QEvent::Wheel:
return HandleMouseWheel(static_cast<QWheelEvent *>(event));
case QEvent::KeyPress:
case QEvent::KeyRelease:
return HandleKeyEvent(static_cast<QKeyEvent *>(event));
case QEvent::FocusIn:
case QEvent::FocusOut: {
OBSSourceAutoRelease source =
obs_weak_source_get_source(_source);
if (source) {
obs_source_send_focus(source,
event->type() == QEvent::FocusIn);
}
return true;
}
default:
break;
}
return QDialog::eventFilter(obj, event);
}
bool SourceInteractionRecorder::HandleMouseClick(QMouseEvent *event)
{
SourceInteractionStep step;
step.type = SourceInteractionStep::Type::MOUSE_CLICK;
int srcX = 0, srcY = 0;
QPoint pos = event->pos();
_preview->GetSourceRelativeXY(pos.x(), pos.y(), srcX, srcY);
step.x.SetValue(srcX);
step.y.SetValue(srcY);
step.mouseUp = (event->type() == QEvent::MouseButtonRelease);
step.clickCount.SetValue(
(event->type() == QEvent::MouseButtonDblClick) ? 2 : 1);
switch (event->button()) {
case Qt::LeftButton:
step.button = MOUSE_LEFT;
break;
case Qt::MiddleButton:
step.button = MOUSE_MIDDLE;
break;
case Qt::RightButton:
step.button = MOUSE_RIGHT;
break;
default:
return false;
}
AppendStep(step);
OBSSourceAutoRelease source = obs_weak_source_get_source(_source);
if (source) {
PerformInteractionStep(source, step);
}
return true;
}
bool SourceInteractionRecorder::HandleMouseMove(QMouseEvent *event)
{
SourceInteractionStep step;
step.type = SourceInteractionStep::Type::MOUSE_MOVE;
int srcX = 0, srcY = 0;
QPoint pos = event->pos();
_preview->GetSourceRelativeXY(pos.x(), pos.y(), srcX, srcY);
step.x.SetValue(srcX);
step.y.SetValue(srcY);
// Always forward moves to the source so hover/cursor effects work,
// but only record them when a button is held (drag), to reduce noise.
OBSSourceAutoRelease source = obs_weak_source_get_source(_source);
if (source) {
PerformInteractionStep(source, step);
}
if (event->buttons() != Qt::NoButton) {
AppendStep(step);
}
return true;
}
bool SourceInteractionRecorder::HandleMouseWheel(QWheelEvent *event)
{
SourceInteractionStep step;
step.type = SourceInteractionStep::Type::MOUSE_WHEEL;
int srcX = 0, srcY = 0;
const QPointF pos = event->position();
_preview->GetSourceRelativeXY((int)pos.x(), (int)pos.y(), srcX, srcY);
step.x.SetValue(srcX);
step.y.SetValue(srcY);
const QPoint angle = event->angleDelta();
step.wheelDeltaX.SetValue(angle.x());
step.wheelDeltaY.SetValue(angle.y());
AppendStep(step);
OBSSourceAutoRelease source = obs_weak_source_get_source(_source);
if (source) {
PerformInteractionStep(source, step);
}
return true;
}
bool SourceInteractionRecorder::HandleKeyEvent(QKeyEvent *event)
{
SourceInteractionStep step;
step.type = SourceInteractionStep::Type::KEY_PRESS;
step.keyUp = (event->type() == QEvent::KeyRelease);
step.nativeVkey.SetValue(static_cast<int>(event->nativeVirtualKey()));
step.text = event->text().toStdString();
AppendStep(step);
OBSSourceAutoRelease source = obs_weak_source_get_source(_source);
if (source) {
PerformInteractionStep(source, step);
}
return true;
}
} // namespace advss

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