mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-04-25 15:34:48 -05:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f090dea136 | ||
|
|
d00986df03 | ||
|
|
03e5397b7e | ||
|
|
94d49db196 | ||
|
|
1483f9d9dc | ||
|
|
99629e8c66 | ||
|
|
ba38b8bf27 | ||
|
|
38513735dd | ||
|
|
acfc7d605b | ||
|
|
df42538319 | ||
|
|
b02259e926 | ||
|
|
2b6bd9fb51 | ||
|
|
aadedbb610 | ||
|
|
d15a324137 | ||
|
|
119fac17bf | ||
|
|
088881b674 | ||
|
|
f008d5367b | ||
|
|
1ace185a57 | ||
|
|
a01d26e25d | ||
|
|
7e2ef9aacc | ||
|
|
4a98d064f7 | ||
|
|
b0d6cea52a | ||
|
|
d6bf9ed101 | ||
|
|
7e940e515b | ||
|
|
29e1ff0754 | ||
|
|
6309100cc4 | ||
|
|
ec3052c823 | ||
|
|
a28b800228 | ||
|
|
713a64ab45 | ||
|
|
4d5ffe38fc | ||
|
|
85460157b1 | ||
|
|
35e5b7620e | ||
|
|
e4c06b14a7 | ||
|
|
7fe3258269 | ||
|
|
ec19b4d4fb | ||
|
|
9301ead060 | ||
|
|
bc29ece526 | ||
|
|
4158c7a363 | ||
|
|
944d1059da | ||
|
|
bf18d8e106 | ||
|
|
bf216e0917 | ||
|
|
2405b6dbbf | ||
|
|
e64f2d195e | ||
|
|
f66bec8caf | ||
|
|
3eb79e3adb | ||
|
|
07e2ac3ca0 | ||
|
|
8e2c466c2d | ||
|
|
cc68e2366c | ||
|
|
7a0e08b0d8 | ||
|
|
be8744f0d0 | ||
|
|
d4425df694 | ||
|
|
70e5f6002d | ||
|
|
ff98ec36d6 | ||
|
|
4966802f14 | ||
|
|
b23a90557f | ||
|
|
d6ea815b85 |
4
.github/scripts/.build.zsh
vendored
4
.github/scripts/.build.zsh
vendored
|
|
@ -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}
|
||||
)
|
||||
|
||||
|
|
|
|||
3
.github/scripts/utils.zsh/check_macos
vendored
3
.github/scripts/utils.zsh/check_macos
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
.github/workflows/build-project.yaml
vendored
2
.github/workflows/build-project.yaml
vendored
|
|
@ -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 🔎
|
||||
|
|
|
|||
|
|
@ -204,6 +204,8 @@ target_sources(
|
|||
lib/utils/file-selection.hpp
|
||||
lib/utils/filter-combo-box.cpp
|
||||
lib/utils/filter-combo-box.hpp
|
||||
lib/utils/first-run-wizard.cpp
|
||||
lib/utils/first-run-wizard.hpp
|
||||
lib/utils/help-icon.hpp
|
||||
lib/utils/help-icon.cpp
|
||||
lib/utils/item-selection-helpers.cpp
|
||||
|
|
@ -454,14 +456,29 @@ else()
|
|||
endif()
|
||||
if(PROCPS2_INCLUDE_DIR)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(libproc2 IMPORTED_TARGET libproc2<4.0.5 QUIET)
|
||||
if(libproc2_FOUND)
|
||||
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS2_USE_INFO)
|
||||
endif()
|
||||
pkg_check_modules(libproc2 REQUIRED IMPORTED_TARGET libproc2)
|
||||
set(PROC_INCLUDE_DIR "${PROCPS2_INCLUDE_DIR}")
|
||||
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS2_AVAILABLE)
|
||||
set(PROCESS_CONDITION_SUPPORTED 1)
|
||||
|
||||
# Check if PIDS_VAL takes 4 arguments (old API, pre-4.0.5) or 3 (new API)
|
||||
include(CheckCSourceCompiles)
|
||||
set(CMAKE_REQUIRED_INCLUDES "${PROCPS2_INCLUDE_DIR}")
|
||||
set(CMAKE_REQUIRED_LIBRARIES proc2)
|
||||
check_c_source_compiles(
|
||||
"
|
||||
#include <libproc2/pids.h>
|
||||
int main(void) {
|
||||
struct pids_stack *s = 0;
|
||||
struct pids_info *i = 0;
|
||||
(void)PIDS_VAL(0, str, s, i);
|
||||
return 0;
|
||||
}
|
||||
"
|
||||
PROCPS2_PIDS_VAL_TAKES_INFO)
|
||||
if(PROCPS2_PIDS_VAL_TAKES_INFO)
|
||||
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS2_USE_INFO)
|
||||
endif()
|
||||
endif()
|
||||
if(NOT DEFINED PROCESS_CONDITION_SUPPORTED)
|
||||
message(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ function(_git_find_closest_git_dir _start_dir _git_dir_var)
|
|||
while(NOT EXISTS "${git_dir}")
|
||||
# .git dir not found, search parent directories
|
||||
set(git_previous_parent "${cur_dir}")
|
||||
get_filename_component(cur_dir ${cur_dir} DIRECTORY)
|
||||
get_filename_component(cur_dir "${cur_dir}" DIRECTORY)
|
||||
if(cur_dir STREQUAL git_previous_parent)
|
||||
# We have reached the root directory, we are not in git
|
||||
set(${_git_dir_var}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ AdvSceneSwitcher.macroTab.name="Name:"
|
|||
AdvSceneSwitcher.macroTab.run="Makro ausführen"
|
||||
AdvSceneSwitcher.macroTab.runFail="Ausführen von \"%1\" fehlgeschlagen!\nEntweder ist eine der Aktionen fehlgeschlagen oder das Makro wird bereits ausgeführt.\nSoll die aktuelle Ausführung gestoppt werden?"
|
||||
AdvSceneSwitcher.macroTab.runInParallel="Parallel zu anderen Makros ausführen"
|
||||
AdvSceneSwitcher.macroTab.onChange="Nur bei Änderung ausführen"
|
||||
AdvSceneSwitcher.macroTab.defaultname="Makro %1"
|
||||
AdvSceneSwitcher.macroTab.defaultGroupName="Gruppe %1"
|
||||
AdvSceneSwitcher.macroTab.removeGroupPopup.text="Sicher, dass \"%1\" und alle zugehörigen Elemente gelöscht werden?"
|
||||
|
|
@ -146,11 +145,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"
|
||||
|
|
@ -216,8 +211,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"
|
||||
|
|
@ -413,7 +408,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"
|
||||
|
|
@ -422,7 +416,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"
|
||||
|
|
@ -458,7 +451,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"
|
||||
|
|
@ -506,9 +499,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"
|
||||
|
|
@ -835,8 +828,6 @@ AdvSceneSwitcher.status.inactive="Inaktiv"
|
|||
AdvSceneSwitcher.running="Plugin läuft"
|
||||
AdvSceneSwitcher.stopped="Plugin gestoppt"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Dies scheint das erste Mal zu sein, dass der Erweiterte Szenenwechsler gestartet wurde.<br>Bitte schaue ins <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> für eine Liste von Anleitungen und Beispielen.<br>Nicht zögern und Fragen im <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">Thread</span></a> des Plugins im OBS-Forum stellen!!</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="Die Entwicklung für dieses Tab wurde gestoppt!\nBitte stattdessen zur Verwendung des Makros-Tab übergehen.\nDieser Hinweis kann im Allgemein-Tab deaktiviert werden."
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="Millisekunden"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -177,7 +178,12 @@ AdvSceneSwitcher.macroTab.run.tooltip="Run all macro actions regardless of condi
|
|||
AdvSceneSwitcher.macroTab.runElse="Run macro (else)"
|
||||
AdvSceneSwitcher.macroTab.runFail="Running \"%1\" failed!\nEither one of the actions failed or the macro is running already.\nDo you want to stop it?"
|
||||
AdvSceneSwitcher.macroTab.runInParallel="Run macro in parallel to other macros"
|
||||
AdvSceneSwitcher.macroTab.onChange="Perform actions only on condition change"
|
||||
AdvSceneSwitcher.macroTab.actionTriggerMode.label="Perform actions:"
|
||||
AdvSceneSwitcher.macroTab.actionTriggerMode.tooltip="Controls when the actions of this macro are performed.\n\n\"Always\" - actions are performed every time the conditions are met.\n\"Macro result changed\" - actions are only performed when the macro transitions between matching and not matching.\n\"Any condition changed\" - actions are only performed when any individual condition changes its result.\n\"Any condition triggered\" - actions are only performed when any individual condition changes from false to true."
|
||||
AdvSceneSwitcher.macroTab.actionTriggerMode.always="always"
|
||||
AdvSceneSwitcher.macroTab.actionTriggerMode.onOverallChange="only when result changes"
|
||||
AdvSceneSwitcher.macroTab.actionTriggerMode.onAnyConditionChange="only when any condition changes"
|
||||
AdvSceneSwitcher.macroTab.actionTriggerMode.onAnyConditionTriggered="only when any condition becomes true"
|
||||
AdvSceneSwitcher.macroTab.defaultname="Macro %1"
|
||||
AdvSceneSwitcher.macroTab.defaultGroupName="Group %1"
|
||||
AdvSceneSwitcher.macroTab.macroNameExists="The name \"%1\" is already used by a macro."
|
||||
|
|
@ -363,11 +369,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"
|
||||
|
|
@ -482,8 +484,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"
|
||||
|
|
@ -671,6 +674,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"
|
||||
|
|
@ -874,6 +905,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"
|
||||
|
|
@ -882,9 +922,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!"
|
||||
|
|
@ -901,7 +942,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."
|
||||
|
|
@ -944,6 +986,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\""
|
||||
|
|
@ -966,6 +1010,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"
|
||||
|
|
@ -985,7 +1072,7 @@ 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"
|
||||
|
|
@ -1000,6 +1087,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"
|
||||
|
|
@ -1010,6 +1101,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"
|
||||
|
|
@ -1082,13 +1174,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"
|
||||
|
|
@ -1248,23 +1343,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"
|
||||
|
|
@ -1285,6 +1409,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."
|
||||
|
|
@ -1390,6 +1516,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"
|
||||
|
|
@ -1659,6 +1786,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!"
|
||||
|
|
@ -2176,8 +2304,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."
|
||||
|
|
@ -2196,6 +2337,8 @@ AdvSceneSwitcher.tempVar.recording.durationSeconds.description="Recording durati
|
|||
AdvSceneSwitcher.tempVar.recording.lastSavePath="Last save path"
|
||||
AdvSceneSwitcher.tempVar.recording.lastSavePath.description="The file path of the last saved recording."
|
||||
|
||||
AdvSceneSwitcher.tempVar.video.similarity="Similarity Rating"
|
||||
AdvSceneSwitcher.tempVar.video.similarity.description="Values range from 0 to 1, where 0 means dissimilar and 1 means highly similar."
|
||||
AdvSceneSwitcher.tempVar.video.patternCount="Pattern count"
|
||||
AdvSceneSwitcher.tempVar.video.patternCount.description="The number of times the given pattern has been found in a given video input frame."
|
||||
AdvSceneSwitcher.tempVar.video.objectCount="Object count"
|
||||
|
|
@ -2206,6 +2349,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"
|
||||
|
|
@ -2335,9 +2480,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"
|
||||
|
||||
|
|
@ -2347,6 +2497,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"
|
||||
|
|
@ -2436,8 +2594,6 @@ AdvSceneSwitcher.status.inactive="Inactive"
|
|||
AdvSceneSwitcher.running="Plugin running"
|
||||
AdvSceneSwitcher.stopped="Plugin stopped"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>This seems to be the first time the Advanced Scene Switcher was started.<br>Please have a look at the <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> for a list of guides and examples.<br>Do not hesitate to ask questions in the plugin's <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">thread</span></a> on the OBS forums!</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="Development for this tab stopped!\nPlease consider transitioning to using Macros instead.\nThis hint can be disabled on the General tab."
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="milliseconds"
|
||||
|
|
@ -2477,6 +2633,37 @@ AdvSceneSwitcher.day.sunday="Sunday"
|
|||
AdvSceneSwitcher.day.none="No day"
|
||||
AdvSceneSwitcher.day.any="Any day"
|
||||
|
||||
# First run wizard
|
||||
FirstRunWizard.windowTitle="Advanced Scene Switcher - Setup Wizard"
|
||||
|
||||
FirstRunWizard.welcome.title="Welcome to Advanced Scene Switcher"
|
||||
FirstRunWizard.welcome.subtitle="Let's create your first automation in a few easy steps."
|
||||
FirstRunWizard.welcome.body="<p>Advanced Scene Switcher lets you build <b>Macros</b> - rules of the form:</p><blockquote><i>When [condition] -> perform [action]</i></blockquote><p>This wizard creates a macro that <b>switches to a chosen OBS scene whenever a specific window comes into focus</b>.</p><p>You can skip at any time. Re-open this wizard later from the <b>General</b> tab inside the Advanced Scene Switcher dialog.</p>"
|
||||
|
||||
FirstRunWizard.scene.title="Choose a Target Scene"
|
||||
FirstRunWizard.scene.subtitle="Which OBS scene should become active when your chosen window comes into focus?"
|
||||
FirstRunWizard.scene.label="Switch to scene:"
|
||||
|
||||
FirstRunWizard.window.title="Choose a Trigger Window"
|
||||
FirstRunWizard.window.subtitle="Enter the title of the window that should trigger the scene switch. Partial matches are fine - \"Firefox\" will match any Firefox window."
|
||||
FirstRunWizard.window.label="Window title contains:"
|
||||
FirstRunWizard.window.placeholder="e.g. \"Firefox\", \"Visual Studio Code\""
|
||||
FirstRunWizard.window.autoDetect="Auto-detect focused window"
|
||||
FirstRunWizard.window.autoDetectTooltip="Click, then switch to the target window. The title will be captured automatically after 3 seconds."
|
||||
FirstRunWizard.window.autoDetectCountdown="Detecting in %1 s - focus your window now..."
|
||||
FirstRunWizard.window.hint="<small>Tip: a short partial title is more robust than the full title, which often changes when you switch tabs.</small>"
|
||||
|
||||
FirstRunWizard.review.title="Review Your Macro"
|
||||
FirstRunWizard.review.subtitle="Click Back to make changes, or Finish to create the macro."
|
||||
FirstRunWizard.review.summary="<b>Macro name:</b> Window -> %1<br><br><b>Condition:</b> Focused window title contains <i>\"%2\"</i> (case-insensitive)<br><br><b>Action:</b> Switch to scene <i>\"%1\"</i>"
|
||||
FirstRunWizard.review.errorTitle="Macro Creation Failed"
|
||||
FirstRunWizard.review.errorBody="The macro could not be created automatically.\n\nYou can create it manually on the Macros tab:\n Condition: Window -> contains \"%1\"\n Action: Switch scene -> \"%2\""
|
||||
|
||||
FirstRunWizard.done.title="You're all set!"
|
||||
FirstRunWizard.done.subtitle="Your first macro has been created and is now active."
|
||||
FirstRunWizard.done.body="<p>You can view and edit it on the <b>Macros</b> tab of the Advanced Scene Switcher dialog.</p><p>To learn more:</p><ul><li><a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\">Plugin wiki</a></li><li><a href=\"https://obsproject.com/forum/resources/automatic-scene-switching.395/\">OBS forum thread</a></li></ul>"
|
||||
|
||||
FirstRunWizard.openButton="Open Setup Wizard..."
|
||||
# This secion is copied from the OBS locale files
|
||||
|
||||
# OBS commonly shared locale
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ AdvSceneSwitcher.macroTab.add="Agregar nueva macro"
|
|||
AdvSceneSwitcher.macroTab.name="Nombre:"
|
||||
AdvSceneSwitcher.macroTab.run="Ejecutar macro"
|
||||
AdvSceneSwitcher.macroTab.runInParallel="Ejecutar macro en paralelo a otras macros"
|
||||
AdvSceneSwitcher.macroTab.onChange="Realizar acciones solo en el cambio de condición"
|
||||
AdvSceneSwitcher.macroTab.defaultname="Macro %1"
|
||||
AdvSceneSwitcher.macroTab.copy="Crear copia"
|
||||
AdvSceneSwitcher.macroTab.expandAll="Expandir todo"
|
||||
|
|
@ -118,9 +117,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"
|
||||
|
|
@ -176,7 +173,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"
|
||||
|
|
@ -335,7 +332,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"
|
||||
|
|
@ -344,7 +340,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"
|
||||
|
|
@ -376,7 +371,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"
|
||||
|
|
@ -424,9 +419,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"
|
||||
|
|
@ -681,8 +676,6 @@ AdvSceneSwitcher.status.inactive="Inactivo"
|
|||
AdvSceneSwitcher.running="Iniciar complemento"
|
||||
AdvSceneSwitcher.stopped="Complemento de detención"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Esta parece ser la primera vez que se inicia el conmutador de escena avanzado.<br>Por favor, eche un vistazo a <a href=\"https:/ /github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> para obtener una lista de guías y ejemplos.<br>No dude en hacer preguntas en el complemento <a href=\"https://obsproject.com /forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">hilo</span></a> en los foros de OBS!</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="¡Se detuvo el desarrollo de esta pestaña!\nConsidere cambiar a macros en su lugar.\nEsta sugerencia se puede desactivar en la pestaña General".
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="milisegundos"
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ AdvSceneSwitcher.macroTab.add="Ajouter une nouvelle macro"
|
|||
AdvSceneSwitcher.macroTab.name="Nom :"
|
||||
AdvSceneSwitcher.macroTab.run="Exécuter la macro"
|
||||
AdvSceneSwitcher.macroTab.runInParallel="Exécuter la macro en parallèle avec d'autres macros"
|
||||
AdvSceneSwitcher.macroTab.onChange="Exécuter des actions uniquement en cas de changement de condition"
|
||||
AdvSceneSwitcher.macroTab.defaultname="Macro %1"
|
||||
AdvSceneSwitcher.macroTab.defaultGroupName="Groupe %1"
|
||||
AdvSceneSwitcher.macroTab.removeSingleMacroPopup.text="Êtes-vous sûr de vouloir supprimer \"%1\" ?"
|
||||
|
|
@ -188,8 +187,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"
|
||||
|
|
@ -282,8 +279,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"
|
||||
|
|
@ -554,7 +551,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"
|
||||
|
|
@ -623,9 +620,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"
|
||||
|
|
@ -1123,8 +1120,6 @@ AdvSceneSwitcher.status.inactive="Inactif"
|
|||
AdvSceneSwitcher.running="Plugin en cours d'exécution"
|
||||
AdvSceneSwitcher.stopped="Plugin arrêté"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Il semble que ce soit la première fois que l'Advanced Scene Switcher est démarré.<br>Veuillez consulter le <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> pour obtenir une liste de guides et d'exemples.<br>N'hésitez pas à poser des questions dans le <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">fil de discussion</span></a> du plugin sur les forums OBS !</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="Le développement de cet onglet est terminé !\nVeuillez envisager de passer à l'utilisation des Macros à la place.\nCette astuce peut être désactivée dans l'onglet Général."
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="millisecondes"
|
||||
|
|
|
|||
|
|
@ -164,7 +164,6 @@ AdvSceneSwitcher.macroTab.run.tooltip="条件に関係なくすべてのマク
|
|||
AdvSceneSwitcher.macroTab.runElse="マクロ実行(else)"
|
||||
AdvSceneSwitcher.macroTab.runFail="\"%1\" の実行に失敗しました!\nいずれかのアクションが失敗したか、マクロがすでに実行されています。\n停止しますか?"
|
||||
AdvSceneSwitcher.macroTab.runInParallel="他のマクロと並行してマクロを実行する"
|
||||
AdvSceneSwitcher.macroTab.onChange="条件変更時のみアクションを実行"
|
||||
AdvSceneSwitcher.macroTab.defaultname="マクロ %1"
|
||||
AdvSceneSwitcher.macroTab.defaultGroupName="グループ %1"
|
||||
AdvSceneSwitcher.macroTab.macroNameExists="名前 \"%1\" は既にマクロで使用されています。"
|
||||
|
|
@ -332,14 +331,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="時間が一致"
|
||||
|
|
@ -454,8 +445,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="プラグインの状態"
|
||||
|
|
@ -932,7 +923,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 と評価された場合のみ"
|
||||
|
|
@ -1021,9 +1012,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="続行"
|
||||
|
|
@ -1191,8 +1182,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="クリップをキャプチャする前にわずかな遅延を追加します"
|
||||
|
|
@ -2252,8 +2250,6 @@ AdvSceneSwitcher.status.inactive="停止中"
|
|||
AdvSceneSwitcher.running="プラグイン実行中"
|
||||
AdvSceneSwitcher.stopped="プラグイン停止しました"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>高機能シーンスイッチャーが初めて起動したようです。<br>ガイドと使用例については <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> をご覧ください。<br>質問は <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">OBSフォーラムのプラグインのスレッド</span></a> で遠慮なくどうぞ!</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="このタブの開発は停止しました!\n代わりにマクロの使用に移行することを検討してください。\nこのヒントは [全般] タブで無効にすることができます。"
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="ミリ秒"
|
||||
|
|
|
|||
|
|
@ -147,7 +147,6 @@ AdvSceneSwitcher.macroTab.run.tooltip="Execute todas as ações da macro indepen
|
|||
AdvSceneSwitcher.macroTab.runElse="Executar macro (alternativa)"
|
||||
AdvSceneSwitcher.macroTab.runFail="Falha ao executar \"%1\"!\nUma das ações falhou ou a macro já está em execução.\nDeseja interrompê-la?"
|
||||
AdvSceneSwitcher.macroTab.runInParallel="Executar macro em paralelo com outras macros"
|
||||
AdvSceneSwitcher.macroTab.onChange="Executar ações apenas quando houver mudança na condição"
|
||||
AdvSceneSwitcher.macroTab.defaultname="Macro %1"
|
||||
AdvSceneSwitcher.macroTab.defaultGroupName="Grupo %1"
|
||||
AdvSceneSwitcher.macroTab.macroNameExists="O nome \"%1\" já está em uso por uma macro."
|
||||
|
|
@ -296,11 +295,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"
|
||||
|
|
@ -401,8 +395,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"
|
||||
|
|
@ -755,7 +749,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!"
|
||||
|
|
@ -772,7 +765,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."
|
||||
|
|
@ -847,7 +839,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"
|
||||
|
|
@ -934,9 +926,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"
|
||||
|
|
@ -1066,7 +1058,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"
|
||||
|
|
@ -1872,8 +1864,6 @@ AdvSceneSwitcher.status.inactive="Inativo"
|
|||
AdvSceneSwitcher.running="Plugin em execução"
|
||||
AdvSceneSwitcher.stopped="Plugin parado"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Parece que esta é a primeira vez que o Advanced Scene Switcher está sendo iniciado.<br>Por favor, consulte o <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> para uma lista de guias e exemplos.<br>Não hesite em fazer perguntas no <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">tópico</span></a> do plugin nos fóruns do OBS!</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="Desenvolvimento para esta aba interrompido!\nPor favor, considere a transição para o uso de Macros em vez disso.\nEsta dica pode ser desativada na aba Geral."
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="milissegundos"
|
||||
|
|
|
|||
|
|
@ -85,8 +85,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 +103,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 +128,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 +135,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="Запустить"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ AdvSceneSwitcher.macroTab.add="Yeni Makro ekle"
|
|||
AdvSceneSwitcher.macroTab.name="İsim:"
|
||||
AdvSceneSwitcher.macroTab.run="Makro Çalıştırma"
|
||||
AdvSceneSwitcher.macroTab.runInParallel="Makroyu diğer makrolara paralel olarak çalıştırın"
|
||||
AdvSceneSwitcher.macroTab.onChange="Eylemleri yalnızca koşul değişikliğinde gerçekleştirin"
|
||||
AdvSceneSwitcher.macroTab.defaultname="Makro %1"
|
||||
AdvSceneSwitcher.macroTab.copy="Kopya oluştur"
|
||||
AdvSceneSwitcher.macroTab.expandAll="Hepsini Genişlet"
|
||||
|
|
@ -108,9 +107,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ı "
|
||||
|
|
@ -153,7 +150,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"
|
||||
|
|
@ -266,7 +263,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"
|
||||
|
|
@ -274,7 +270,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"
|
||||
|
|
@ -305,7 +300,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:"
|
||||
|
|
@ -342,8 +337,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"
|
||||
|
|
@ -587,8 +582,6 @@ AdvSceneSwitcher.status.inactive="İnaktif"
|
|||
AdvSceneSwitcher.running="Eklenti çalışıyor"
|
||||
AdvSceneSwitcher.stopped="Eklenti durdu"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Gelişmiş Sahne Değiştirici ilk kez başlatılıyor gibi görünüyor.<br>Lütfen <a href=\"https://github.com/ adresine bir göz atın. WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> için kılavuzlar ve örnekler listesi.<br>Yapmayın. eklentinin <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:# sayfasında soru sormaktan çekinmeyin OBS forumlarında 268bd2;\">konu</span></a>!</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="millisaniye"
|
||||
AdvSceneSwitcher.unit.seconds="saniye"
|
||||
AdvSceneSwitcher.unit.minutes="dakika"
|
||||
|
|
|
|||
|
|
@ -151,7 +151,6 @@ AdvSceneSwitcher.macroTab.run.tooltip="无论条件如何,都运行所有宏
|
|||
AdvSceneSwitcher.macroTab.runElse="运行宏(不满足条件)"
|
||||
AdvSceneSwitcher.macroTab.runFail="运行 \"%1\" 失败!\n要么其中一个操作失败,要么宏已在运行中.\n你想停止它吗?"
|
||||
AdvSceneSwitcher.macroTab.runInParallel="与其他宏并行运行宏"
|
||||
AdvSceneSwitcher.macroTab.onChange="仅在条件结果发生变化时执行操作(条件结果不变时只执行一次操作)"
|
||||
AdvSceneSwitcher.macroTab.defaultname="宏 %1"
|
||||
AdvSceneSwitcher.macroTab.defaultGroupName="分组 %1"
|
||||
AdvSceneSwitcher.macroTab.macroNameExists="名称 \"%1\" 已被宏使用."
|
||||
|
|
@ -319,11 +318,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="时间限制匹配"
|
||||
|
|
@ -429,8 +424,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="插件状态"
|
||||
|
|
@ -814,7 +809,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="警告:更改回放缓存时长上限,仅适用于下次回放缓存开启时!"
|
||||
|
|
@ -831,7 +825,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="请注意,如果不勾选此选项,宏属性将不起作用,因为进程的启动会脱离逻辑的其他部分,因此无法对其进行控制."
|
||||
|
|
@ -906,7 +899,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="仅当条件结果为假时"
|
||||
|
|
@ -995,9 +988,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="继续"
|
||||
|
|
@ -1140,10 +1133,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="在捕捉视频片段前稍加延迟"
|
||||
|
|
@ -2116,8 +2116,6 @@ AdvSceneSwitcher.status.inactive="已停止"
|
|||
AdvSceneSwitcher.running="插件正在运行"
|
||||
AdvSceneSwitcher.stopped="插件已停止"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>这似乎是您第一次启动高级场景切换器.<br>请看一下 <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> 查看指南和示例列表.<br>如果有问题,在在OBS论坛插件帖子内提问 <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">thread</span></a></p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="此选项卡的开发已停止!请考虑转换为使用宏来代替。\n可以在“常规”选项卡上禁用此提示."
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="毫秒"
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>962</width>
|
||||
<height>1160</height>
|
||||
<height>1190</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_19">
|
||||
|
|
@ -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>
|
||||
|
|
@ -565,6 +572,13 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="openSetupWizard">
|
||||
<property name="text">
|
||||
<string>FirstRunWizard.openButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
|
|
@ -590,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>
|
||||
|
|
@ -786,25 +800,19 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="runMacroOnChange">
|
||||
<property name="toolTip">
|
||||
<string>AdvSceneSwitcher.macroTab.onChange</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="text">
|
||||
<string/>
|
||||
<string>AdvSceneSwitcher.macroTab.actionTriggerMode.label</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_18">
|
||||
<widget class="QComboBox" name="actionTriggerMode">
|
||||
<property name="toolTip">
|
||||
<string>AdvSceneSwitcher.macroTab.onChange</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.macroTab.onChange</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
<string>AdvSceneSwitcher.macroTab.actionTriggerMode.tooltip</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
@ -4118,7 +4126,7 @@
|
|||
<tabstop>macroName</tabstop>
|
||||
<tabstop>runMacro</tabstop>
|
||||
<tabstop>runMacroInParallel</tabstop>
|
||||
<tabstop>runMacroOnChange</tabstop>
|
||||
<tabstop>actionTriggerMode</tabstop>
|
||||
<tabstop>macroSettings</tabstop>
|
||||
<tabstop>macroEdit</tabstop>
|
||||
<tabstop>sceneGroups</tabstop>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -66,10 +66,12 @@ 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();
|
||||
void on_threadPriority_currentTextChanged(const QString &text);
|
||||
void on_openSetupWizard_clicked();
|
||||
|
||||
/* --- End of legacy tab section --- */
|
||||
|
||||
|
|
@ -107,7 +109,7 @@ public slots:
|
|||
void on_macroDown_clicked() const;
|
||||
void on_macroName_editingFinished();
|
||||
void on_runMacroInParallel_stateChanged(int value) const;
|
||||
void on_runMacroOnChange_stateChanged(int value) const;
|
||||
void on_actionTriggerMode_currentIndexChanged(int index) const;
|
||||
void MacroSelectionChanged();
|
||||
void ShowMacroContextMenu(const QPoint &);
|
||||
void CopyMacro();
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
#include "advanced-scene-switcher.hpp"
|
||||
#include "crash-handler.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "filter-combo-box.hpp"
|
||||
#include "first-run-wizard.hpp"
|
||||
#include "layout-helpers.hpp"
|
||||
#include "macro.hpp"
|
||||
#include "macro-search.hpp"
|
||||
#include "macro-settings.hpp"
|
||||
#include "path-helpers.hpp"
|
||||
#include "selection-helpers.hpp"
|
||||
#include "source-helpers.hpp"
|
||||
#include "splitter-helpers.hpp"
|
||||
#include "status-control.hpp"
|
||||
#include "switcher-data.hpp"
|
||||
#include "tab-helpers.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "variable.hpp"
|
||||
#include "version.h"
|
||||
|
||||
|
|
@ -195,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(
|
||||
|
|
@ -361,14 +370,46 @@ void AdvSceneSwitcher::RestoreWindowGeo()
|
|||
}
|
||||
}
|
||||
|
||||
static void renameMacroIfNecessary(const std::shared_ptr<Macro> ¯o)
|
||||
{
|
||||
if (!GetMacroByName(macro->Name().c_str())) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto name = macro->Name();
|
||||
int i = 2;
|
||||
while (GetMacroByName((name + " " + std::to_string(i)).c_str())) {
|
||||
i++;
|
||||
}
|
||||
|
||||
macro->SetName(name + " " + std::to_string(i));
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::CheckFirstTimeSetup()
|
||||
{
|
||||
if (switcher->firstBoot && !switcher->disableHints) {
|
||||
switcher->firstBoot = false;
|
||||
DisplayMessage(
|
||||
obs_module_text("AdvSceneSwitcher.firstBootMessage"));
|
||||
switcher->Start();
|
||||
if (!IsFirstRun() || !GetTopLevelMacros().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto macro = FirstRunWizard::ShowWizard(this);
|
||||
if (macro) {
|
||||
renameMacroIfNecessary(macro);
|
||||
QTimer::singleShot(0, this,
|
||||
[this, macro]() { ui->macros->Add(macro); });
|
||||
}
|
||||
switcher->Start();
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_openSetupWizard_clicked()
|
||||
{
|
||||
auto macro = FirstRunWizard::ShowWizard(this);
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
renameMacroIfNecessary(macro);
|
||||
ui->macros->Add(macro);
|
||||
ui->tabWidget->setCurrentWidget(ui->macroTab);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_tabWidget_currentChanged(int)
|
||||
|
|
@ -915,6 +956,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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,17 @@ 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);
|
||||
}
|
||||
|
||||
_nestedMacro->setVisible(action ==
|
||||
MacroActionMacro::Action::NESTED_MACRO);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@ namespace advss {
|
|||
|
||||
const std::string MacroActionVariable::id = "variable";
|
||||
|
||||
std::vector<TempVariableRef> MacroActionVariable::GetTempVarRefs() const
|
||||
{
|
||||
if (!_tempVar.HasValidID()) {
|
||||
return {};
|
||||
}
|
||||
return {_tempVar};
|
||||
}
|
||||
|
||||
bool MacroActionVariable::_registered = MacroActionFactory::Register(
|
||||
MacroActionVariable::id,
|
||||
{MacroActionVariable::Create, MacroActionVariableEdit::Create,
|
||||
|
|
@ -1450,6 +1458,7 @@ void MacroActionVariableEdit::SelectionChanged(const TempVariableRef &var)
|
|||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_tempVar = var;
|
||||
IncrementTempVarInUseGeneration();
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@ namespace advss {
|
|||
|
||||
const std::string MacroConditionTempVar::id = "temp_var";
|
||||
|
||||
std::vector<TempVariableRef> MacroConditionTempVar::GetTempVarRefs() const
|
||||
{
|
||||
if (!_tempVar.HasValidID()) {
|
||||
return {};
|
||||
}
|
||||
return {_tempVar};
|
||||
}
|
||||
|
||||
bool MacroConditionTempVar::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionTempVar::id,
|
||||
{MacroConditionTempVar::Create, MacroConditionTempVarEdit::Create,
|
||||
|
|
@ -266,6 +274,7 @@ void MacroConditionTempVarEdit::VariableChanged(const TempVariableRef &var)
|
|||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_tempVar = var;
|
||||
IncrementTempVarInUseGeneration();
|
||||
}
|
||||
|
||||
void MacroConditionTempVarEdit::Variable2Changed(const QString &text)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,17 @@ MacroCondition::MacroCondition(Macro *m, bool supportsVariableValue)
|
|||
{
|
||||
}
|
||||
|
||||
bool MacroCondition::EvaluateCondition()
|
||||
{
|
||||
bool newValue = CheckCondition();
|
||||
_changed = _previousValue.has_value() && (*_previousValue != newValue);
|
||||
const bool negate = _logic.IsNegationType(GetLogicType());
|
||||
_risingEdge = _changed &&
|
||||
((!negate && newValue) || (negate && !newValue));
|
||||
_previousValue = newValue;
|
||||
return newValue;
|
||||
}
|
||||
|
||||
bool MacroCondition::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroSegment::Save(obj);
|
||||
|
|
|
|||
|
|
@ -4,13 +4,19 @@
|
|||
#include "duration-modifier.hpp"
|
||||
#include "macro-ref.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class EXPORT MacroCondition : public MacroSegment {
|
||||
public:
|
||||
MacroCondition(Macro *m, bool supportsVariableValue = false);
|
||||
virtual ~MacroCondition() = default;
|
||||
virtual bool CheckCondition() = 0;
|
||||
|
||||
bool EvaluateCondition();
|
||||
bool HasChanged() const { return _changed; }
|
||||
bool IsRisingEdge() const { return _risingEdge; }
|
||||
|
||||
virtual bool Save(obs_data_t *obj) const = 0;
|
||||
virtual bool Load(obs_data_t *obj) = 0;
|
||||
|
||||
|
|
@ -28,9 +34,15 @@ public:
|
|||
|
||||
static std::string_view GetDefaultID();
|
||||
|
||||
protected:
|
||||
virtual bool CheckCondition() = 0;
|
||||
|
||||
private:
|
||||
Logic _logic = Logic(Logic::Type::ROOT_NONE);
|
||||
DurationModifier _durationModifier;
|
||||
std::optional<bool> _previousValue;
|
||||
bool _changed = false;
|
||||
bool _risingEdge = false;
|
||||
};
|
||||
|
||||
class EXPORT MacroRefCondition : virtual public MacroCondition {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@ namespace advss {
|
|||
class Macro;
|
||||
class MacroSegment;
|
||||
|
||||
/*******************************************************************************
|
||||
* Advanced Scene Switcher window
|
||||
*******************************************************************************/
|
||||
class MacroEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@
|
|||
|
||||
namespace advss {
|
||||
|
||||
std::vector<TempVariableRef> MacroSegment::GetTempVarRefs() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
MacroSegment::MacroSegment(Macro *m, bool supportsVariableValue)
|
||||
: _macro(m),
|
||||
_supportsVariableValue(supportsVariableValue)
|
||||
|
|
@ -152,6 +157,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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ public:
|
|||
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;
|
||||
|
|
@ -54,6 +55,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 +66,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>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ void MacroSelection::HideSelectedMacro()
|
|||
return;
|
||||
}
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
const auto m = ssWindow->ui->macros->GetCurrentMacro();
|
||||
if (!m) {
|
||||
return;
|
||||
|
|
@ -61,6 +62,7 @@ void MacroSelection::HideSelectedMacro()
|
|||
}
|
||||
|
||||
qobject_cast<QListView *>(view())->setRowHidden(idx, true);
|
||||
#endif // !UNIT_TEST
|
||||
}
|
||||
|
||||
void MacroSelection::HideGroups()
|
||||
|
|
|
|||
|
|
@ -474,14 +474,17 @@ void AdvSceneSwitcher::on_runMacroInParallel_stateChanged(int value) const
|
|||
macro->SetRunInParallel(value);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_runMacroOnChange_stateChanged(int value) const
|
||||
void AdvSceneSwitcher::on_actionTriggerMode_currentIndexChanged(int index) const
|
||||
{
|
||||
auto macro = GetSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
macro->SetMatchOnChange(value);
|
||||
const auto mode = static_cast<Macro::ActionTriggerMode>(
|
||||
ui->actionTriggerMode->itemData(index).toInt());
|
||||
macro->SetActionTriggerMode(mode);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable) const
|
||||
|
|
@ -489,7 +492,7 @@ void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable) const
|
|||
ui->macroName->setDisabled(disable);
|
||||
ui->runMacro->setDisabled(disable);
|
||||
ui->runMacroInParallel->setDisabled(disable);
|
||||
ui->runMacroOnChange->setDisabled(disable);
|
||||
ui->actionTriggerMode->setDisabled(disable);
|
||||
ui->macroEdit->SetControlsDisabled(disable);
|
||||
}
|
||||
|
||||
|
|
@ -519,10 +522,12 @@ void AdvSceneSwitcher::MacroSelectionChanged()
|
|||
{
|
||||
const QSignalBlocker b1(ui->macroName);
|
||||
const QSignalBlocker b2(ui->runMacroInParallel);
|
||||
const QSignalBlocker b3(ui->runMacroOnChange);
|
||||
const QSignalBlocker b3(ui->actionTriggerMode);
|
||||
ui->macroName->setText(macro->Name().c_str());
|
||||
ui->runMacroInParallel->setChecked(macro->RunInParallel());
|
||||
ui->runMacroOnChange->setChecked(macro->MatchOnChange());
|
||||
ui->actionTriggerMode->setCurrentIndex(
|
||||
ui->actionTriggerMode->findData(static_cast<int>(
|
||||
macro->GetActionTriggerMode())));
|
||||
}
|
||||
|
||||
macro->ResetUIHelpers();
|
||||
|
|
@ -552,9 +557,9 @@ void AdvSceneSwitcher::HighlightOnChange() const
|
|||
return;
|
||||
}
|
||||
|
||||
if (macro->OnChangePreventedActionsSince(
|
||||
if (macro->ActionTriggerModePreventedActionsSince(
|
||||
lastOnChangeHighlightCheckTime)) {
|
||||
HighlightWidget(ui->runMacroOnChange, Qt::yellow,
|
||||
HighlightWidget(ui->actionTriggerMode, Qt::yellow,
|
||||
Qt::transparent, true);
|
||||
}
|
||||
|
||||
|
|
@ -665,23 +670,49 @@ 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,
|
||||
ui->macroSearchShowSettings,
|
||||
[this]() { ui->macros->RefreshFilter(); });
|
||||
|
||||
static const std::vector<
|
||||
std::pair<Macro::ActionTriggerMode, const char *>>
|
||||
actionTriggerModes = {
|
||||
{Macro::ActionTriggerMode::ALWAYS,
|
||||
"AdvSceneSwitcher.macroTab.actionTriggerMode.always"},
|
||||
{Macro::ActionTriggerMode::MACRO_RESULT_CHANGED,
|
||||
"AdvSceneSwitcher.macroTab.actionTriggerMode.onOverallChange"},
|
||||
{Macro::ActionTriggerMode::ANY_CONDITION_CHANGED,
|
||||
"AdvSceneSwitcher.macroTab.actionTriggerMode.onAnyConditionChange"},
|
||||
{Macro::ActionTriggerMode::ANY_CONDITION_TRIGGERED,
|
||||
"AdvSceneSwitcher.macroTab.actionTriggerMode.onAnyConditionTriggered"},
|
||||
};
|
||||
|
||||
for (const auto &[mode, name] : actionTriggerModes) {
|
||||
ui->actionTriggerMode->addItem(obs_module_text(name),
|
||||
static_cast<int>(mode));
|
||||
}
|
||||
|
||||
ui->macroListBox->setSizePolicy(QSizePolicy::Ignored,
|
||||
QSizePolicy::Preferred);
|
||||
ui->macroListBox->setMinimumWidth(0);
|
||||
ui->macroEditGroup->setSizePolicy(QSizePolicy::Ignored,
|
||||
QSizePolicy::Preferred);
|
||||
ui->macroEditGroup->setMinimumWidth(0);
|
||||
|
||||
if (shouldRestoreSplitter(
|
||||
switcher->macroListMacroEditSplitterPosition)) {
|
||||
ui->macroListMacroEditSplitter->setSizes(
|
||||
switcher->macroListMacroEditSplitterPosition);
|
||||
} else {
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
const auto totalWidth =
|
||||
ui->macroListMacroEditSplitter->width();
|
||||
ui->macroListMacroEditSplitter->setSizes(
|
||||
{totalWidth / 5, totalWidth * 4 / 5});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
|
||||
|
|
|
|||
|
|
@ -224,7 +224,8 @@ void MacroTreeItem::HighlightIfExecuted()
|
|||
|
||||
if (!wasHighlighted &&
|
||||
_lastHighlightCheckTime.time_since_epoch().count() != 0 &&
|
||||
_macro->OnChangePreventedActionsSince(_lastHighlightCheckTime)) {
|
||||
_macro->ActionTriggerModePreventedActionsSince(
|
||||
_lastHighlightCheckTime)) {
|
||||
HighlightWidget(this, Qt::yellow, QColor(0, 0, 0, 0), true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ static bool checkCondition(const std::shared_ptr<MacroCondition> &condition)
|
|||
const auto startTime = std::chrono::high_resolution_clock::now();
|
||||
bool conditionMatched = false;
|
||||
condition->WithLock([&condition, &conditionMatched]() {
|
||||
conditionMatched = condition->CheckCondition();
|
||||
conditionMatched = condition->EvaluateCondition();
|
||||
});
|
||||
const auto endTime = std::chrono::high_resolution_clock::now();
|
||||
const auto timeSpent = endTime - startTime;
|
||||
|
|
@ -241,12 +241,36 @@ bool Macro::CheckConditions(bool ignorePause)
|
|||
|
||||
vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched);
|
||||
|
||||
_conditionSateChanged = _lastMatched != _matched;
|
||||
_actionModeMatch = false;
|
||||
switch (_actionTriggerMode) {
|
||||
case Macro::ActionTriggerMode::ALWAYS:
|
||||
_actionModeMatch = true;
|
||||
break;
|
||||
case Macro::ActionTriggerMode::MACRO_RESULT_CHANGED:
|
||||
_actionModeMatch = _lastMatched != _matched;
|
||||
break;
|
||||
case Macro::ActionTriggerMode::ANY_CONDITION_CHANGED:
|
||||
for (const auto &condition : _conditions) {
|
||||
if (condition->HasChanged()) {
|
||||
_actionModeMatch = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Macro::ActionTriggerMode::ANY_CONDITION_TRIGGERED:
|
||||
for (const auto &condition : _conditions) {
|
||||
if (condition->IsRisingEdge()) {
|
||||
_actionModeMatch = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const bool hasActionsToExecute = _matched ? (_actions.size() > 0)
|
||||
: (_elseActions.size() > 0);
|
||||
if (!_conditionSateChanged && _performActionsOnChange &&
|
||||
hasActionsToExecute) {
|
||||
_lastOnChangeActionsPreventedTime =
|
||||
if (!_actionModeMatch && hasActionsToExecute) {
|
||||
_lastActionRunModePreventTime =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
|
|
@ -306,9 +330,9 @@ bool Macro::WasExecutedSince(const TimePoint &time) const
|
|||
return _lastExecutionTime > time;
|
||||
}
|
||||
|
||||
bool Macro::OnChangePreventedActionsSince(const TimePoint &time) const
|
||||
bool Macro::ActionTriggerModePreventedActionsSince(const TimePoint &time) const
|
||||
{
|
||||
return _lastOnChangeActionsPreventedTime > time;
|
||||
return _lastActionRunModePreventTime > time;
|
||||
}
|
||||
|
||||
Macro::TimePoint Macro::GetLastExecutionTime() const
|
||||
|
|
@ -342,10 +366,9 @@ bool Macro::ShouldRunActions() const
|
|||
|
||||
const bool hasActionsToExecute =
|
||||
!_paused && (_matched || _elseActions.size() > 0) &&
|
||||
(!_performActionsOnChange || _conditionSateChanged);
|
||||
_actionModeMatch;
|
||||
|
||||
if (VerboseLoggingEnabled() && _performActionsOnChange &&
|
||||
!_conditionSateChanged) {
|
||||
if (VerboseLoggingEnabled() && !_actionModeMatch) {
|
||||
if (_matched && _actions.size() > 0) {
|
||||
blog(LOG_INFO, "skip actions for Macro %s (on change)",
|
||||
_name.c_str());
|
||||
|
|
@ -376,10 +399,24 @@ void Macro::ResetTimers()
|
|||
_lastExecutionTime = {};
|
||||
}
|
||||
|
||||
void Macro::SetActionTriggerMode(ActionTriggerMode mode)
|
||||
{
|
||||
_actionTriggerMode = mode;
|
||||
}
|
||||
|
||||
Macro::ActionTriggerMode Macro::GetActionTriggerMode() const
|
||||
{
|
||||
return _actionTriggerMode;
|
||||
}
|
||||
|
||||
bool Macro::RunActionsHelper(
|
||||
const std::deque<std::shared_ptr<MacroAction>> &actionsToRun,
|
||||
bool ignorePause)
|
||||
{
|
||||
if (_paused && !ignorePause) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create copy of action list as elements might be removed, inserted, or
|
||||
// reordered while actions are currently being executed.
|
||||
auto actions = actionsToRun;
|
||||
|
|
@ -429,11 +466,6 @@ bool Macro::WasPausedSince(const TimePoint &time) const
|
|||
return _lastUnpauseTime > time;
|
||||
}
|
||||
|
||||
void Macro::SetMatchOnChange(bool onChange)
|
||||
{
|
||||
_performActionsOnChange = onChange;
|
||||
}
|
||||
|
||||
void Macro::SetStopActionsIfNotDone(bool stopActionsIfNotDone)
|
||||
{
|
||||
_stopActionsIfNotDone = stopActionsIfNotDone;
|
||||
|
|
@ -754,7 +786,8 @@ bool Macro::Save(obs_data_t *obj, bool saveForCopy) const
|
|||
obs_data_set_bool(obj, "pause", _paused);
|
||||
obs_data_set_bool(obj, "parallel", _runInParallel);
|
||||
obs_data_set_bool(obj, "checkConditionsInParallel", _checkInParallel);
|
||||
obs_data_set_bool(obj, "onChange", _performActionsOnChange);
|
||||
obs_data_set_int(obj, "actionTriggerMode",
|
||||
static_cast<int>(_actionTriggerMode));
|
||||
obs_data_set_bool(obj, "skipExecOnStart", _skipExecOnStart);
|
||||
obs_data_set_bool(obj, "stopActionsIfNotDone", _stopActionsIfNotDone);
|
||||
obs_data_set_bool(obj, "useShortCircuitEvaluation",
|
||||
|
|
@ -840,7 +873,15 @@ bool Macro::Load(obs_data_t *obj)
|
|||
}
|
||||
_runInParallel = obs_data_get_bool(obj, "parallel");
|
||||
_checkInParallel = obs_data_get_bool(obj, "checkConditionsInParallel");
|
||||
_performActionsOnChange = obs_data_get_bool(obj, "onChange");
|
||||
if (obs_data_has_user_value(obj, "onChange")) {
|
||||
const bool onChange = obs_data_get_bool(obj, "onChange");
|
||||
_actionTriggerMode =
|
||||
onChange ? ActionTriggerMode::MACRO_RESULT_CHANGED
|
||||
: ActionTriggerMode::ALWAYS;
|
||||
} else {
|
||||
_actionTriggerMode = static_cast<ActionTriggerMode>(
|
||||
obs_data_get_int(obj, "actionTriggerMode"));
|
||||
}
|
||||
_skipExecOnStart = obs_data_get_bool(obj, "skipExecOnStart");
|
||||
_stopActionsIfNotDone = obs_data_get_bool(obj, "stopActionsIfNotDone");
|
||||
_useShortCircuitEvaluation =
|
||||
|
|
@ -1100,9 +1141,11 @@ void Macro::ClearHotkeys() const
|
|||
void setHotkeyDescriptionHelper(const char *formatModuleText,
|
||||
const std::string name, const obs_hotkey_id id)
|
||||
{
|
||||
#ifndef UNIT_TEST
|
||||
QString format{obs_module_text(formatModuleText)};
|
||||
QString hotkeyDesc = format.arg(QString::fromStdString(name));
|
||||
obs_hotkey_set_description(id, hotkeyDesc.toStdString().c_str());
|
||||
#endif // !UNIT_TEST
|
||||
}
|
||||
|
||||
void Macro::SetHotkeysDesc() const
|
||||
|
|
|
|||
|
|
@ -26,6 +26,16 @@ class Macro {
|
|||
|
||||
public:
|
||||
enum class PauseStateSaveBehavior { PERSIST, PAUSE, UNPAUSE };
|
||||
enum class ActionTriggerMode {
|
||||
// Trigger always
|
||||
ALWAYS,
|
||||
// Trigger when macro match result flips
|
||||
MACRO_RESULT_CHANGED,
|
||||
// Trigger when any individual condition changes
|
||||
ANY_CONDITION_CHANGED,
|
||||
// Trigger when any individual condition evaluates to true
|
||||
ANY_CONDITION_TRIGGERED,
|
||||
};
|
||||
|
||||
Macro(const std::string &name = "");
|
||||
Macro(const std::string &name, const GlobalMacroSettings &settings);
|
||||
|
|
@ -54,8 +64,8 @@ public:
|
|||
bool GetStop() const { return _stop; }
|
||||
void ResetTimers();
|
||||
|
||||
void SetMatchOnChange(bool onChange);
|
||||
bool MatchOnChange() const { return _performActionsOnChange; }
|
||||
void SetActionTriggerMode(ActionTriggerMode);
|
||||
ActionTriggerMode GetActionTriggerMode() const;
|
||||
|
||||
void SetSkipExecOnStart(bool skip) { _skipExecOnStart = skip; }
|
||||
bool SkipExecOnStart() const { return _skipExecOnStart; }
|
||||
|
|
@ -137,7 +147,7 @@ public:
|
|||
const QList<int> &GetElseActionSplitterPosition() const;
|
||||
bool HasValidSplitterPositions() const;
|
||||
bool WasExecutedSince(const TimePoint &) const;
|
||||
bool OnChangePreventedActionsSince(const TimePoint &) const;
|
||||
bool ActionTriggerModePreventedActionsSince(const TimePoint &) const;
|
||||
TimePoint GetLastExecutionTime() const;
|
||||
void ResetUIHelpers();
|
||||
|
||||
|
|
@ -169,7 +179,7 @@ private:
|
|||
TimePoint _lastCheckTime{};
|
||||
TimePoint _lastUnpauseTime{};
|
||||
TimePoint _lastExecutionTime{};
|
||||
TimePoint _lastOnChangeActionsPreventedTime{};
|
||||
TimePoint _lastActionRunModePreventTime{};
|
||||
std::vector<std::thread> _helperThreads;
|
||||
|
||||
std::deque<std::shared_ptr<MacroCondition>> _conditions;
|
||||
|
|
@ -184,14 +194,13 @@ private:
|
|||
bool _useShortCircuitEvaluation = false;
|
||||
bool _useCustomConditionCheckInterval = false;
|
||||
Duration _customConditionCheckInterval = 0.3;
|
||||
bool _conditionSateChanged = false;
|
||||
bool _actionModeMatch = false;
|
||||
|
||||
bool _runInParallel = false;
|
||||
bool _checkInParallel = false;
|
||||
bool _matched = false;
|
||||
std::future<void> _conditionCheckFuture;
|
||||
bool _lastMatched = false;
|
||||
bool _performActionsOnChange = true;
|
||||
bool _skipExecOnStart = false;
|
||||
bool _stopActionsIfNotDone = false;
|
||||
bool _paused = false;
|
||||
|
|
@ -201,6 +210,9 @@ private:
|
|||
obs_hotkey_id _unpauseHotkey = OBS_INVALID_HOTKEY_ID;
|
||||
obs_hotkey_id _togglePauseHotkey = OBS_INVALID_HOTKEY_ID;
|
||||
|
||||
ActionTriggerMode _actionTriggerMode =
|
||||
ActionTriggerMode::MACRO_RESULT_CHANGED;
|
||||
|
||||
PauseStateSaveBehavior _pauseSaveBehavior =
|
||||
PauseStateSaveBehavior::PERSIST;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ bool SwitcherData::VersionChanged(obs_data_t *obj, std::string currentVersion)
|
|||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
return false;
|
||||
}
|
||||
switcher->firstBoot = false;
|
||||
|
||||
std::string previousVersion = obs_data_get_string(obj, "version");
|
||||
return previousVersion != currentVersion;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@ public:
|
|||
bool stop = false;
|
||||
std::condition_variable cv;
|
||||
|
||||
bool firstBoot = true;
|
||||
bool transitionActive = false;
|
||||
bool sceneCollectionStop = false;
|
||||
bool obsIsShuttingDown = false;
|
||||
|
|
|
|||
|
|
@ -54,31 +54,18 @@ void AskForBackup(obs_data_t *settings)
|
|||
// or crashing.
|
||||
// Therefore, we ask the user whether they want to back up the settings
|
||||
// asynchronously.
|
||||
//
|
||||
// On macOS, an additional QTimer::singleShot wrapper is required for
|
||||
// this to work correctly.
|
||||
|
||||
auto json = obs_data_get_json(settings);
|
||||
static QString jsonQString = json ? json : "";
|
||||
|
||||
static const auto askForBackupWrapper = [](void *) {
|
||||
#ifdef __APPLE__
|
||||
QTimer::singleShot(0,
|
||||
static_cast<QMainWindow *>(
|
||||
obs_frontend_get_main_window()),
|
||||
[]() {
|
||||
#endif
|
||||
showBackupDialogs(jsonQString);
|
||||
#ifdef __APPLE__
|
||||
});
|
||||
#endif
|
||||
showBackupDialogs(jsonQString);
|
||||
};
|
||||
|
||||
std::thread t([]() {
|
||||
AddFinishedLoadingStep([]() {
|
||||
obs_queue_task(OBS_TASK_UI, askForBackupWrapper, nullptr,
|
||||
false);
|
||||
});
|
||||
t.detach();
|
||||
}
|
||||
|
||||
void BackupSettingsOfCurrentVersion()
|
||||
|
|
|
|||
|
|
@ -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,29 @@ static constexpr bool handleUncleanShutdown = true;
|
|||
#endif
|
||||
|
||||
static bool wasCleanShutdown = false;
|
||||
static bool suppressCrashDialog = false;
|
||||
|
||||
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;
|
||||
}();
|
||||
|
||||
|
|
@ -108,8 +128,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,31 +172,22 @@ 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.
|
||||
// Therefore, we ask the user whether they want to start the plugin
|
||||
// asynchronously.
|
||||
//
|
||||
// On macOS, an additional QTimer::singleShot wrapper is required for
|
||||
// this to work correctly.
|
||||
static const auto showDialogWrapper = [](void *) {
|
||||
#ifdef __APPLE__
|
||||
QTimer::singleShot(0,
|
||||
static_cast<QMainWindow *>(
|
||||
obs_frontend_get_main_window()),
|
||||
[]() {
|
||||
#endif
|
||||
askForStartupSkip();
|
||||
#ifdef __APPLE__
|
||||
});
|
||||
#endif
|
||||
askForStartupSkip();
|
||||
};
|
||||
|
||||
std::thread t([]() {
|
||||
AddFinishedLoadingStep([]() {
|
||||
obs_queue_task(OBS_TASK_UI, showDialogWrapper, nullptr, false);
|
||||
});
|
||||
t.detach();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,4 +4,7 @@ namespace advss {
|
|||
|
||||
bool ShouldSkipPluginStartOnUncleanShutdown();
|
||||
|
||||
bool GetSuppressCrashDialog();
|
||||
void SetSuppressCrashDialog(bool suppress);
|
||||
|
||||
} // namespace advss
|
||||
|
|
|
|||
|
|
@ -1,12 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
|
||||
#define EXPORT
|
||||
#define ADVSS_EXPORT
|
||||
|
||||
#else
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define EXPORT __declspec(dllexport)
|
||||
#else
|
||||
|
|
@ -19,5 +12,3 @@
|
|||
#else
|
||||
#define ADVSS_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
||||
|
||||
#endif // UNIT_TEST
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
463
lib/utils/first-run-wizard.cpp
Normal file
463
lib/utils/first-run-wizard.cpp
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
#include "first-run-wizard.hpp"
|
||||
|
||||
#include "log-helper.hpp"
|
||||
#include "macro.hpp"
|
||||
#include "macro-action-factory.hpp"
|
||||
#include "macro-condition-factory.hpp"
|
||||
#include "macro-settings.hpp"
|
||||
#include "platform-funcs.hpp"
|
||||
#include "selection-helpers.hpp"
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <obs-data.h>
|
||||
#include <util/config-file.h>
|
||||
|
||||
#include <QFrame>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QRegularExpression>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
static constexpr char kConditionIdWindow[] = "window";
|
||||
static constexpr char kActionIdSceneSwitch[] = "scene_switch";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// OBS global config helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
static constexpr char kConfigSection[] = "AdvancedSceneSwitcher";
|
||||
static constexpr char kFirstRunKey[] = "firstRun";
|
||||
|
||||
bool IsFirstRun()
|
||||
{
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(31, 0, 0)
|
||||
config_t *cfg = obs_frontend_get_user_config();
|
||||
#else
|
||||
config_t *cfg = obs_frontend_get_global_config();
|
||||
#endif
|
||||
if (!config_has_user_value(cfg, kConfigSection, kFirstRunKey)) {
|
||||
return true;
|
||||
}
|
||||
return config_get_bool(cfg, kConfigSection, kFirstRunKey);
|
||||
}
|
||||
|
||||
static void WriteFirstRun(bool value)
|
||||
{
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(31, 0, 0)
|
||||
config_t *cfg = obs_frontend_get_user_config();
|
||||
#else
|
||||
config_t *cfg = obs_frontend_get_global_config();
|
||||
#endif
|
||||
config_set_bool(cfg, kConfigSection, kFirstRunKey, value);
|
||||
config_save_safe(cfg, "tmp", nullptr);
|
||||
}
|
||||
|
||||
static QString DetectFocusedWindow()
|
||||
{
|
||||
return QString::fromStdString(GetCurrentWindowTitle());
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// WelcomePage
|
||||
// ===========================================================================
|
||||
|
||||
WelcomePage::WelcomePage(QWidget *parent) : QWizardPage(parent)
|
||||
{
|
||||
setTitle(obs_module_text("FirstRunWizard.welcome.title"));
|
||||
setSubTitle(obs_module_text("FirstRunWizard.welcome.subtitle"));
|
||||
|
||||
auto body = new QLabel(obs_module_text("FirstRunWizard.welcome.body"),
|
||||
this);
|
||||
body->setWordWrap(true);
|
||||
body->setTextFormat(Qt::RichText);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->addWidget(body);
|
||||
layout->addStretch();
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// SceneSelectionPage
|
||||
// ===========================================================================
|
||||
|
||||
SceneSelectionPage::SceneSelectionPage(QWidget *parent) : QWizardPage(parent)
|
||||
{
|
||||
setTitle(obs_module_text("FirstRunWizard.scene.title"));
|
||||
setSubTitle(obs_module_text("FirstRunWizard.scene.subtitle"));
|
||||
|
||||
auto label =
|
||||
new QLabel(obs_module_text("FirstRunWizard.scene.label"), this);
|
||||
_sceneCombo = new QComboBox(this);
|
||||
_sceneCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
|
||||
// registerField with * suffix means the field is mandatory for Next
|
||||
registerField("targetScene*", _sceneCombo, "currentText",
|
||||
SIGNAL(currentTextChanged(QString)));
|
||||
|
||||
connect(_sceneCombo, &QComboBox::currentTextChanged, this,
|
||||
&QWizardPage::completeChanged);
|
||||
|
||||
auto row = new QHBoxLayout;
|
||||
row->addWidget(label);
|
||||
row->addWidget(_sceneCombo, 1);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->addLayout(row);
|
||||
layout->addStretch();
|
||||
}
|
||||
|
||||
void SceneSelectionPage::initializePage()
|
||||
{
|
||||
_sceneCombo->clear();
|
||||
for (const QString &name : GetSceneNames())
|
||||
_sceneCombo->addItem(name);
|
||||
}
|
||||
|
||||
bool SceneSelectionPage::isComplete() const
|
||||
{
|
||||
return _sceneCombo->count() > 0 &&
|
||||
!_sceneCombo->currentText().isEmpty();
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// WindowConditionPage
|
||||
// ===========================================================================
|
||||
|
||||
WindowConditionPage::WindowConditionPage(QWidget *parent)
|
||||
: QWizardPage(parent),
|
||||
_detectTimer(new QTimer(this))
|
||||
{
|
||||
setTitle(obs_module_text("FirstRunWizard.window.title"));
|
||||
setSubTitle(obs_module_text("FirstRunWizard.window.subtitle"));
|
||||
|
||||
auto label = new QLabel(obs_module_text("FirstRunWizard.window.label"),
|
||||
this);
|
||||
_windowEdit = new QLineEdit(this);
|
||||
_windowEdit->setPlaceholderText(
|
||||
obs_module_text("FirstRunWizard.window.placeholder"));
|
||||
|
||||
_autoDetect = new QPushButton(
|
||||
obs_module_text("FirstRunWizard.window.autoDetect"), this);
|
||||
_autoDetect->setToolTip(
|
||||
obs_module_text("FirstRunWizard.window.autoDetectTooltip"));
|
||||
|
||||
registerField("windowTitle*", _windowEdit);
|
||||
|
||||
connect(_windowEdit, &QLineEdit::textChanged, this,
|
||||
&QWizardPage::completeChanged);
|
||||
connect(_autoDetect, &QPushButton::clicked, this,
|
||||
&WindowConditionPage::onAutoDetectClicked);
|
||||
connect(_detectTimer, &QTimer::timeout, this,
|
||||
&WindowConditionPage::onCountdownTick);
|
||||
|
||||
auto row = new QHBoxLayout;
|
||||
row->addWidget(label);
|
||||
row->addWidget(_windowEdit, 1);
|
||||
|
||||
auto hint =
|
||||
new QLabel(obs_module_text("FirstRunWizard.window.hint"), this);
|
||||
hint->setTextFormat(Qt::RichText);
|
||||
hint->setWordWrap(true);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->addLayout(row);
|
||||
layout->addWidget(_autoDetect, 0, Qt::AlignLeft);
|
||||
layout->addWidget(hint);
|
||||
layout->addStretch();
|
||||
}
|
||||
|
||||
void WindowConditionPage::initializePage()
|
||||
{
|
||||
if (_windowEdit->text().isEmpty()) {
|
||||
QString detected = DetectFocusedWindow();
|
||||
if (!detected.isEmpty()) {
|
||||
_windowEdit->setText(detected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowConditionPage::isComplete() const
|
||||
{
|
||||
return !_windowEdit->text().trimmed().isEmpty();
|
||||
}
|
||||
|
||||
void WindowConditionPage::onAutoDetectClicked()
|
||||
{
|
||||
_countdown = 3;
|
||||
_autoDetect->setEnabled(false);
|
||||
_autoDetect->setText(
|
||||
QString(obs_module_text(
|
||||
"FirstRunWizard.window.autoDetectCountdown"))
|
||||
.arg(_countdown));
|
||||
_detectTimer->start(1000);
|
||||
}
|
||||
|
||||
void WindowConditionPage::onCountdownTick()
|
||||
{
|
||||
--_countdown;
|
||||
if (_countdown > 0) {
|
||||
_autoDetect->setText(
|
||||
QString(obs_module_text(
|
||||
"FirstRunWizard.window.autoDetectCountdown"))
|
||||
.arg(_countdown));
|
||||
return;
|
||||
}
|
||||
|
||||
_detectTimer->stop();
|
||||
QString title = DetectFocusedWindow();
|
||||
if (!title.isEmpty()) {
|
||||
_windowEdit->setText(title);
|
||||
}
|
||||
|
||||
_autoDetect->setEnabled(true);
|
||||
_autoDetect->setText(
|
||||
obs_module_text("FirstRunWizard.window.autoDetect"));
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// ReviewPage
|
||||
// ===========================================================================
|
||||
|
||||
ReviewPage::ReviewPage(QWidget *parent, std::shared_ptr<Macro> ¯o)
|
||||
: QWizardPage(parent),
|
||||
_macro(macro)
|
||||
{
|
||||
setTitle(obs_module_text("FirstRunWizard.review.title"));
|
||||
setSubTitle(obs_module_text("FirstRunWizard.review.subtitle"));
|
||||
|
||||
_summary = new QLabel(this);
|
||||
_summary->setWordWrap(true);
|
||||
_summary->setTextFormat(Qt::RichText);
|
||||
_summary->setFrameShape(QFrame::StyledPanel);
|
||||
_summary->setContentsMargins(12, 12, 12, 12);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->addWidget(_summary);
|
||||
layout->addStretch();
|
||||
}
|
||||
|
||||
void ReviewPage::initializePage()
|
||||
{
|
||||
const QString scene = field("targetScene").toString();
|
||||
const QString window = field("windowTitle").toString();
|
||||
|
||||
_summary->setText(
|
||||
QString(obs_module_text("FirstRunWizard.review.summary"))
|
||||
.arg(scene.toHtmlEscaped(), window.toHtmlEscaped()));
|
||||
}
|
||||
|
||||
static QString escapeForRegex(const QString &input)
|
||||
{
|
||||
return QRegularExpression::escape(input);
|
||||
}
|
||||
|
||||
bool ReviewPage::validatePage()
|
||||
{
|
||||
const QString scene = field("targetScene").toString();
|
||||
const QString window = escapeForRegex(field("windowTitle").toString());
|
||||
const std::string name = ("Window -> " + scene).toStdString();
|
||||
|
||||
// Build condition data blob
|
||||
// ---------------------------------------------------------------
|
||||
// Condition blob — mirrors MacroConditionWindow::Save() output:
|
||||
//
|
||||
// {
|
||||
// "segmentSettings": { "enabled": true, "version": 1 },
|
||||
// "id": "window",
|
||||
// "checkTitle": true,
|
||||
// "window": "<user input>",
|
||||
// "windowRegexConfig": {
|
||||
// "enable": true, // use regex-style partial matching
|
||||
// "partial": true, // match anywhere in the title
|
||||
// "options": 3 // case-insensitive (QRegularExpression flags)
|
||||
// },
|
||||
// "focus": true, // only trigger when window is focused
|
||||
// "version": 1
|
||||
// }
|
||||
// ---------------------------------------------------------------
|
||||
OBSDataAutoRelease condSegment = obs_data_create();
|
||||
obs_data_set_bool(condSegment, "enabled", true);
|
||||
obs_data_set_int(condSegment, "version", 1);
|
||||
|
||||
OBSDataAutoRelease condRegex = obs_data_create();
|
||||
obs_data_set_bool(condRegex, "enable", true);
|
||||
obs_data_set_bool(condRegex, "partial", true);
|
||||
obs_data_set_int(condRegex, "options", 3); // CaseInsensitiveOption
|
||||
|
||||
OBSDataAutoRelease condData = obs_data_create();
|
||||
obs_data_set_obj(condData, "segmentSettings", condSegment);
|
||||
obs_data_set_string(condData, "id", "window");
|
||||
obs_data_set_bool(condData, "checkTitle", true);
|
||||
obs_data_set_string(condData, "window", window.toUtf8().constData());
|
||||
obs_data_set_obj(condData, "windowRegexConfig", condRegex);
|
||||
obs_data_set_bool(condData, "focus", true);
|
||||
obs_data_set_int(condData, "version", 1);
|
||||
|
||||
// Build action data blob
|
||||
// ---------------------------------------------------------------
|
||||
// Action blob — mirrors MacroActionSwitchScene::Save() output:
|
||||
//
|
||||
// {
|
||||
// "segmentSettings": { "enabled": true, "version": 1 },
|
||||
// "id": "scene_switch",
|
||||
// "action": 0, // 0 = switch scene
|
||||
// "sceneSelection": {
|
||||
// "type": 0, // 0 = scene by name
|
||||
// "name": "<scene>",
|
||||
// "canvasSelection": "Main"
|
||||
// },
|
||||
// "transitionType": 1, // 1 = use scene's default transition
|
||||
// "blockUntilTransitionDone": false,
|
||||
// "sceneType": 0
|
||||
// }
|
||||
// ---------------------------------------------------------------
|
||||
OBSDataAutoRelease actionSegment = obs_data_create();
|
||||
obs_data_set_bool(actionSegment, "enabled", true);
|
||||
obs_data_set_int(actionSegment, "version", 1);
|
||||
|
||||
OBSDataAutoRelease sceneSelection = obs_data_create();
|
||||
obs_data_set_int(sceneSelection, "type", 0);
|
||||
obs_data_set_string(sceneSelection, "name", scene.toUtf8().constData());
|
||||
obs_data_set_string(sceneSelection, "canvasSelection", "Main");
|
||||
|
||||
OBSDataAutoRelease actionData = obs_data_create();
|
||||
obs_data_set_obj(actionData, "segmentSettings", actionSegment);
|
||||
obs_data_set_string(actionData, "id", "scene_switch");
|
||||
obs_data_set_int(actionData, "action", 0);
|
||||
obs_data_set_obj(actionData, "sceneSelection", sceneSelection);
|
||||
obs_data_set_int(actionData, "transitionType", 1);
|
||||
obs_data_set_bool(actionData, "blockUntilTransitionDone", false);
|
||||
obs_data_set_int(actionData, "sceneType", 0);
|
||||
|
||||
if (!FirstRunWizard::CreateMacro(_macro, name, kConditionIdWindow,
|
||||
condData, kActionIdSceneSwitch,
|
||||
actionData)) {
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
obs_module_text("FirstRunWizard.review.errorTitle"),
|
||||
QString(obs_module_text(
|
||||
"FirstRunWizard.review.errorBody"))
|
||||
.arg(window, scene));
|
||||
_macro.reset();
|
||||
// Still advance so the user is not stuck.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// DonePage
|
||||
// ===========================================================================
|
||||
|
||||
DonePage::DonePage(QWidget *parent) : QWizardPage(parent)
|
||||
{
|
||||
setTitle(obs_module_text("FirstRunWizard.done.title"));
|
||||
setSubTitle(obs_module_text("FirstRunWizard.done.subtitle"));
|
||||
|
||||
auto body =
|
||||
new QLabel(obs_module_text("FirstRunWizard.done.body"), this);
|
||||
body->setWordWrap(true);
|
||||
body->setTextFormat(Qt::RichText);
|
||||
body->setOpenExternalLinks(true);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->addWidget(body);
|
||||
layout->addStretch();
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// FirstRunWizard
|
||||
// ===========================================================================
|
||||
|
||||
FirstRunWizard::FirstRunWizard(QWidget *parent) : QWizard(parent)
|
||||
{
|
||||
setWindowTitle(obs_module_text("FirstRunWizard.windowTitle"));
|
||||
setWizardStyle(QWizard::ModernStyle);
|
||||
setMinimumSize(540, 420);
|
||||
|
||||
setPage(PAGE_WELCOME, new WelcomePage(this));
|
||||
setPage(PAGE_SCENE, new SceneSelectionPage(this));
|
||||
setPage(PAGE_WINDOW, new WindowConditionPage(this));
|
||||
setPage(PAGE_REVIEW, new ReviewPage(this, _macro));
|
||||
setPage(PAGE_DONE, new DonePage(this));
|
||||
|
||||
setStartId(PAGE_WELCOME);
|
||||
setOption(QWizard::NoBackButtonOnLastPage, true);
|
||||
setOption(QWizard::NoCancelButtonOnLastPage, true);
|
||||
|
||||
// Mark done on both Accept (Finish) and Reject (Cancel / close)
|
||||
connect(this, &QWizard::accepted, this,
|
||||
&FirstRunWizard::markFirstRunComplete);
|
||||
connect(this, &QWizard::rejected, this,
|
||||
&FirstRunWizard::markFirstRunComplete);
|
||||
}
|
||||
|
||||
void FirstRunWizard::markFirstRunComplete()
|
||||
{
|
||||
WriteFirstRun(false);
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<Macro> FirstRunWizard::ShowWizard(QWidget *parent)
|
||||
{
|
||||
auto wizard = new FirstRunWizard(parent);
|
||||
wizard->exec();
|
||||
wizard->deleteLater();
|
||||
return wizard->_macro;
|
||||
}
|
||||
|
||||
// static
|
||||
bool FirstRunWizard::CreateMacro(std::shared_ptr<Macro> ¯o,
|
||||
const std::string ¯oName,
|
||||
const std::string &conditionId,
|
||||
obs_data_t *conditionData,
|
||||
const std::string &actionId,
|
||||
obs_data_t *actionData)
|
||||
{
|
||||
// 1. Create and register the Macro
|
||||
macro = std::make_shared<Macro>(macroName, GetGlobalMacroSettings());
|
||||
if (!macro) {
|
||||
blog(LOG_WARNING, "FirstRunWizard: Macro allocation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Instantiate condition via factory, then hydrate via Load()
|
||||
auto condition =
|
||||
MacroConditionFactory::Create(conditionId, macro.get());
|
||||
if (!condition) {
|
||||
blog(LOG_WARNING,
|
||||
"FirstRunWizard: condition factory returned null "
|
||||
"for id '%s' — is the base plugin loaded?",
|
||||
conditionId.c_str());
|
||||
return false;
|
||||
}
|
||||
if (!condition->Load(conditionData)) {
|
||||
blog(LOG_WARNING,
|
||||
"FirstRunWizard: condition Load() failed for id '%s'",
|
||||
conditionId.c_str());
|
||||
return false;
|
||||
}
|
||||
macro->Conditions().emplace_back(condition);
|
||||
|
||||
// 3. Instantiate action via factory, then hydrate via Load()
|
||||
auto action = MacroActionFactory::Create(actionId, macro.get());
|
||||
if (!action) {
|
||||
blog(LOG_WARNING,
|
||||
"FirstRunWizard: action factory returned null "
|
||||
"for id '%s' — is the base plugin loaded?",
|
||||
actionId.c_str());
|
||||
return false;
|
||||
}
|
||||
if (!action->Load(actionData)) {
|
||||
blog(LOG_WARNING,
|
||||
"FirstRunWizard: action Load() failed for id '%s'",
|
||||
actionId.c_str());
|
||||
return false;
|
||||
}
|
||||
macro->Actions().emplace_back(action);
|
||||
|
||||
blog(LOG_INFO, "FirstRunWizard: created macro '%s'", macroName.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
128
lib/utils/first-run-wizard.hpp
Normal file
128
lib/utils/first-run-wizard.hpp
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
#pragma once
|
||||
|
||||
#include <obs-data.h>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
#include <QWizard>
|
||||
#include <QWizardPage>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class Macro;
|
||||
|
||||
bool IsFirstRun();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Page IDs
|
||||
// ---------------------------------------------------------------------------
|
||||
enum WizardPageId {
|
||||
PAGE_WELCOME = 0,
|
||||
PAGE_SCENE,
|
||||
PAGE_WINDOW,
|
||||
PAGE_REVIEW,
|
||||
PAGE_DONE,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WelcomePage
|
||||
// ---------------------------------------------------------------------------
|
||||
class WelcomePage : public QWizardPage {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WelcomePage(QWidget *parent = nullptr);
|
||||
int nextId() const override { return PAGE_SCENE; }
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SceneSelectionPage
|
||||
// Registers wizard field "targetScene" (QString).
|
||||
// ---------------------------------------------------------------------------
|
||||
class SceneSelectionPage : public QWizardPage {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SceneSelectionPage(QWidget *parent = nullptr);
|
||||
void initializePage() override;
|
||||
bool isComplete() const override;
|
||||
int nextId() const override { return PAGE_WINDOW; }
|
||||
|
||||
private:
|
||||
QComboBox *_sceneCombo;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WindowConditionPage
|
||||
// Registers wizard field "windowTitle" (QString).
|
||||
// Auto-detect button samples the focused window after a countdown.
|
||||
// ---------------------------------------------------------------------------
|
||||
class WindowConditionPage : public QWizardPage {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WindowConditionPage(QWidget *parent = nullptr);
|
||||
void initializePage() override;
|
||||
bool isComplete() const override;
|
||||
int nextId() const override { return PAGE_REVIEW; }
|
||||
|
||||
private slots:
|
||||
void onAutoDetectClicked();
|
||||
void onCountdownTick();
|
||||
|
||||
private:
|
||||
QLineEdit *_windowEdit;
|
||||
QPushButton *_autoDetect;
|
||||
QTimer *_detectTimer;
|
||||
int _countdown = 3;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ReviewPage
|
||||
// Displays a summary and calls FirstRunWizard::CreateMacro() on Finish.
|
||||
// ---------------------------------------------------------------------------
|
||||
class ReviewPage : public QWizardPage {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ReviewPage(QWidget *parent, std::shared_ptr<Macro> ¯o);
|
||||
void initializePage() override;
|
||||
bool validatePage() override;
|
||||
int nextId() const override { return PAGE_DONE; }
|
||||
|
||||
private:
|
||||
QLabel *_summary;
|
||||
std::shared_ptr<Macro> &_macro;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DonePage
|
||||
// ---------------------------------------------------------------------------
|
||||
class DonePage : public QWizardPage {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DonePage(QWidget *parent = nullptr);
|
||||
int nextId() const override { return -1; }
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FirstRunWizard
|
||||
// ---------------------------------------------------------------------------
|
||||
class FirstRunWizard : public QWizard {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FirstRunWizard(QWidget *parent = nullptr);
|
||||
static std::shared_ptr<Macro> ShowWizard(QWidget *parent);
|
||||
static bool
|
||||
CreateMacro(std::shared_ptr<Macro> ¯o, const std::string ¯oName,
|
||||
const std::string &conditionId, obs_data_t *conditionData,
|
||||
const std::string &actionId, obs_data_t *actionData);
|
||||
|
||||
private:
|
||||
void markFirstRunComplete();
|
||||
|
||||
std::shared_ptr<Macro> _macro;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
#pragma once
|
||||
#ifndef UNIT_TEST
|
||||
#include <util/base.h>
|
||||
#endif
|
||||
|
||||
namespace advss {
|
||||
|
||||
|
|
@ -9,6 +7,7 @@ namespace advss {
|
|||
#define blog(level, msg, ...)
|
||||
#define vblog(level, msg, ...)
|
||||
#define ablog(level, msg, ...)
|
||||
#define mblog(level, msg, ...)
|
||||
#else
|
||||
|
||||
// Print log with "[adv-ss] " prefix
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -3,12 +3,42 @@
|
|||
#include "macro-signals.hpp"
|
||||
#include "switcher-data.hpp"
|
||||
|
||||
#include "obs-frontend-api.h"
|
||||
|
||||
namespace advss {
|
||||
|
||||
static std::mutex initMutex;
|
||||
static std::mutex postLoadMutex;
|
||||
static std::mutex finishLoadMutex;
|
||||
static std::mutex mutex;
|
||||
|
||||
static bool setup();
|
||||
static bool setupDonw = setup();
|
||||
bool loadingFinished = false;
|
||||
|
||||
static std::vector<std::function<void()>> &getFinishLoadSteps();
|
||||
|
||||
static bool setup()
|
||||
{
|
||||
static auto handleEvent = [](enum obs_frontend_event event, void *) {
|
||||
switch (event) {
|
||||
case OBS_FRONTEND_EVENT_FINISHED_LOADING: {
|
||||
std::lock_guard<std::mutex> lock(finishLoadMutex);
|
||||
for (const auto &step : getFinishLoadSteps()) {
|
||||
step();
|
||||
}
|
||||
getFinishLoadSteps().clear();
|
||||
loadingFinished = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
};
|
||||
};
|
||||
obs_frontend_add_event_callback(handleEvent, nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::vector<std::function<void()>> &getPluginInitSteps()
|
||||
{
|
||||
static std::vector<std::function<void()>> steps;
|
||||
|
|
@ -63,6 +93,12 @@ static std::vector<std::function<void()>> &getPostLoadSteps()
|
|||
return steps;
|
||||
}
|
||||
|
||||
static std::vector<std::function<void()>> &getFinishLoadSteps()
|
||||
{
|
||||
static std::vector<std::function<void()>> steps;
|
||||
return steps;
|
||||
}
|
||||
|
||||
void SavePluginSettings(obs_data_t *obj)
|
||||
{
|
||||
GetSwitcher()->SaveSettings(obj);
|
||||
|
|
@ -178,6 +214,16 @@ void RunIntervalResetSteps()
|
|||
}
|
||||
}
|
||||
|
||||
void AddFinishedLoadingStep(std::function<void()> step)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(finishLoadMutex);
|
||||
if (loadingFinished) {
|
||||
return;
|
||||
}
|
||||
|
||||
getFinishLoadSteps().emplace_back(step);
|
||||
}
|
||||
|
||||
void AddStartStep(std::function<void()> step)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ void RunStartSteps();
|
|||
void RunStopSteps();
|
||||
void RunIntervalResetSteps();
|
||||
|
||||
// Steps are executed after OBS_FRONTEND_EVENT_FINISHED_LOADING is fired
|
||||
EXPORT void AddFinishedLoadingStep(std::function<void()>);
|
||||
|
||||
enum class NoMatchBehavior { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 };
|
||||
EXPORT void SetPluginNoMatchBehavior(NoMatchBehavior);
|
||||
EXPORT NoMatchBehavior GetPluginNoMatchBehavior();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ void CenterSplitterPosition(QSplitter *splitter)
|
|||
|
||||
void SetSplitterPositionByFraction(QSplitter *splitter, double fraction)
|
||||
{
|
||||
int value1 = (double)QWIDGETSIZE_MAX * fraction;
|
||||
int value2 = (double)QWIDGETSIZE_MAX * (1.0 - fraction);
|
||||
int value1 = (int)((double)QWIDGETSIZE_MAX * fraction);
|
||||
int value2 = (int)((double)QWIDGETSIZE_MAX * (1.0 - fraction));
|
||||
splitter->setSizes(QList<int>() << value1 << value2);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,22 @@
|
|||
|
||||
namespace advss {
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
std::mutex *GetSwitcherMutex()
|
||||
{
|
||||
static std::mutex m;
|
||||
return &m;
|
||||
}
|
||||
std::unique_lock<std::mutex> *GetSwitcherLoopLock()
|
||||
{
|
||||
static std::mutex m;
|
||||
static std::unique_lock<std::mutex> lock(m);
|
||||
return &lock;
|
||||
}
|
||||
#else
|
||||
std::mutex *GetSwitcherMutex();
|
||||
std::unique_lock<std::mutex> *GetSwitcherLoopLock();
|
||||
#endif
|
||||
|
||||
std::mutex *GetMutex()
|
||||
{
|
||||
|
|
@ -52,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
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
#include "export-symbol-helper.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace advss {
|
||||
|
|
@ -52,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
|
||||
|
|
|
|||
|
|
@ -10,9 +10,12 @@
|
|||
|
||||
#include <QVariant>
|
||||
#include <QAbstractItemView>
|
||||
#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 +123,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 ¯o : 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 +722,13 @@ 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);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ public:
|
|||
void SetValue(const std::string &val);
|
||||
void InvalidateValue();
|
||||
TempVariableRef GetRef() const;
|
||||
EXPORT bool IsInUse() const;
|
||||
|
||||
private:
|
||||
std::string _id = "";
|
||||
|
|
@ -53,6 +54,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 +125,6 @@ private:
|
|||
};
|
||||
|
||||
void NotifyUIAboutTempVarChange(MacroSegment *);
|
||||
EXPORT void IncrementTempVarInUseGeneration();
|
||||
|
||||
} // namespace advss
|
||||
|
|
|
|||
|
|
@ -81,13 +81,13 @@ void GenericVariableSpinbox::DisableVariableSelection()
|
|||
|
||||
void GenericVariableSpinbox::setMinimum(double value)
|
||||
{
|
||||
_fixedValueInt->setMinimum(value);
|
||||
_fixedValueInt->setMinimum((int)value);
|
||||
_fixedValueDouble->setMinimum(value);
|
||||
}
|
||||
|
||||
void GenericVariableSpinbox::setMaximum(double value)
|
||||
{
|
||||
_fixedValueInt->setMaximum(value);
|
||||
_fixedValueInt->setMaximum((int)value);
|
||||
_fixedValueDouble->setMaximum(value);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -12,16 +12,23 @@ 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 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 +44,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 +52,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 +72,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 +101,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 +115,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 +127,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 +143,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 +231,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;
|
||||
}
|
||||
|
|
@ -461,6 +486,7 @@ void ImportVariables(obs_data_t *data)
|
|||
|
||||
std::chrono::high_resolution_clock::time_point GetLastVariableChangeTime()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lastVariableChangeMutex);
|
||||
return lastVariableChange;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -133,9 +133,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 +147,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 +168,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 +340,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 +373,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 +387,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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ void MacroActionFileEdit::UpdateEntryData()
|
|||
}
|
||||
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_filePath->SetPath(QString::fromStdString(_entryData->_file));
|
||||
_filePath->SetPath(_entryData->_file);
|
||||
_text->setPlainText(_entryData->_text);
|
||||
|
||||
adjustSize();
|
||||
|
|
|
|||
|
|
@ -9,6 +9,14 @@ namespace advss {
|
|||
|
||||
const std::string MacroActionFilter::id = "filter";
|
||||
|
||||
std::vector<TempVariableRef> MacroActionFilter::GetTempVarRefs() const
|
||||
{
|
||||
if (!_tempVar.HasValidID()) {
|
||||
return {};
|
||||
}
|
||||
return {_tempVar};
|
||||
}
|
||||
|
||||
bool MacroActionFilter::_registered = MacroActionFactory::Register(
|
||||
MacroActionFilter::id,
|
||||
{MacroActionFilter::Create, MacroActionFilterEdit::Create,
|
||||
|
|
@ -428,6 +436,7 @@ void MacroActionFilterEdit::SelectionChanged(const TempVariableRef &var)
|
|||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_tempVar = var;
|
||||
IncrementTempVarInUseGeneration();
|
||||
}
|
||||
|
||||
void MacroActionFilterEdit::SelectionChanged(const SourceSetting &setting)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ public:
|
|||
SettingsInputMethod _settingsInputMethod =
|
||||
SettingsInputMethod::INDIVIDUAL_MANUAL;
|
||||
|
||||
std::vector<TempVariableRef> GetTempVarRefs() const;
|
||||
|
||||
SourceSelection _source;
|
||||
FilterSelection _filter;
|
||||
Action _action = Action::ENABLE;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
446
plugins/base/macro-action-play-audio.cpp
Normal file
446
plugins/base/macro-action-play-audio.cpp
Normal 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
|
||||
91
plugins/base/macro-action-play-audio.hpp
Normal file
91
plugins/base/macro-action-play-audio.hpp
Normal 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
|
||||
|
|
@ -47,20 +47,28 @@ getNextMacros(std::vector<MacroRef> ¯os, 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),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
292
plugins/base/macro-action-source-interaction.cpp
Normal file
292
plugins/base/macro-action-source-interaction.cpp
Normal 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
|
||||
81
plugins/base/macro-action-source-interaction.hpp
Normal file
81
plugins/base/macro-action-source-interaction.hpp
Normal 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
|
||||
|
|
@ -13,6 +13,14 @@ namespace advss {
|
|||
|
||||
const std::string MacroActionSource::id = "source";
|
||||
|
||||
std::vector<TempVariableRef> MacroActionSource::GetTempVarRefs() const
|
||||
{
|
||||
if (!_tempVar.HasValidID()) {
|
||||
return {};
|
||||
}
|
||||
return {_tempVar};
|
||||
}
|
||||
|
||||
bool MacroActionSource::_registered = MacroActionFactory::Register(
|
||||
MacroActionSource::id,
|
||||
{MacroActionSource::Create, MacroActionSourceEdit::Create,
|
||||
|
|
@ -45,6 +53,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 +285,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 +388,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 +555,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 +590,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 +660,7 @@ void MacroActionSourceEdit::SelectionChanged(const TempVariableRef &var)
|
|||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_tempVar = var;
|
||||
IncrementTempVarInUseGeneration();
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::SettingsInputMethodChanged(int idx)
|
||||
|
|
@ -664,19 +718,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 +749,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 +767,31 @@ 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);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -710,6 +710,12 @@ void MacroConditionDateEdit::UpdateEntryData()
|
|||
SetWidgetStatus();
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::showEvent(QShowEvent *event)
|
||||
{
|
||||
const QSignalBlocker b(this);
|
||||
UpdateEntryData();
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::SetupSimpleView()
|
||||
{
|
||||
SetLayoutVisible(_simpleLayout, true);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user