mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 04:36:21 -05:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d38d22270 | ||
|
|
de007176d5 | ||
|
|
713f695aa4 | ||
|
|
dec5c2d763 | ||
|
|
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 |
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 🔎
|
||||
|
||||
@@ -504,7 +504,11 @@ endif()
|
||||
# --- End of section ---
|
||||
|
||||
add_subdirectory(plugins)
|
||||
add_subdirectory(tests)
|
||||
|
||||
option(ADVSS_ENABLE_TESTS "Build advanced-scene-switcher unit tests" OFF)
|
||||
if(ADVSS_ENABLE_TESTS)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
# --- Install ---
|
||||
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
"description": "Build for macOS 11.0+ (Universal binary) for CI",
|
||||
"generator": "Xcode",
|
||||
"cacheVariables": {
|
||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true
|
||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true,
|
||||
"ADVSS_ENABLE_TESTS": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -69,7 +70,8 @@
|
||||
"displayName": "Windows x64 CI build",
|
||||
"description": "Build for Windows x64 on CI",
|
||||
"cacheVariables": {
|
||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true
|
||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true,
|
||||
"ADVSS_ENABLE_TESTS": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -97,7 +99,8 @@
|
||||
"description": "Build for Linux x86_64 on CI",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true
|
||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true,
|
||||
"ADVSS_ENABLE_TESTS": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -125,7 +128,8 @@
|
||||
"description": "Build for Linux aarch64 on CI",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true
|
||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true,
|
||||
"ADVSS_ENABLE_TESTS": true
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -145,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"
|
||||
@@ -215,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"
|
||||
@@ -412,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"
|
||||
@@ -421,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"
|
||||
@@ -457,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"
|
||||
@@ -505,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"
|
||||
|
||||
@@ -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"
|
||||
@@ -368,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"
|
||||
@@ -487,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"
|
||||
@@ -676,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"
|
||||
@@ -879,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"
|
||||
@@ -887,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!"
|
||||
@@ -906,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."
|
||||
@@ -949,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\""
|
||||
@@ -971,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"
|
||||
@@ -990,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"
|
||||
@@ -1005,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"
|
||||
@@ -1015,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"
|
||||
@@ -1087,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"
|
||||
@@ -1253,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"
|
||||
@@ -1290,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."
|
||||
@@ -1395,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"
|
||||
@@ -1664,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!"
|
||||
@@ -1672,6 +1795,10 @@ AdvSceneSwitcher.script.file.layout="Script file:{{path}}{{open}}"
|
||||
|
||||
AdvSceneSwitcher.tempVar.select="--select value--"
|
||||
AdvSceneSwitcher.tempVar.selectionInfo.lastValues="Last values:"
|
||||
AdvSceneSwitcher.tempVar.outputMappings="Save outputs to variables:"
|
||||
AdvSceneSwitcher.tempVar.outputMappings.add="+ Add output mapping"
|
||||
AdvSceneSwitcher.tempVar.outputMappings.toggle="Assign outputs to variables"
|
||||
AdvSceneSwitcher.tempVar.outputMappings.remove="Remove output mapping"
|
||||
|
||||
AdvSceneSwitcher.tempVar.twitch.broadcaster_user_id="Twitch broadcaster user ID"
|
||||
AdvSceneSwitcher.tempVar.twitch.broadcaster_user_id.description="The numerical Twitch user ID of the broadcaster."
|
||||
@@ -2181,8 +2308,21 @@ AdvSceneSwitcher.tempVar.macro.runCount="Run count"
|
||||
AdvSceneSwitcher.tempVar.macro.runCount.description="The number of times a macro was executed."
|
||||
AdvSceneSwitcher.tempVar.macro.matchedCount="Matched count"
|
||||
AdvSceneSwitcher.tempVar.macro.matchedCount.description="The number of macros of which the condition state was true."
|
||||
AdvSceneSwitcher.tempVar.macro.info.conditionCount="Condition count"
|
||||
AdvSceneSwitcher.tempVar.macro.info.conditionCount.description="The number of conditions in the macro."
|
||||
AdvSceneSwitcher.tempVar.macro.info.actionCount="Action count"
|
||||
AdvSceneSwitcher.tempVar.macro.info.actionCount.description="The number of actions in the macro."
|
||||
AdvSceneSwitcher.tempVar.macro.info.elseActionCount="Else-action count"
|
||||
AdvSceneSwitcher.tempVar.macro.info.elseActionCount.description="The number of else-actions in the macro."
|
||||
AdvSceneSwitcher.tempVar.macro.info.paused="Paused"
|
||||
AdvSceneSwitcher.tempVar.macro.info.paused.description="Whether the macro is currently paused. Value is \"true\" or \"false\"."
|
||||
AdvSceneSwitcher.tempVar.macro.info.runCount="Run count"
|
||||
AdvSceneSwitcher.tempVar.macro.info.runCount.description="The number of times the macro has been executed."
|
||||
AdvSceneSwitcher.tempVar.macro.info.secondsSinceLastRun="Seconds since last run"
|
||||
AdvSceneSwitcher.tempVar.macro.info.secondsSinceLastRun.description="The number of seconds elapsed since the macro was last executed. Value is -1 if the macro has never been executed."
|
||||
|
||||
AdvSceneSwitcher.tempVar.process.name="Process name"
|
||||
AdvSceneSwitcher.tempVar.process.path="Process path"
|
||||
|
||||
AdvSceneSwitcher.tempVar.run.process.id="Process ID"
|
||||
AdvSceneSwitcher.tempVar.run.process.id.description="PID of the process assigned by the system."
|
||||
@@ -2213,6 +2353,8 @@ AdvSceneSwitcher.tempVar.video.text="OCR text"
|
||||
AdvSceneSwitcher.tempVar.video.text.description="The text detected in a given video input frame."
|
||||
AdvSceneSwitcher.tempVar.video.color="Average color"
|
||||
AdvSceneSwitcher.tempVar.video.color.description="The average RGB color in a given video input frame in HexArgb format."
|
||||
AdvSceneSwitcher.tempVar.video.dominantColor="Dominant color"
|
||||
AdvSceneSwitcher.tempVar.video.dominantColor.description="The most dominant RGB color in a given video input frame in HexArgb format.\nDetermined using k-means clustering, which groups similar colors and returns the center of the largest group.\nDue to the nature of the algorithm the result may vary slightly between evaluations."
|
||||
|
||||
AdvSceneSwitcher.tempVar.websocket.message="Received websocket message"
|
||||
AdvSceneSwitcher.tempVar.websocket.message.description="The received websocket message, which matched the given pattern"
|
||||
@@ -2342,9 +2484,14 @@ AdvSceneSwitcher.tempVar.gameCapture.executable="Executable"
|
||||
AdvSceneSwitcher.tempVar.gameCapture.executable.description="Executable name of the application captured by the source."
|
||||
|
||||
AdvSceneSwitcher.tempVar.http.status="Status code"
|
||||
AdvSceneSwitcher.tempVar.http.body="Message body"
|
||||
AdvSceneSwitcher.tempVar.http.error="Error"
|
||||
AdvSceneSwitcher.tempVar.http.error.description="Empty when no error occurred.\nOther possible values:\n\n * Could not establish connection\n * Failed to bind IP address\n * Failed to read connection\n * Failed to write connection\n * Maximum redirect count exceeded\n * Connection handling canceled\n * SSL connection failed\n * SSL certificate loading failed\n * SSL server verification failed\n * Unsupported HTTP multipart boundary characters\n * Compression failed\n * Connection timed out\n * Proxy connection failed\n * Unknown"
|
||||
AdvSceneSwitcher.tempVar.http.method="Method"
|
||||
AdvSceneSwitcher.tempVar.http.method.description="Received HTTP request method"
|
||||
AdvSceneSwitcher.tempVar.http.path="Request path"
|
||||
AdvSceneSwitcher.tempVar.http.path.description="Received HTTP request path"
|
||||
AdvSceneSwitcher.tempVar.http.body="Message body"
|
||||
AdvSceneSwitcher.tempVar.http.body.description="Received HTTP request body"
|
||||
|
||||
AdvSceneSwitcher.tempVar.mqtt.message="Message"
|
||||
|
||||
@@ -2354,6 +2501,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"
|
||||
|
||||
@@ -117,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"
|
||||
@@ -175,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"
|
||||
@@ -334,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"
|
||||
@@ -343,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"
|
||||
@@ -375,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"
|
||||
@@ -423,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"
|
||||
|
||||
@@ -187,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"
|
||||
@@ -281,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"
|
||||
@@ -553,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"
|
||||
@@ -622,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"
|
||||
|
||||
@@ -331,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="時間が一致"
|
||||
@@ -453,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="プラグインの状態"
|
||||
@@ -931,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 と評価された場合のみ"
|
||||
@@ -1020,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="続行"
|
||||
@@ -1190,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="クリップをキャプチャする前にわずかな遅延を追加します"
|
||||
|
||||
@@ -295,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"
|
||||
@@ -400,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"
|
||||
@@ -754,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!"
|
||||
@@ -771,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."
|
||||
@@ -846,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"
|
||||
@@ -933,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"
|
||||
@@ -1065,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"
|
||||
|
||||
@@ -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="Запустить"
|
||||
|
||||
|
||||
|
||||
@@ -107,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ı "
|
||||
@@ -152,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"
|
||||
@@ -265,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"
|
||||
@@ -273,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"
|
||||
@@ -304,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:"
|
||||
@@ -341,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"
|
||||
|
||||
@@ -318,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="时间限制匹配"
|
||||
@@ -428,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="插件状态"
|
||||
@@ -813,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="警告:更改回放缓存时长上限,仅适用于下次回放缓存开启时!"
|
||||
@@ -830,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="请注意,如果不勾选此选项,宏属性将不起作用,因为进程的启动会脱离逻辑的其他部分,因此无法对其进行控制."
|
||||
@@ -905,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="仅当条件结果为假时"
|
||||
@@ -994,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="继续"
|
||||
@@ -1139,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="在捕捉视频片段前稍加延迟"
|
||||
|
||||
10
data/res/images/DarkVariable.svg
Normal file
10
data/res/images/DarkVariable.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#fefefe" xmlns="http://www.w3.org/2000/svg"
|
||||
width="800px" height="800px" viewBox="0 0 52 52" enable-background="new 0 0 52 52" xml:space="preserve">
|
||||
<path d="M42.6,17.8c2.4,0,7.2-2,7.2-8.4c0-6.4-4.6-6.8-6.1-6.8c-2.8,0-5.6,2-8.1,6.3c-2.5,4.4-5.3,9.1-5.3,9.1
|
||||
l-0.1,0c-0.6-3.1-1.1-5.6-1.3-6.7c-0.5-2.7-3.6-8.4-9.9-8.4c-6.4,0-12.2,3.7-12.2,3.7l0,0C5.8,7.3,5.1,8.5,5.1,9.9
|
||||
c0,2.1,1.7,3.9,3.9,3.9c0.6,0,1.2-0.2,1.7-0.4l0,0c0,0,4.8-2.7,5.9,0c0.3,0.8,0.6,1.7,0.9,2.7c1.2,4.2,2.4,9.1,3.3,13.5l-4.2,6
|
||||
c0,0-4.7-1.7-7.1-1.7s-7.2,2-7.2,8.4s4.6,6.8,6.1,6.8c2.8,0,5.6-2,8.1-6.3c2.5-4.4,5.3-9.1,5.3-9.1c0.8,4,1.5,7.1,1.9,8.5
|
||||
c1.6,4.5,5.3,7.2,10.1,7.2c0,0,5,0,10.9-3.3c1.4-0.6,2.4-2,2.4-3.6c0-2.1-1.7-3.9-3.9-3.9c-0.6,0-1.2,0.2-1.7,0.4l0,0
|
||||
c0,0-4.2,2.4-5.6,0.5c-1-2-1.9-4.6-2.6-7.8c-0.6-2.8-1.3-6.2-2-9.5l4.3-6.2C35.5,16.1,40.2,17.8,42.6,17.8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 973 B |
10
data/res/images/LightVariable.svg
Normal file
10
data/res/images/LightVariable.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#202020" xmlns="http://www.w3.org/2000/svg"
|
||||
width="800px" height="800px" viewBox="0 0 52 52" enable-background="new 0 0 52 52" xml:space="preserve">
|
||||
<path d="M42.6,17.8c2.4,0,7.2-2,7.2-8.4c0-6.4-4.6-6.8-6.1-6.8c-2.8,0-5.6,2-8.1,6.3c-2.5,4.4-5.3,9.1-5.3,9.1
|
||||
l-0.1,0c-0.6-3.1-1.1-5.6-1.3-6.7c-0.5-2.7-3.6-8.4-9.9-8.4c-6.4,0-12.2,3.7-12.2,3.7l0,0C5.8,7.3,5.1,8.5,5.1,9.9
|
||||
c0,2.1,1.7,3.9,3.9,3.9c0.6,0,1.2-0.2,1.7-0.4l0,0c0,0,4.8-2.7,5.9,0c0.3,0.8,0.6,1.7,0.9,2.7c1.2,4.2,2.4,9.1,3.3,13.5l-4.2,6
|
||||
c0,0-4.7-1.7-7.1-1.7s-7.2,2-7.2,8.4s4.6,6.8,6.1,6.8c2.8,0,5.6-2,8.1-6.3c2.5-4.4,5.3-9.1,5.3-9.1c0.8,4,1.5,7.1,1.9,8.5
|
||||
c1.6,4.5,5.3,7.2,10.1,7.2c0,0,5,0,10.9-3.3c1.4-0.6,2.4-2,2.4-3.6c0-2.1-1.7-3.9-3.9-3.9c-0.6,0-1.2,0.2-1.7,0.4l0,0
|
||||
c0,0-4.2,2.4-5.6,0.5c-1-2-1.9-4.6-2.6-7.8c-0.6-2.8-1.3-6.2-2-9.5l4.3-6.2C35.5,16.1,40.2,17.8,42.6,17.8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 973 B |
@@ -251,6 +251,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="suppressCrashRecoveryDialog">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.generalTab.generalBehavior.suppressCrashRecoveryDialog</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -597,7 +604,7 @@
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<widget class="QGroupBox" name="macroListBox">
|
||||
<property name="title">
|
||||
<string>AdvSceneSwitcher.macroTab.macros</string>
|
||||
</property>
|
||||
|
||||
@@ -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,6 +66,7 @@ public slots:
|
||||
void on_uiHintsDisable_stateChanged(int state);
|
||||
void on_disableComboBoxFilter_stateChanged(int state);
|
||||
void on_warnPluginLoadFailure_stateChanged(int state);
|
||||
void on_suppressCrashRecoveryDialog_stateChanged(int state);
|
||||
void on_hideLegacyTabs_stateChanged(int state);
|
||||
void on_priorityUp_clicked();
|
||||
void on_priorityDown_clicked();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "advanced-scene-switcher.hpp"
|
||||
#include "crash-handler.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "filter-combo-box.hpp"
|
||||
#include "first-run-wizard.hpp"
|
||||
@@ -194,6 +195,15 @@ void AdvSceneSwitcher::on_warnPluginLoadFailure_stateChanged(int state)
|
||||
switcher->warnPluginLoadFailure = state;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_suppressCrashRecoveryDialog_stateChanged(int state)
|
||||
{
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetSuppressCrashDialog(state);
|
||||
}
|
||||
|
||||
static bool isLegacyTab(const QString &name)
|
||||
{
|
||||
return name == obs_module_text(
|
||||
@@ -946,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);
|
||||
|
||||
@@ -49,10 +49,12 @@ MacroActionEdit::MacroActionEdit(QWidget *parent,
|
||||
_section->AddHeaderWidget(_enable);
|
||||
_section->AddHeaderWidget(_actionSelection);
|
||||
_section->AddHeaderWidget(_headerInfo);
|
||||
_section->AddHeaderWidget(_varMappingToggle);
|
||||
|
||||
auto actionLayout = new QVBoxLayout;
|
||||
actionLayout->setContentsMargins({7, 7, 7, 7});
|
||||
actionLayout->addWidget(_section);
|
||||
actionLayout->addWidget(_outputMappings);
|
||||
_contentLayout->addLayout(actionLayout);
|
||||
|
||||
auto mainLayout = new QHBoxLayout;
|
||||
@@ -61,7 +63,6 @@ MacroActionEdit::MacroActionEdit(QWidget *parent,
|
||||
mainLayout->addWidget(_frame);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
SetupWidgets(true);
|
||||
|
||||
actionStateTimer->start(300);
|
||||
@@ -90,7 +91,10 @@ void MacroActionEdit::SetupWidgets(bool basicSetup)
|
||||
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
|
||||
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
|
||||
this, SLOT(HeaderInfoChanged(const QString &)));
|
||||
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
|
||||
SLOT(ShowVariableMappings(bool)));
|
||||
_section->SetContent(widget, (*_entryData)->GetCollapsed());
|
||||
SetupVarMappings((*_entryData).get());
|
||||
SetFocusPolicyOfWidgets();
|
||||
|
||||
_allWidgetsAreSetup = true;
|
||||
@@ -121,7 +125,10 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
|
||||
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
|
||||
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
|
||||
this, SLOT(HeaderInfoChanged(const QString &)));
|
||||
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
|
||||
SLOT(ShowVariableMappings(bool)));
|
||||
_section->SetContent(widget);
|
||||
SetupVarMappings((*_entryData).get());
|
||||
SetFocusPolicyOfWidgets();
|
||||
}
|
||||
|
||||
|
||||
@@ -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,15 @@ namespace advss {
|
||||
|
||||
const std::string MacroActionVariable::id = "variable";
|
||||
|
||||
std::vector<TempVariableRef> MacroActionVariable::GetTempVarRefs() const
|
||||
{
|
||||
auto refs = MacroSegment::GetTempVarRefs();
|
||||
if (_tempVar.HasValidID()) {
|
||||
refs.push_back(_tempVar);
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
bool MacroActionVariable::_registered = MacroActionFactory::Register(
|
||||
MacroActionVariable::id,
|
||||
{MacroActionVariable::Create, MacroActionVariableEdit::Create,
|
||||
@@ -1450,6 +1459,7 @@ void MacroActionVariableEdit::SelectionChanged(const TempVariableRef &var)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_tempVar = var;
|
||||
IncrementTempVarInUseGeneration();
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -126,10 +126,12 @@ MacroConditionEdit::MacroConditionEdit(
|
||||
_section->AddHeaderWidget(_conditionSelection);
|
||||
_section->AddHeaderWidget(_headerInfo);
|
||||
_section->AddHeaderWidget(_dur);
|
||||
_section->AddHeaderWidget(_varMappingToggle);
|
||||
|
||||
QVBoxLayout *conditionLayout = new QVBoxLayout;
|
||||
conditionLayout->setContentsMargins({7, 7, 7, 7});
|
||||
conditionLayout->addWidget(_section);
|
||||
conditionLayout->addWidget(_outputMappings);
|
||||
_contentLayout->addLayout(conditionLayout);
|
||||
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
@@ -204,7 +206,10 @@ void MacroConditionEdit::SetupWidgets(bool basicSetup)
|
||||
MacroConditionFactory::CreateWidget(id, this, *_entryData);
|
||||
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
|
||||
this, SLOT(HeaderInfoChanged(const QString &)));
|
||||
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
|
||||
SLOT(ShowVariableMappings(bool)));
|
||||
_section->SetContent(widget, (*_entryData)->GetCollapsed());
|
||||
SetupVarMappings((*_entryData).get());
|
||||
SetFocusPolicyOfWidgets();
|
||||
|
||||
_allWidgetsAreSetup = true;
|
||||
@@ -245,7 +250,10 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
|
||||
MacroConditionFactory::CreateWidget(id, this, *_entryData);
|
||||
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
|
||||
this, SLOT(HeaderInfoChanged(const QString &)));
|
||||
QWidget::connect(widget, SIGNAL(ShowVariableMappings(bool)), this,
|
||||
SLOT(ShowVariableMappings(bool)));
|
||||
_section->SetContent(widget);
|
||||
SetupVarMappings((*_entryData).get());
|
||||
_dur->setVisible(MacroConditionFactory::UsesDurationModifier(id));
|
||||
SetFocusPolicyOfWidgets();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,15 @@ namespace advss {
|
||||
|
||||
const std::string MacroConditionTempVar::id = "temp_var";
|
||||
|
||||
std::vector<TempVariableRef> MacroConditionTempVar::GetTempVarRefs() const
|
||||
{
|
||||
auto refs = MacroSegment::GetTempVarRefs();
|
||||
if (_tempVar.HasValidID()) {
|
||||
refs.push_back(_tempVar);
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
bool MacroConditionTempVar::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionTempVar::id,
|
||||
{MacroConditionTempVar::Create, MacroConditionTempVarEdit::Create,
|
||||
@@ -266,6 +275,7 @@ void MacroConditionTempVarEdit::VariableChanged(const TempVariableRef &var)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_tempVar = var;
|
||||
IncrementTempVarInUseGeneration();
|
||||
}
|
||||
|
||||
void MacroConditionTempVarEdit::Variable2Changed(const QString &text)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "macro-segment.hpp"
|
||||
#include "macro.hpp"
|
||||
#include "mouse-wheel-guard.hpp"
|
||||
#include "path-helpers.hpp"
|
||||
#include "section.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
#include "variable.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QEvent>
|
||||
@@ -13,20 +15,84 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
std::vector<TempVariableRef> MacroSegment::GetTempVarRefs() const
|
||||
{
|
||||
std::vector<TempVariableRef> refs;
|
||||
for (const auto &mapping : _varMappings) {
|
||||
if (mapping.tempVar.HasValidID()) {
|
||||
refs.push_back(mapping.tempVar);
|
||||
}
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
void MacroSegment::ApplyVarMappings()
|
||||
{
|
||||
auto macro = GetMacro();
|
||||
for (const auto &mapping : _varMappings) {
|
||||
auto var = mapping.variable.lock();
|
||||
if (!var) {
|
||||
continue;
|
||||
}
|
||||
const auto tempVar = mapping.tempVar.GetTempVariable(macro);
|
||||
if (!tempVar) {
|
||||
continue;
|
||||
}
|
||||
const auto value = tempVar->Value();
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
var->SetValue(*value);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<VarMapping> &MacroSegment::GetVarMappings() const
|
||||
{
|
||||
return _varMappings;
|
||||
}
|
||||
|
||||
void MacroSegment::SetVarMappings(const std::vector<VarMapping> &mappings)
|
||||
{
|
||||
_varMappings = mappings;
|
||||
}
|
||||
|
||||
std::vector<TempVariable> MacroSegment::GetOwnTempVars() const
|
||||
{
|
||||
return _tempVariables;
|
||||
}
|
||||
|
||||
MacroSegment::MacroSegment(Macro *m, bool supportsVariableValue)
|
||||
: _macro(m),
|
||||
_supportsVariableValue(supportsVariableValue)
|
||||
{
|
||||
}
|
||||
|
||||
void MacroSegment::SetVarMappingExpanded(bool expanded)
|
||||
{
|
||||
_varMappingExpanded = expanded;
|
||||
}
|
||||
|
||||
bool MacroSegment::Save(obs_data_t *obj) const
|
||||
{
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_bool(data, "collapsed", _collapsed);
|
||||
obs_data_set_bool(data, "varMappingExpanded", _varMappingExpanded);
|
||||
obs_data_set_bool(data, "useCustomLabel", _useCustomLabel);
|
||||
obs_data_set_string(data, "customLabel", _customLabel.c_str());
|
||||
obs_data_set_bool(data, "enabled", _enabled);
|
||||
obs_data_set_int(data, "version", 1);
|
||||
|
||||
OBSDataArrayAutoRelease mappingsArray = obs_data_array_create();
|
||||
for (const auto &mapping : _varMappings) {
|
||||
OBSDataAutoRelease item = obs_data_create();
|
||||
mapping.tempVar.Save(item, GetMacro(), "tempVar");
|
||||
obs_data_set_string(
|
||||
item, "variable",
|
||||
GetWeakVariableName(mapping.variable).c_str());
|
||||
obs_data_array_push_back(mappingsArray, item);
|
||||
}
|
||||
obs_data_set_array(data, "varMappings", mappingsArray);
|
||||
|
||||
obs_data_set_obj(obj, "segmentSettings", data);
|
||||
return true;
|
||||
}
|
||||
@@ -35,6 +101,7 @@ bool MacroSegment::Load(obs_data_t *obj)
|
||||
{
|
||||
OBSDataAutoRelease data = obs_data_get_obj(obj, "segmentSettings");
|
||||
_collapsed = obs_data_get_bool(data, "collapsed");
|
||||
_varMappingExpanded = obs_data_get_bool(data, "varMappingExpanded");
|
||||
_useCustomLabel = obs_data_get_bool(data, "useCustomLabel");
|
||||
_customLabel = obs_data_get_string(data, "customLabel");
|
||||
obs_data_set_default_bool(data, "enabled", true);
|
||||
@@ -45,6 +112,20 @@ bool MacroSegment::Load(obs_data_t *obj)
|
||||
_enabled = obs_data_get_bool(obj, "enabled");
|
||||
}
|
||||
|
||||
_varMappings.clear();
|
||||
OBSDataArrayAutoRelease mappingsArray =
|
||||
obs_data_get_array(data, "varMappings");
|
||||
const size_t count = obs_data_array_count(mappingsArray);
|
||||
_varMappings.reserve(count);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
OBSDataAutoRelease item = obs_data_array_item(mappingsArray, i);
|
||||
VarMapping mapping;
|
||||
mapping.variable = GetWeakVariableByName(
|
||||
obs_data_get_string(item, "variable"));
|
||||
_varMappings.push_back(std::move(mapping));
|
||||
_varMappings.back().tempVar.Load(item, GetMacro(), "tempVar");
|
||||
}
|
||||
|
||||
ClearAvailableTempvars();
|
||||
return true;
|
||||
}
|
||||
@@ -152,6 +233,16 @@ void MacroSegment::AddTempvar(const std::string &id, const std::string &name,
|
||||
NotifyUIAboutTempVarChange(this);
|
||||
}
|
||||
|
||||
bool MacroSegment::IsTempVarInUse(const std::string &id) const
|
||||
{
|
||||
for (const auto &var : _tempVariables) {
|
||||
if (var.ID() == id) {
|
||||
return var.IsInUse();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MacroSegment::SetTempVarValue(const std::string &id,
|
||||
const std::string &value)
|
||||
{
|
||||
@@ -211,11 +302,38 @@ MacroSegmentEdit::MacroSegmentEdit(QWidget *parent)
|
||||
_headerInfo(new QLabel()),
|
||||
_frame(new QWidget),
|
||||
_contentLayout(new QVBoxLayout),
|
||||
_outputMappings(new TempVarOutputMappingsWidget(this)),
|
||||
_varMappingToggle(new QPushButton(this)),
|
||||
_noBorderframe(new QFrame),
|
||||
_borderFrame(new QFrame),
|
||||
_dropLineAbove(new QFrame),
|
||||
_dropLineBelow(new QFrame)
|
||||
{
|
||||
const auto iconPath = QString::fromStdString(GetDataFilePath(
|
||||
"res/images/" + GetThemeTypeName() + "Variable.svg"));
|
||||
_varMappingToggle->setIcon(QIcon(iconPath));
|
||||
_varMappingToggle->setMaximumWidth(22);
|
||||
_varMappingToggle->setFlat(true);
|
||||
_varMappingToggle->setCheckable(true);
|
||||
_varMappingToggle->setVisible(false);
|
||||
_varMappingToggle->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.outputMappings.toggle"));
|
||||
|
||||
QWidget::connect(_varMappingToggle, &QPushButton::toggled,
|
||||
_outputMappings,
|
||||
&TempVarOutputMappingsWidget::SetPanelExpanded);
|
||||
QWidget::connect(_varMappingToggle, &QPushButton::toggled, this,
|
||||
[this](bool checked) {
|
||||
if (Data()) {
|
||||
Data()->SetVarMappingExpanded(checked);
|
||||
}
|
||||
});
|
||||
QWidget::connect(_outputMappings,
|
||||
&TempVarOutputMappingsWidget::ExpandableChanged,
|
||||
_varMappingToggle, &QPushButton::setVisible);
|
||||
QWidget::connect(_section, &Section::Collapsed, _outputMappings,
|
||||
&TempVarOutputMappingsWidget::SetSectionCollapsed);
|
||||
|
||||
_dropLineAbove->setLineWidth(3);
|
||||
_dropLineAbove->setFixedHeight(11);
|
||||
_dropLineBelow->setLineWidth(3);
|
||||
@@ -277,6 +395,21 @@ MacroSegmentEdit::MacroSegmentEdit(QWidget *parent)
|
||||
_headerInfo->installEventFilter(this);
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::SetupVarMappings(MacroSegment *segment)
|
||||
{
|
||||
_outputMappings->SetSegment(segment);
|
||||
_varMappingToggle->setChecked(segment &&
|
||||
segment->GetVarMappingExpanded());
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::ShowVariableMappings(bool show)
|
||||
{
|
||||
_varMappingToggle->setChecked(show);
|
||||
if (Data()) {
|
||||
Data()->SetVarMappingExpanded(show);
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroSegmentEdit::eventFilter(QObject *obj, QEvent *ev)
|
||||
{
|
||||
if (obj == _headerInfo && ev->type() == QEvent::MouseMove) {
|
||||
|
||||
@@ -9,15 +9,23 @@
|
||||
|
||||
#include <QWidget>
|
||||
#include <QFrame>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QTimer>
|
||||
#include <obs-data.h>
|
||||
#include <memory>
|
||||
|
||||
class QLabel;
|
||||
|
||||
namespace advss {
|
||||
|
||||
class Macro;
|
||||
class Variable;
|
||||
|
||||
struct VarMapping {
|
||||
TempVariableRef tempVar;
|
||||
std::weak_ptr<Variable> variable;
|
||||
};
|
||||
|
||||
class EXPORT MacroSegment : public Lockable {
|
||||
public:
|
||||
@@ -28,12 +36,15 @@ public:
|
||||
int GetIndex() const { return _idx; }
|
||||
void SetCollapsed(bool collapsed) { _collapsed = collapsed; }
|
||||
bool GetCollapsed() const { return _collapsed; }
|
||||
void SetVarMappingExpanded(bool expanded);
|
||||
bool GetVarMappingExpanded() const { return _varMappingExpanded; }
|
||||
void SetUseCustomLabel(bool enable) { _useCustomLabel = enable; }
|
||||
bool GetUseCustomLabel() const { return _useCustomLabel; }
|
||||
void SetCustomLabel(const std::string &label) { _customLabel = label; }
|
||||
std::string GetCustomLabel() const { return _customLabel; }
|
||||
virtual bool Save(obs_data_t *obj) const = 0;
|
||||
virtual bool Load(obs_data_t *obj) = 0;
|
||||
virtual std::vector<TempVariableRef> GetTempVarRefs() const;
|
||||
virtual bool PostLoad();
|
||||
virtual std::string GetShortDesc() const;
|
||||
virtual std::string GetId() const = 0;
|
||||
@@ -42,6 +53,10 @@ public:
|
||||
void SetEnabled(bool);
|
||||
bool Enabled() const;
|
||||
virtual std::string GetVariableValue() const;
|
||||
void ApplyVarMappings();
|
||||
const std::vector<VarMapping> &GetVarMappings() const;
|
||||
void SetVarMappings(const std::vector<VarMapping> &mappings);
|
||||
std::vector<TempVariable> GetOwnTempVars() const;
|
||||
|
||||
protected:
|
||||
friend bool SupportsVariableValue(MacroSegment *);
|
||||
@@ -54,6 +69,7 @@ protected:
|
||||
void AddTempvar(const std::string &id, const std::string &name,
|
||||
const std::string &description = "");
|
||||
|
||||
bool IsTempVarInUse(const std::string &id) const;
|
||||
void SetTempVarValue(const std::string &id, const std::string &value);
|
||||
|
||||
template<typename T, typename = std::enable_if_t<
|
||||
@@ -64,6 +80,14 @@ protected:
|
||||
: std::string("false"));
|
||||
}
|
||||
|
||||
template<typename F, typename = std::enable_if_t<std::is_invocable_v<F>>>
|
||||
void SetTempVarValue(const std::string &id, F &&valueProvider)
|
||||
{
|
||||
if (IsTempVarInUse(id)) {
|
||||
SetTempVarValue(id, valueProvider());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void ClearAvailableTempvars();
|
||||
std::optional<const TempVariable>
|
||||
@@ -77,6 +101,7 @@ private:
|
||||
// UI helper
|
||||
bool _highlight = false;
|
||||
bool _collapsed = false;
|
||||
bool _varMappingExpanded = false;
|
||||
bool _enabled = true;
|
||||
|
||||
// Custom header labels
|
||||
@@ -89,6 +114,7 @@ private:
|
||||
int _variableRefs = 0;
|
||||
std::string _variableValue;
|
||||
std::vector<TempVariable> _tempVariables;
|
||||
std::vector<VarMapping> _varMappings;
|
||||
|
||||
friend class Macro;
|
||||
};
|
||||
@@ -108,8 +134,11 @@ public:
|
||||
virtual std::shared_ptr<MacroSegment> Data() const = 0;
|
||||
virtual void SetupWidgets(bool basicSetup = false) = 0;
|
||||
|
||||
void SetupVarMappings(MacroSegment *segment);
|
||||
|
||||
public slots:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
void ShowVariableMappings(bool show);
|
||||
|
||||
protected slots:
|
||||
void Collapsed(bool) const;
|
||||
@@ -124,6 +153,8 @@ protected:
|
||||
QLabel *_headerInfo;
|
||||
QWidget *_frame;
|
||||
QVBoxLayout *_contentLayout;
|
||||
TempVarOutputMappingsWidget *_outputMappings;
|
||||
QPushButton *_varMappingToggle;
|
||||
bool _allWidgetsAreSetup = false;
|
||||
|
||||
private:
|
||||
|
||||
@@ -670,18 +670,6 @@ void AdvSceneSwitcher::SetupMacroTab()
|
||||
SLOT(HighlightOnChange()));
|
||||
onChangeHighlightTimer.start();
|
||||
|
||||
// Reserve more space for macro edit area than for the macro list
|
||||
ui->macroListMacroEditSplitter->setStretchFactor(0, 1);
|
||||
ui->macroListMacroEditSplitter->setStretchFactor(1, 4);
|
||||
|
||||
if (switcher->saveWindowGeo) {
|
||||
if (shouldRestoreSplitter(
|
||||
switcher->macroListMacroEditSplitterPosition)) {
|
||||
ui->macroListMacroEditSplitter->setSizes(
|
||||
switcher->macroListMacroEditSplitterPosition);
|
||||
}
|
||||
}
|
||||
|
||||
SetupMacroSearchWidgets(ui->macroSearchLayout, ui->macroSearchText,
|
||||
ui->macroSearchClear, ui->macroSearchType,
|
||||
ui->macroSearchRegex,
|
||||
@@ -705,6 +693,26 @@ void AdvSceneSwitcher::SetupMacroTab()
|
||||
ui->actionTriggerMode->addItem(obs_module_text(name),
|
||||
static_cast<int>(mode));
|
||||
}
|
||||
|
||||
ui->macroListBox->setSizePolicy(QSizePolicy::Ignored,
|
||||
QSizePolicy::Preferred);
|
||||
ui->macroListBox->setMinimumWidth(0);
|
||||
ui->macroEditGroup->setSizePolicy(QSizePolicy::Ignored,
|
||||
QSizePolicy::Preferred);
|
||||
ui->macroEditGroup->setMinimumWidth(0);
|
||||
|
||||
if (shouldRestoreSplitter(
|
||||
switcher->macroListMacroEditSplitterPosition)) {
|
||||
ui->macroListMacroEditSplitter->setSizes(
|
||||
switcher->macroListMacroEditSplitterPosition);
|
||||
} else {
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
const auto totalWidth =
|
||||
ui->macroListMacroEditSplitter->width();
|
||||
ui->macroListMacroEditSplitter->setSizes(
|
||||
{totalWidth / 5, totalWidth * 4 / 5});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
|
||||
|
||||
@@ -113,6 +113,7 @@ static bool checkCondition(const std::shared_ptr<MacroCondition> &condition)
|
||||
condition->WithLock([&condition, &conditionMatched]() {
|
||||
conditionMatched = condition->EvaluateCondition();
|
||||
});
|
||||
condition->ApplyVarMappings();
|
||||
const auto endTime = std::chrono::high_resolution_clock::now();
|
||||
const auto timeSpent = endTime - startTime;
|
||||
|
||||
@@ -432,6 +433,7 @@ bool Macro::RunActionsHelper(
|
||||
action->WithLock([&action, &actionResult]() {
|
||||
actionResult = action->PerformAction();
|
||||
});
|
||||
action->ApplyVarMappings();
|
||||
actionsExecutedSuccessfully =
|
||||
actionsExecutedSuccessfully && actionResult;
|
||||
} else {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,6 +172,10 @@ bool ShouldSkipPluginStartOnUncleanShutdown()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (suppressCrashDialog) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This function is called while the plugin settings are being loaded.
|
||||
// Blocking at this stage can cause issues such as OBS failing to start
|
||||
// or crashing.
|
||||
|
||||
@@ -4,4 +4,7 @@ namespace advss {
|
||||
|
||||
bool ShouldSkipPluginStartOnUncleanShutdown();
|
||||
|
||||
bool GetSuppressCrashDialog();
|
||||
void SetSuppressCrashDialog(bool suppress);
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -55,9 +55,7 @@ static void WriteFirstRun(bool value)
|
||||
|
||||
static QString DetectFocusedWindow()
|
||||
{
|
||||
std::string title;
|
||||
GetCurrentWindowTitle(title);
|
||||
return QString::fromStdString(title);
|
||||
return QString::fromStdString(GetCurrentWindowTitle());
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -66,4 +66,15 @@ void Lockable::WithLock(const std::function<void()> &func)
|
||||
func();
|
||||
}
|
||||
|
||||
SuspendLock::SuspendLock(Lockable &lockable)
|
||||
: _mtx(static_cast<std::mutex &>(lockable._mtx))
|
||||
{
|
||||
_mtx.unlock();
|
||||
}
|
||||
|
||||
SuspendLock::~SuspendLock()
|
||||
{
|
||||
_mtx.lock();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -51,6 +51,24 @@ public:
|
||||
|
||||
private:
|
||||
PerInstanceMutex _mtx;
|
||||
|
||||
friend class SuspendLock;
|
||||
};
|
||||
|
||||
// RAII guard that temporarily releases a Lockable's per-segment lock.
|
||||
// Use this inside PerformAction() / CheckCondition() to unblock the UI
|
||||
// during long-running operations while still running on the original segment.
|
||||
// The caller MUST be holding the lock (i.e. be inside WithLock) when
|
||||
// constructing this object; the lock is re-acquired on destruction.
|
||||
class EXPORT SuspendLock {
|
||||
public:
|
||||
SuspendLock(Lockable &lockable);
|
||||
~SuspendLock();
|
||||
SuspendLock(const SuspendLock &) = delete;
|
||||
SuspendLock &operator=(const SuspendLock &) = delete;
|
||||
|
||||
private:
|
||||
std::mutex &_mtx;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -5,14 +5,26 @@
|
||||
#include "macro-edit.hpp"
|
||||
#include "macro-segment.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "sync-helpers.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "variable.hpp"
|
||||
|
||||
#include <QVariant>
|
||||
#include <QAbstractItemView>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QPushButton>
|
||||
#include <QToolButton>
|
||||
#include <QVariant>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
Q_DECLARE_METATYPE(advss::TempVariableRef);
|
||||
|
||||
static std::atomic<uint64_t> tempVarInUseGeneration{0};
|
||||
|
||||
namespace advss {
|
||||
|
||||
TempVariable::TempVariable(const std::string &id, const std::string &name,
|
||||
@@ -120,6 +132,60 @@ TempVariableRef TempVariable::GetRef() const
|
||||
return ref;
|
||||
}
|
||||
|
||||
static bool refsContain(const std::vector<TempVariableRef> &refs,
|
||||
const TempVariableRef &ref)
|
||||
{
|
||||
for (const auto &r : refs) {
|
||||
if (r == ref) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static bool segmentsReferTo(const T &segments, const TempVariableRef &ref)
|
||||
{
|
||||
for (const auto &segment : segments) {
|
||||
if (refsContain(segment->GetTempVarRefs(), ref)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TempVariable::IsInUse() const
|
||||
{
|
||||
const auto currentGen =
|
||||
tempVarInUseGeneration.load(std::memory_order_relaxed);
|
||||
if (_isInUseCacheGeneration == currentGen) {
|
||||
return _isInUseCache;
|
||||
}
|
||||
|
||||
bool inUse = false;
|
||||
const auto ref = GetRef();
|
||||
|
||||
if (ref.HasValidID()) {
|
||||
for (const auto ¯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 +731,302 @@ TempVarSignalManager *TempVarSignalManager::Instance()
|
||||
|
||||
void NotifyUIAboutTempVarChange(MacroSegment *segment)
|
||||
{
|
||||
obs_queue_task(
|
||||
OBS_TASK_UI,
|
||||
IncrementTempVarInUseGeneration();
|
||||
QueueUITask(
|
||||
[](void *segment) {
|
||||
TempVarSignalManager::Instance()->SegmentTempVarsChanged(
|
||||
(MacroSegment *)segment);
|
||||
},
|
||||
segment, false);
|
||||
segment);
|
||||
}
|
||||
|
||||
TempVarOutputMappingsWidget::TempVarOutputMappingsWidget(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_rowsLayout(new QVBoxLayout()),
|
||||
_addButton(new QPushButton(
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.outputMappings.add"),
|
||||
this)),
|
||||
_animation(new QPropertyAnimation(this, "maximumHeight", this))
|
||||
{
|
||||
_rowsLayout->setContentsMargins(0, 0, 0, 0);
|
||||
_rowsLayout->setSpacing(2);
|
||||
auto rowsContainer = new QWidget(this);
|
||||
rowsContainer->setLayout(_rowsLayout);
|
||||
|
||||
auto label = new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.outputMappings"),
|
||||
this);
|
||||
|
||||
auto mainLayout = new QVBoxLayout();
|
||||
mainLayout->addWidget(label);
|
||||
mainLayout->addWidget(rowsContainer);
|
||||
mainLayout->addWidget(_addButton);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_animation->setDuration(300);
|
||||
_animation->setEasingCurve(QEasingCurve::InOutQuad);
|
||||
connect(_animation, &QPropertyAnimation::finished, this,
|
||||
&TempVarOutputMappingsWidget::AnimationFinished);
|
||||
|
||||
hide();
|
||||
|
||||
connect(_addButton, &QPushButton::clicked, this,
|
||||
&TempVarOutputMappingsWidget::Add);
|
||||
connect(TempVarSignalManager::Instance(),
|
||||
SIGNAL(SegmentTempVarsChanged(MacroSegment *)), this,
|
||||
SLOT(SegmentTempVarsChanged(MacroSegment *)));
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::SetSegment(MacroSegment *segment)
|
||||
{
|
||||
_segment = segment;
|
||||
_panelExpanded = false;
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::Add()
|
||||
{
|
||||
if (!_segment) {
|
||||
return;
|
||||
}
|
||||
auto mappings = _segment->GetVarMappings();
|
||||
mappings.push_back(VarMapping{});
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_segment->SetVarMappings(mappings);
|
||||
}
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::Remove(int rowIdx)
|
||||
{
|
||||
if (!_segment) {
|
||||
return;
|
||||
}
|
||||
auto mappings = _segment->GetVarMappings();
|
||||
if (rowIdx < 0 || rowIdx >= (int)mappings.size()) {
|
||||
return;
|
||||
}
|
||||
mappings.erase(mappings.begin() + rowIdx);
|
||||
{
|
||||
auto lock = LockContext();
|
||||
_segment->SetVarMappings(mappings);
|
||||
}
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::SegmentTempVarsChanged(MacroSegment *segment)
|
||||
{
|
||||
if (segment != _segment) {
|
||||
return;
|
||||
}
|
||||
// Re-populate each temp var combobox in case temp vars were added/removed
|
||||
_loading = true;
|
||||
const auto &mappings = _segment->GetVarMappings();
|
||||
for (int i = 0; i < (int)_rows.size(); i++) {
|
||||
auto combo = _rows[i].tempVarCombo;
|
||||
const QSignalBlocker blocker(combo);
|
||||
combo->clear();
|
||||
PopulateTempVarCombo(combo);
|
||||
if (i < (int)mappings.size() &&
|
||||
mappings[i].tempVar.HasValidID()) {
|
||||
QVariant v;
|
||||
v.setValue(mappings[i].tempVar);
|
||||
combo->setCurrentIndex(combo->findData(v));
|
||||
}
|
||||
}
|
||||
_loading = false;
|
||||
UpdateVisibility();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::WriteBackMappings()
|
||||
{
|
||||
if (_loading || !_segment) {
|
||||
return;
|
||||
}
|
||||
std::vector<VarMapping> mappings;
|
||||
for (const auto &row : _rows) {
|
||||
VarMapping m;
|
||||
const int idx = row.tempVarCombo->currentIndex();
|
||||
if (idx >= 0) {
|
||||
m.tempVar = row.tempVarCombo->itemData(idx)
|
||||
.value<TempVariableRef>();
|
||||
}
|
||||
|
||||
auto item = row.varSelection->GetCurrentItem();
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m.variable = GetWeakVariableByName(item->Name());
|
||||
mappings.push_back(std::move(m));
|
||||
}
|
||||
auto lock = LockContext();
|
||||
_segment->SetVarMappings(mappings);
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::Rebuild()
|
||||
{
|
||||
_loading = true;
|
||||
|
||||
// Remove all existing row widgets from layout and delete them
|
||||
for (auto &row : _rows) {
|
||||
_rowsLayout->removeWidget(row.container);
|
||||
delete row.container;
|
||||
}
|
||||
_rows.clear();
|
||||
|
||||
if (!_segment) {
|
||||
_loading = false;
|
||||
UpdateVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &mappings = _segment->GetVarMappings();
|
||||
for (int i = 0; i < (int)mappings.size(); i++) {
|
||||
const auto &mapping = mappings[i];
|
||||
|
||||
auto container = new QWidget(this);
|
||||
auto rowLayout = new QHBoxLayout();
|
||||
rowLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto tempVarCombo = new FilterComboBox(
|
||||
container,
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.select"));
|
||||
tempVarCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
tempVarCombo->setMaximumWidth(350);
|
||||
tempVarCombo->setDuplicatesEnabled(true);
|
||||
PopulateTempVarCombo(tempVarCombo);
|
||||
if (mapping.tempVar.HasValidID()) {
|
||||
QVariant v;
|
||||
v.setValue(mapping.tempVar);
|
||||
tempVarCombo->setCurrentIndex(
|
||||
tempVarCombo->findData(v));
|
||||
}
|
||||
|
||||
auto arrowLabel = new QLabel(container);
|
||||
{
|
||||
QIcon icon;
|
||||
const auto path = (GetThemeTypeName() == "Light")
|
||||
? "theme:Light/right.svg"
|
||||
: "theme:Dark/right.svg";
|
||||
icon.addFile(QString::fromUtf8(path), QSize(),
|
||||
QIcon::Normal, QIcon::Off);
|
||||
arrowLabel->setPixmap(icon.pixmap(16, 16));
|
||||
}
|
||||
|
||||
auto varSel = new VariableSelection(container);
|
||||
varSel->SetVariable(mapping.variable);
|
||||
|
||||
auto removeBtn = new QToolButton(container);
|
||||
removeBtn->setProperty("themeID", QVariant(QString::fromUtf8(
|
||||
"removeIconSmall")));
|
||||
removeBtn->setProperty(
|
||||
"class", QVariant(QString::fromUtf8("icon-trash")));
|
||||
removeBtn->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.outputMappings.remove"));
|
||||
|
||||
rowLayout->addWidget(tempVarCombo, 1);
|
||||
rowLayout->addWidget(arrowLabel);
|
||||
rowLayout->addWidget(varSel, 1);
|
||||
rowLayout->addWidget(removeBtn);
|
||||
container->setLayout(rowLayout);
|
||||
|
||||
_rowsLayout->addWidget(container);
|
||||
_rows.push_back({container, tempVarCombo, varSel});
|
||||
|
||||
const int capturedIdx = i;
|
||||
connect(removeBtn, &QPushButton::clicked, this,
|
||||
[this, capturedIdx]() { Remove(capturedIdx); });
|
||||
connect(tempVarCombo,
|
||||
QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
this, &TempVarOutputMappingsWidget::WriteBackMappings);
|
||||
connect(varSel, &VariableSelection::SelectionChanged, this,
|
||||
[this](const QString &) { WriteBackMappings(); });
|
||||
}
|
||||
|
||||
_loading = false;
|
||||
UpdateVisibility();
|
||||
}
|
||||
|
||||
bool TempVarOutputMappingsWidget::IsExpandable() const
|
||||
{
|
||||
return _segment && !_segment->GetOwnTempVars().empty();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::SetPanelExpanded(bool expanded)
|
||||
{
|
||||
_panelExpanded = expanded;
|
||||
UpdateHeight();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::SetSectionCollapsed(bool collapsed)
|
||||
{
|
||||
_sectionCollapsed = collapsed;
|
||||
UpdateHeight();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::AnimateTo(int targetHeight)
|
||||
{
|
||||
if (_animation->state() == QAbstractAnimation::Running) {
|
||||
_animation->stop();
|
||||
}
|
||||
|
||||
if (targetHeight > 0 && !isVisible()) {
|
||||
setMinimumHeight(0);
|
||||
setMaximumHeight(0);
|
||||
show();
|
||||
}
|
||||
|
||||
const int currentMax = maximumHeight();
|
||||
const int currentHeight = (currentMax >= QWIDGETSIZE_MAX) ? height()
|
||||
: currentMax;
|
||||
|
||||
_animationTargetHeight = targetHeight;
|
||||
_animation->setStartValue(currentHeight);
|
||||
_animation->setEndValue(targetHeight);
|
||||
_animation->start();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::AnimationFinished()
|
||||
{
|
||||
if (_animationTargetHeight > 0) {
|
||||
setMaximumHeight(QWIDGETSIZE_MAX);
|
||||
} else {
|
||||
hide();
|
||||
setMaximumHeight(QWIDGETSIZE_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::UpdateHeight()
|
||||
{
|
||||
const bool shouldShow = _panelExpanded && !_sectionCollapsed &&
|
||||
IsExpandable();
|
||||
if (shouldShow) {
|
||||
AnimateTo(sizeHint().height());
|
||||
} else {
|
||||
AnimateTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::UpdateVisibility()
|
||||
{
|
||||
const bool expandable = IsExpandable();
|
||||
emit ExpandableChanged(expandable);
|
||||
UpdateHeight();
|
||||
}
|
||||
|
||||
void TempVarOutputMappingsWidget::PopulateTempVarCombo(
|
||||
FilterComboBox *combo) const
|
||||
{
|
||||
if (!_segment) {
|
||||
return;
|
||||
}
|
||||
for (const auto &var : _segment->GetOwnTempVars()) {
|
||||
QVariant v;
|
||||
v.setValue(var.GetRef());
|
||||
combo->addItem(QString::fromStdString(var.Name()), v);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
#include <obs-data.h>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <QEnterEvent>
|
||||
#include <QEvent>
|
||||
#include <QStringList>
|
||||
#include <string>
|
||||
|
||||
class QPropertyAnimation;
|
||||
class QPushButton;
|
||||
class QVBoxLayout;
|
||||
|
||||
namespace advss {
|
||||
|
||||
class Macro;
|
||||
@@ -17,6 +18,9 @@ class MacroEdit;
|
||||
class MacroSegment;
|
||||
class TempVariableRef;
|
||||
class TempVariableSelection;
|
||||
class TempVarOutputMappingsWidget;
|
||||
class VariableSelection;
|
||||
struct VarMapping;
|
||||
|
||||
// TempVariables are variables that are local to a given macro.
|
||||
// They can be created and used by macro segments.
|
||||
@@ -44,6 +48,7 @@ public:
|
||||
void SetValue(const std::string &val);
|
||||
void InvalidateValue();
|
||||
TempVariableRef GetRef() const;
|
||||
EXPORT bool IsInUse() const;
|
||||
|
||||
private:
|
||||
std::string _id = "";
|
||||
@@ -53,6 +58,8 @@ private:
|
||||
mutable std::mutex _lastValuesMutex;
|
||||
std::vector<std::string> _lastValues;
|
||||
bool _valueIsValid = false;
|
||||
mutable bool _isInUseCache = false;
|
||||
mutable uint64_t _isInUseCacheGeneration = UINT64_MAX;
|
||||
|
||||
std::weak_ptr<MacroSegment> _segment;
|
||||
friend TempVariableSelection;
|
||||
@@ -122,5 +129,50 @@ private:
|
||||
};
|
||||
|
||||
void NotifyUIAboutTempVarChange(MacroSegment *);
|
||||
EXPORT void IncrementTempVarInUseGeneration();
|
||||
|
||||
class ADVSS_EXPORT TempVarOutputMappingsWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TempVarOutputMappingsWidget(QWidget *parent);
|
||||
void SetSegment(MacroSegment *segment);
|
||||
void SetPanelExpanded(bool expanded);
|
||||
void SetSectionCollapsed(bool collapsed);
|
||||
bool IsExpandable() const;
|
||||
|
||||
signals:
|
||||
void ExpandableChanged(bool);
|
||||
|
||||
private slots:
|
||||
void Add();
|
||||
void Remove(int rowIdx);
|
||||
void SegmentTempVarsChanged(MacroSegment *segment);
|
||||
void WriteBackMappings();
|
||||
void AnimationFinished();
|
||||
|
||||
private:
|
||||
void Rebuild();
|
||||
void UpdateVisibility();
|
||||
void UpdateHeight();
|
||||
void AnimateTo(int targetHeight);
|
||||
void PopulateTempVarCombo(FilterComboBox *combo) const;
|
||||
|
||||
struct MappingRow {
|
||||
QWidget *container;
|
||||
FilterComboBox *tempVarCombo;
|
||||
VariableSelection *varSelection;
|
||||
};
|
||||
|
||||
MacroSegment *_segment = nullptr;
|
||||
QVBoxLayout *_rowsLayout;
|
||||
QPushButton *_addButton;
|
||||
std::vector<MappingRow> _rows;
|
||||
bool _loading = false;
|
||||
bool _panelExpanded = false;
|
||||
bool _sectionCollapsed = false;
|
||||
int _animationTargetHeight = 0;
|
||||
QPropertyAnimation *_animation;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,6 +9,15 @@ namespace advss {
|
||||
|
||||
const std::string MacroActionFilter::id = "filter";
|
||||
|
||||
std::vector<TempVariableRef> MacroActionFilter::GetTempVarRefs() const
|
||||
{
|
||||
auto refs = MacroSegment::GetTempVarRefs();
|
||||
if (_tempVar.HasValidID()) {
|
||||
refs.push_back(_tempVar);
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
bool MacroActionFilter::_registered = MacroActionFactory::Register(
|
||||
MacroActionFilter::id,
|
||||
{MacroActionFilter::Create, MacroActionFilterEdit::Create,
|
||||
@@ -428,6 +437,7 @@ void MacroActionFilterEdit::SelectionChanged(const TempVariableRef &var)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_tempVar = var;
|
||||
IncrementTempVarInUseGeneration();
|
||||
}
|
||||
|
||||
void MacroActionFilterEdit::SelectionChanged(const SourceSetting &setting)
|
||||
|
||||
@@ -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,15 @@ namespace advss {
|
||||
|
||||
const std::string MacroActionSource::id = "source";
|
||||
|
||||
std::vector<TempVariableRef> MacroActionSource::GetTempVarRefs() const
|
||||
{
|
||||
auto refs = MacroSegment::GetTempVarRefs();
|
||||
if (_tempVar.HasValidID()) {
|
||||
refs.push_back(_tempVar);
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
bool MacroActionSource::_registered = MacroActionFactory::Register(
|
||||
MacroActionSource::id,
|
||||
{MacroActionSource::Create, MacroActionSourceEdit::Create,
|
||||
@@ -45,6 +54,10 @@ const static std::map<MacroActionSource::Action, std::string> actionTypes = {
|
||||
"AdvSceneSwitcher.action.source.type.closeFilterDialog"},
|
||||
{MacroActionSource::Action::CLOSE_PROPERTIES_DIALOG,
|
||||
"AdvSceneSwitcher.action.source.type.closePropertiesDialog"},
|
||||
{MacroActionSource::Action::GET_SETTING,
|
||||
"AdvSceneSwitcher.action.source.type.getSetting"},
|
||||
{MacroActionSource::Action::GET_SETTINGS,
|
||||
"AdvSceneSwitcher.action.source.type.getSettings"},
|
||||
};
|
||||
|
||||
const static std::map<obs_deinterlace_mode, std::string> deinterlaceModes = {
|
||||
@@ -273,6 +286,22 @@ bool MacroActionSource::PerformAction()
|
||||
"OBSBasicProperties");
|
||||
});
|
||||
break;
|
||||
case Action::GET_SETTING: {
|
||||
const auto value =
|
||||
GetSourceSettingValue(_source.GetSource(), _setting);
|
||||
if (value) {
|
||||
SetTempVarValue("setting", *value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Action::GET_SETTINGS: {
|
||||
const auto settings =
|
||||
GetSourceSettings(_source.GetSource(), true);
|
||||
if (settings) {
|
||||
SetTempVarValue("settings", *settings);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -360,6 +389,31 @@ void MacroActionSource::ResolveVariablesToFixedValues()
|
||||
_manualSettingValue.ResolveVariables();
|
||||
}
|
||||
|
||||
void MacroActionSource::SetupTempVars()
|
||||
{
|
||||
MacroAction::SetupTempVars();
|
||||
switch (_action) {
|
||||
case Action::GET_SETTING:
|
||||
AddTempvar("setting",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.source.setting"));
|
||||
break;
|
||||
case Action::GET_SETTINGS:
|
||||
AddTempvar("settings",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.source.settings"));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionSource::SetAction(Action action)
|
||||
{
|
||||
_action = action;
|
||||
SetupTempVars();
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto &[actionType, name] : actionTypes) {
|
||||
@@ -502,7 +556,7 @@ void MacroActionSourceEdit::UpdateEntryData()
|
||||
|
||||
const auto weakSource = _entryData->_source.GetSource();
|
||||
_settingsButtons->SetSelection(weakSource, _entryData->_button);
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->GetAction()));
|
||||
_sources->SetSource(_entryData->_source);
|
||||
_sourceSettings->SetSelection(weakSource, _entryData->_setting);
|
||||
_settingsString->setPlainText(_entryData->_settingsString);
|
||||
@@ -537,7 +591,7 @@ void MacroActionSourceEdit::SourceChanged(const SourceSelection &source)
|
||||
void MacroActionSourceEdit::ActionChanged(int value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_action = static_cast<MacroActionSource::Action>(value);
|
||||
_entryData->SetAction(static_cast<MacroActionSource::Action>(value));
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
@@ -607,6 +661,7 @@ void MacroActionSourceEdit::SelectionChanged(const TempVariableRef &var)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_tempVar = var;
|
||||
IncrementTempVarInUseGeneration();
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::SettingsInputMethodChanged(int idx)
|
||||
@@ -664,19 +719,29 @@ static QString GetIndividualListEntryName()
|
||||
|
||||
void MacroActionSourceEdit::SetWidgetVisibility()
|
||||
{
|
||||
const auto action = _entryData->GetAction();
|
||||
|
||||
const bool isSetSettings = action ==
|
||||
MacroActionSource::Action::SETTINGS;
|
||||
const bool isGetSetting = action ==
|
||||
MacroActionSource::Action::GET_SETTING;
|
||||
const bool isGetSettings = action ==
|
||||
MacroActionSource::Action::GET_SETTINGS;
|
||||
|
||||
SetLayoutVisible(_settingsLayout,
|
||||
_entryData->_action ==
|
||||
MacroActionSource::Action::SETTINGS);
|
||||
isSetSettings || isGetSetting || isGetSettings);
|
||||
_settingsInputMethods->setVisible(isSetSettings);
|
||||
_sourceSettings->setVisible(
|
||||
_entryData->_action == MacroActionSource::Action::SETTINGS &&
|
||||
_entryData->_settingsInputMethod !=
|
||||
MacroActionSource::SettingsInputMethod::JSON_STRING);
|
||||
(isSetSettings &&
|
||||
_entryData->_settingsInputMethod !=
|
||||
MacroActionSource::SettingsInputMethod::JSON_STRING) ||
|
||||
isGetSetting);
|
||||
_settingsString->setVisible(
|
||||
_entryData->_action == MacroActionSource::Action::SETTINGS &&
|
||||
isSetSettings &&
|
||||
_entryData->_settingsInputMethod ==
|
||||
MacroActionSource::SettingsInputMethod::JSON_STRING);
|
||||
_getSettings->setVisible(
|
||||
_entryData->_action == MacroActionSource::Action::SETTINGS &&
|
||||
isSetSettings &&
|
||||
_entryData->_settingsInputMethod !=
|
||||
MacroActionSource::SettingsInputMethod::
|
||||
INDIVIDUAL_TEMPVAR);
|
||||
@@ -685,13 +750,12 @@ void MacroActionSourceEdit::SetWidgetVisibility()
|
||||
GetIndividualListEntryName(),
|
||||
_entryData->_setting.IsList());
|
||||
|
||||
_tempVars->setVisible(_entryData->_action ==
|
||||
MacroActionSource::Action::SETTINGS &&
|
||||
_tempVars->setVisible(isSetSettings &&
|
||||
_entryData->_settingsInputMethod ==
|
||||
MacroActionSource::SettingsInputMethod::
|
||||
INDIVIDUAL_TEMPVAR);
|
||||
|
||||
if (_entryData->_action == MacroActionSource::Action::SETTINGS &&
|
||||
if (isSetSettings &&
|
||||
(_entryData->_settingsInputMethod ==
|
||||
MacroActionSource::SettingsInputMethod::INDIVIDUAL_MANUAL ||
|
||||
_entryData->_settingsInputMethod ==
|
||||
@@ -704,35 +768,33 @@ void MacroActionSourceEdit::SetWidgetVisibility()
|
||||
_manualSettingValue->hide();
|
||||
}
|
||||
|
||||
const bool showWarning =
|
||||
_entryData->_action == MacroActionSource::Action::ENABLE ||
|
||||
_entryData->_action == MacroActionSource::Action::DISABLE;
|
||||
const bool showWarning = action == MacroActionSource::Action::ENABLE ||
|
||||
action == MacroActionSource::Action::DISABLE;
|
||||
_warning->setVisible(showWarning);
|
||||
_settingsButtons->setVisible(
|
||||
_entryData->_action ==
|
||||
MacroActionSource::Action::SETTINGS_BUTTON);
|
||||
action == MacroActionSource::Action::SETTINGS_BUTTON);
|
||||
_deinterlaceMode->setVisible(
|
||||
_entryData->_action ==
|
||||
MacroActionSource::Action::DEINTERLACE_MODE);
|
||||
action == MacroActionSource::Action::DEINTERLACE_MODE);
|
||||
_deinterlaceOrder->setVisible(
|
||||
_entryData->_action ==
|
||||
MacroActionSource::Action::DEINTERLACE_FIELD_ORDER);
|
||||
action == MacroActionSource::Action::DEINTERLACE_FIELD_ORDER);
|
||||
|
||||
_refreshSettingSelection->setVisible(
|
||||
(_entryData->_settingsInputMethod ==
|
||||
MacroActionSource::SettingsInputMethod::
|
||||
INDIVIDUAL_MANUAL ||
|
||||
_entryData->_settingsInputMethod ==
|
||||
MacroActionSource::SettingsInputMethod::
|
||||
INDIVIDUAL_LIST_ENTRY) &&
|
||||
((isSetSettings &&
|
||||
(_entryData->_settingsInputMethod ==
|
||||
MacroActionSource::SettingsInputMethod::
|
||||
INDIVIDUAL_MANUAL ||
|
||||
_entryData->_settingsInputMethod ==
|
||||
MacroActionSource::SettingsInputMethod::
|
||||
INDIVIDUAL_LIST_ENTRY)) ||
|
||||
isGetSetting) &&
|
||||
_entryData->_source.GetType() ==
|
||||
SourceSelection::Type::VARIABLE);
|
||||
|
||||
_acceptDialog->setVisible(
|
||||
_entryData->_action ==
|
||||
MacroActionSource::Action::CLOSE_FILTER_DIALOG ||
|
||||
_entryData->_action ==
|
||||
MacroActionSource::Action::CLOSE_PROPERTIES_DIALOG);
|
||||
action == MacroActionSource::Action::CLOSE_FILTER_DIALOG ||
|
||||
action == MacroActionSource::Action::CLOSE_PROPERTIES_DIALOG);
|
||||
|
||||
emit ShowVariableMappings(isGetSetting || isGetSettings);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
|
||||
@@ -15,26 +15,22 @@ namespace advss {
|
||||
class MacroActionSource : public MacroAction {
|
||||
public:
|
||||
MacroActionSource(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetShortDesc() const;
|
||||
std::string GetId() const { return id; };
|
||||
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m);
|
||||
std::shared_ptr<MacroAction> Copy() const;
|
||||
void ResolveVariablesToFixedValues();
|
||||
|
||||
SourceSelection _source;
|
||||
SourceSettingButton _button;
|
||||
StringVariable _settingsString = "";
|
||||
StringVariable _manualSettingValue = "";
|
||||
obs_deinterlace_mode _deinterlaceMode = OBS_DEINTERLACE_MODE_DISABLE;
|
||||
obs_deinterlace_field_order _deinterlaceOrder =
|
||||
OBS_DEINTERLACE_FIELD_ORDER_TOP;
|
||||
TempVariableRef _tempVar;
|
||||
SourceSetting _setting;
|
||||
bool _acceptDialog = false;
|
||||
bool PerformAction();
|
||||
void LogAction() const;
|
||||
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
|
||||
std::string GetShortDesc() const;
|
||||
std::string GetId() const { return id; };
|
||||
|
||||
void ResolveVariablesToFixedValues();
|
||||
void SetupTempVars();
|
||||
std::vector<TempVariableRef> GetTempVarRefs() const;
|
||||
|
||||
enum class Action {
|
||||
ENABLE,
|
||||
@@ -50,8 +46,23 @@ public:
|
||||
CLOSE_INTERACTION_DIALOG,
|
||||
CLOSE_FILTER_DIALOG,
|
||||
CLOSE_PROPERTIES_DIALOG,
|
||||
GET_SETTING,
|
||||
GET_SETTINGS,
|
||||
};
|
||||
Action _action = Action::SETTINGS;
|
||||
|
||||
void SetAction(Action);
|
||||
Action GetAction() const { return _action; }
|
||||
|
||||
SourceSelection _source;
|
||||
SourceSettingButton _button;
|
||||
StringVariable _settingsString = "";
|
||||
StringVariable _manualSettingValue = "";
|
||||
obs_deinterlace_mode _deinterlaceMode = OBS_DEINTERLACE_MODE_DISABLE;
|
||||
obs_deinterlace_field_order _deinterlaceOrder =
|
||||
OBS_DEINTERLACE_FIELD_ORDER_TOP;
|
||||
TempVariableRef _tempVar;
|
||||
SourceSetting _setting;
|
||||
bool _acceptDialog = false;
|
||||
|
||||
enum class SettingsInputMethod {
|
||||
INDIVIDUAL_MANUAL,
|
||||
@@ -63,6 +74,8 @@ public:
|
||||
SettingsInputMethod::INDIVIDUAL_MANUAL;
|
||||
|
||||
private:
|
||||
Action _action = Action::SETTINGS;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
@@ -100,6 +113,7 @@ private slots:
|
||||
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
void ShowVariableMappings(bool show);
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "regex-config.hpp"
|
||||
#include "variable-string.hpp"
|
||||
#include "variable-line-edit.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
|
||||
@@ -21,8 +21,11 @@ public:
|
||||
}
|
||||
|
||||
StringVariable _process;
|
||||
bool _checkFocus = true;
|
||||
RegexConfig _regex = RegexConfig::PartialMatchRegexConfig();
|
||||
bool _checkFocus = true;
|
||||
bool _checkPath = false;
|
||||
StringVariable _processPath;
|
||||
RegexConfig _pathRegex = RegexConfig::PartialMatchRegexConfig();
|
||||
|
||||
private:
|
||||
void SetupTempVars();
|
||||
@@ -54,6 +57,9 @@ private slots:
|
||||
void ProcessChanged(const QString &text);
|
||||
void RegexChanged(const RegexConfig &);
|
||||
void FocusChanged(int state);
|
||||
void CheckPathChanged(int state);
|
||||
void ProcessPathChanged(const QString &text);
|
||||
void PathRegexChanged(const RegexConfig &);
|
||||
void UpdateFocusProcess();
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
@@ -66,6 +72,10 @@ private:
|
||||
QCheckBox *_focused;
|
||||
QLabel *_focusProcess;
|
||||
QHBoxLayout *_focusLayout;
|
||||
QCheckBox *_checkPath;
|
||||
VariableLineEdit *_processPath;
|
||||
RegexConfigWidget *_pathRegex;
|
||||
QHBoxLayout *_pathLayout;
|
||||
QTimer _timer;
|
||||
std::shared_ptr<MacroConditionProcess> _entryData;
|
||||
bool _loading = true;
|
||||
|
||||
@@ -135,8 +135,7 @@ static bool foregroundWindowChanged()
|
||||
|
||||
bool MacroConditionWindow::CheckCondition()
|
||||
{
|
||||
std::vector<std::string> windowList;
|
||||
GetWindowList(windowList);
|
||||
const auto windowList = GetWindowList();
|
||||
bool match = false;
|
||||
if (_windowRegex.Enabled()) {
|
||||
match = WindowRegexMatches(windowList);
|
||||
|
||||
237
plugins/base/utils/source-interaction-recorder.cpp
Normal file
237
plugins/base/utils/source-interaction-recorder.cpp
Normal file
@@ -0,0 +1,237 @@
|
||||
#include "source-interaction-recorder.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <obs-interaction.h>
|
||||
|
||||
#include <QEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QWheelEvent>
|
||||
|
||||
namespace advss {
|
||||
|
||||
SourceInteractionRecorder::SourceInteractionRecorder(QWidget *parent,
|
||||
obs_weak_source_t *source)
|
||||
: QDialog(parent),
|
||||
_source(source),
|
||||
_preview(new SourcePreviewWidget(this, source)),
|
||||
_stepList(new SourceInteractionStepList(this)),
|
||||
_startStopButton(new QPushButton(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.record.start"),
|
||||
this))
|
||||
{
|
||||
setWindowTitle(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.record.title"));
|
||||
|
||||
_preview->setMouseTracking(true);
|
||||
_preview->setFocusPolicy(Qt::StrongFocus);
|
||||
_preview->installEventFilter(this);
|
||||
|
||||
_stepList->HideControls();
|
||||
_stepList->SetMinListHeight(50);
|
||||
|
||||
auto buttonRow = new QHBoxLayout;
|
||||
buttonRow->addWidget(_startStopButton);
|
||||
buttonRow->addStretch();
|
||||
|
||||
auto acceptButton = new QPushButton(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.record.accept"),
|
||||
this);
|
||||
buttonRow->addWidget(acceptButton);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->addWidget(_preview, 1);
|
||||
layout->addWidget(_stepList);
|
||||
layout->addLayout(buttonRow);
|
||||
setLayout(layout);
|
||||
|
||||
connect(_startStopButton, &QPushButton::clicked, this,
|
||||
&SourceInteractionRecorder::StartStop);
|
||||
connect(acceptButton, &QPushButton::clicked, this, [this]() {
|
||||
if (_recording) {
|
||||
StartStop();
|
||||
}
|
||||
emit StepsRecorded(_steps);
|
||||
accept();
|
||||
});
|
||||
}
|
||||
|
||||
SourceInteractionRecorder::~SourceInteractionRecorder() {}
|
||||
|
||||
void SourceInteractionRecorder::StartStop()
|
||||
{
|
||||
_recording = !_recording;
|
||||
if (_recording) {
|
||||
_steps.clear();
|
||||
_stepList->Clear();
|
||||
_firstEvent = true;
|
||||
_startStopButton->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.record.stop"));
|
||||
} else {
|
||||
_startStopButton->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.record.start"));
|
||||
}
|
||||
}
|
||||
|
||||
void SourceInteractionRecorder::AppendStep(const SourceInteractionStep &step)
|
||||
{
|
||||
if (!_firstEvent) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
int ms = static_cast<int>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - _lastEventTime)
|
||||
.count());
|
||||
if (ms > 10) {
|
||||
SourceInteractionStep wait;
|
||||
wait.type = SourceInteractionStep::Type::WAIT;
|
||||
wait.waitMs.SetValue(ms);
|
||||
_steps.push_back(wait);
|
||||
_stepList->Insert(
|
||||
QString::fromStdString(wait.ToString()));
|
||||
}
|
||||
}
|
||||
_firstEvent = false;
|
||||
_lastEventTime = std::chrono::steady_clock::now();
|
||||
|
||||
_steps.push_back(step);
|
||||
_stepList->Insert(QString::fromStdString(step.ToString()));
|
||||
_stepList->ScrollToBottom();
|
||||
}
|
||||
|
||||
bool SourceInteractionRecorder::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (obj != _preview || !_recording) {
|
||||
return QDialog::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
switch (event->type()) {
|
||||
case QEvent::MouseButtonPress:
|
||||
case QEvent::MouseButtonRelease:
|
||||
case QEvent::MouseButtonDblClick:
|
||||
return HandleMouseClick(static_cast<QMouseEvent *>(event));
|
||||
case QEvent::MouseMove:
|
||||
return HandleMouseMove(static_cast<QMouseEvent *>(event));
|
||||
case QEvent::Wheel:
|
||||
return HandleMouseWheel(static_cast<QWheelEvent *>(event));
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
return HandleKeyEvent(static_cast<QKeyEvent *>(event));
|
||||
case QEvent::FocusIn:
|
||||
case QEvent::FocusOut: {
|
||||
OBSSourceAutoRelease source =
|
||||
obs_weak_source_get_source(_source);
|
||||
if (source) {
|
||||
obs_source_send_focus(source,
|
||||
event->type() == QEvent::FocusIn);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QDialog::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
bool SourceInteractionRecorder::HandleMouseClick(QMouseEvent *event)
|
||||
{
|
||||
SourceInteractionStep step;
|
||||
step.type = SourceInteractionStep::Type::MOUSE_CLICK;
|
||||
|
||||
int srcX = 0, srcY = 0;
|
||||
QPoint pos = event->pos();
|
||||
_preview->GetSourceRelativeXY(pos.x(), pos.y(), srcX, srcY);
|
||||
step.x.SetValue(srcX);
|
||||
step.y.SetValue(srcY);
|
||||
|
||||
step.mouseUp = (event->type() == QEvent::MouseButtonRelease);
|
||||
step.clickCount.SetValue(
|
||||
(event->type() == QEvent::MouseButtonDblClick) ? 2 : 1);
|
||||
|
||||
switch (event->button()) {
|
||||
case Qt::LeftButton:
|
||||
step.button = MOUSE_LEFT;
|
||||
break;
|
||||
case Qt::MiddleButton:
|
||||
step.button = MOUSE_MIDDLE;
|
||||
break;
|
||||
case Qt::RightButton:
|
||||
step.button = MOUSE_RIGHT;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
AppendStep(step);
|
||||
|
||||
OBSSourceAutoRelease source = obs_weak_source_get_source(_source);
|
||||
if (source) {
|
||||
PerformInteractionStep(source, step);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SourceInteractionRecorder::HandleMouseMove(QMouseEvent *event)
|
||||
{
|
||||
SourceInteractionStep step;
|
||||
step.type = SourceInteractionStep::Type::MOUSE_MOVE;
|
||||
int srcX = 0, srcY = 0;
|
||||
QPoint pos = event->pos();
|
||||
_preview->GetSourceRelativeXY(pos.x(), pos.y(), srcX, srcY);
|
||||
step.x.SetValue(srcX);
|
||||
step.y.SetValue(srcY);
|
||||
|
||||
// Always forward moves to the source so hover/cursor effects work,
|
||||
// but only record them when a button is held (drag), to reduce noise.
|
||||
OBSSourceAutoRelease source = obs_weak_source_get_source(_source);
|
||||
if (source) {
|
||||
PerformInteractionStep(source, step);
|
||||
}
|
||||
|
||||
if (event->buttons() != Qt::NoButton) {
|
||||
AppendStep(step);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SourceInteractionRecorder::HandleMouseWheel(QWheelEvent *event)
|
||||
{
|
||||
SourceInteractionStep step;
|
||||
step.type = SourceInteractionStep::Type::MOUSE_WHEEL;
|
||||
int srcX = 0, srcY = 0;
|
||||
const QPointF pos = event->position();
|
||||
_preview->GetSourceRelativeXY((int)pos.x(), (int)pos.y(), srcX, srcY);
|
||||
step.x.SetValue(srcX);
|
||||
step.y.SetValue(srcY);
|
||||
const QPoint angle = event->angleDelta();
|
||||
step.wheelDeltaX.SetValue(angle.x());
|
||||
step.wheelDeltaY.SetValue(angle.y());
|
||||
AppendStep(step);
|
||||
|
||||
OBSSourceAutoRelease source = obs_weak_source_get_source(_source);
|
||||
if (source) {
|
||||
PerformInteractionStep(source, step);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SourceInteractionRecorder::HandleKeyEvent(QKeyEvent *event)
|
||||
{
|
||||
SourceInteractionStep step;
|
||||
step.type = SourceInteractionStep::Type::KEY_PRESS;
|
||||
step.keyUp = (event->type() == QEvent::KeyRelease);
|
||||
step.nativeVkey.SetValue(static_cast<int>(event->nativeVirtualKey()));
|
||||
step.text = event->text().toStdString();
|
||||
AppendStep(step);
|
||||
|
||||
OBSSourceAutoRelease source = obs_weak_source_get_source(_source);
|
||||
if (source) {
|
||||
PerformInteractionStep(source, step);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
54
plugins/base/utils/source-interaction-recorder.hpp
Normal file
54
plugins/base/utils/source-interaction-recorder.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include "source-interaction-step.hpp"
|
||||
#include "source-interaction-step-list.hpp"
|
||||
#include "source-preview-widget.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
|
||||
#include <QDialog>
|
||||
#include <QPushButton>
|
||||
|
||||
class QKeyEvent;
|
||||
class QMouseEvent;
|
||||
class QWheelEvent;
|
||||
|
||||
namespace advss {
|
||||
|
||||
class SourceInteractionRecorder : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SourceInteractionRecorder(QWidget *parent, obs_weak_source_t *source);
|
||||
~SourceInteractionRecorder();
|
||||
|
||||
const std::vector<SourceInteractionStep> &GetSteps() const
|
||||
{
|
||||
return _steps;
|
||||
}
|
||||
|
||||
signals:
|
||||
void StepsRecorded(const std::vector<SourceInteractionStep> &);
|
||||
|
||||
private slots:
|
||||
void StartStop();
|
||||
|
||||
private:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
bool HandleMouseClick(QMouseEvent *);
|
||||
bool HandleMouseMove(QMouseEvent *);
|
||||
bool HandleMouseWheel(QWheelEvent *);
|
||||
bool HandleKeyEvent(QKeyEvent *);
|
||||
void AppendStep(const SourceInteractionStep &);
|
||||
|
||||
obs_weak_source_t *_source;
|
||||
SourcePreviewWidget *_preview;
|
||||
SourceInteractionStepList *_stepList;
|
||||
QPushButton *_startStopButton;
|
||||
|
||||
bool _recording = false;
|
||||
std::vector<SourceInteractionStep> _steps;
|
||||
std::chrono::steady_clock::time_point _lastEventTime;
|
||||
bool _firstEvent = true;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
315
plugins/base/utils/source-interaction-step-edit.cpp
Normal file
315
plugins/base/utils/source-interaction-step-edit.cpp
Normal file
@@ -0,0 +1,315 @@
|
||||
#include "source-interaction-step-edit.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "variable-line-edit.hpp"
|
||||
#include "variable-spinbox.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
static const std::map<SourceInteractionStep::Type, std::string> stepTypeNames = {
|
||||
{SourceInteractionStep::Type::MOUSE_MOVE,
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.mouseMove"},
|
||||
{SourceInteractionStep::Type::MOUSE_CLICK,
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.mouseClick"},
|
||||
{SourceInteractionStep::Type::MOUSE_WHEEL,
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.mouseWheel"},
|
||||
{SourceInteractionStep::Type::KEY_PRESS,
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.keyPress"},
|
||||
{SourceInteractionStep::Type::TYPE_TEXT,
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.typeText"},
|
||||
{SourceInteractionStep::Type::WAIT,
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.wait"},
|
||||
};
|
||||
|
||||
static const std::map<obs_mouse_button_type, std::string> mouseButtonNames = {
|
||||
{MOUSE_LEFT, "AdvSceneSwitcher.action.sourceInteraction.button.left"},
|
||||
{MOUSE_MIDDLE,
|
||||
"AdvSceneSwitcher.action.sourceInteraction.button.middle"},
|
||||
{MOUSE_RIGHT, "AdvSceneSwitcher.action.sourceInteraction.button.right"},
|
||||
};
|
||||
|
||||
static void populateTypeCombo(QComboBox *combo)
|
||||
{
|
||||
for (const auto &[type, name] : stepTypeNames) {
|
||||
combo->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(type));
|
||||
}
|
||||
}
|
||||
|
||||
static void populateButtonCombo(QComboBox *combo)
|
||||
{
|
||||
for (const auto &[btn, name] : mouseButtonNames) {
|
||||
combo->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(btn));
|
||||
}
|
||||
}
|
||||
|
||||
static QHBoxLayout *labelledRow(const char *labelKey, QWidget *w)
|
||||
{
|
||||
auto row = new QHBoxLayout;
|
||||
row->addWidget(new QLabel(obs_module_text(labelKey)));
|
||||
row->addWidget(w);
|
||||
row->addStretch();
|
||||
return row;
|
||||
}
|
||||
|
||||
SourceInteractionStepEdit::SourceInteractionStepEdit(
|
||||
QWidget *parent, const SourceInteractionStep &step)
|
||||
: QWidget(parent),
|
||||
_typeCombo(new QComboBox(this)),
|
||||
_fields(new QStackedWidget(this)),
|
||||
_step(step)
|
||||
{
|
||||
populateTypeCombo(_typeCombo);
|
||||
|
||||
auto mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto typeRow = labelledRow(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.type",
|
||||
_typeCombo);
|
||||
mainLayout->addLayout(typeRow);
|
||||
mainLayout->addWidget(_fields);
|
||||
setLayout(mainLayout);
|
||||
|
||||
connect(_typeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
this, &SourceInteractionStepEdit::TypeChanged);
|
||||
|
||||
RebuildFields();
|
||||
|
||||
int idx = _typeCombo->findData(static_cast<int>(_step.type));
|
||||
if (idx >= 0) {
|
||||
_typeCombo->setCurrentIndex(idx);
|
||||
}
|
||||
}
|
||||
|
||||
void SourceInteractionStepEdit::TypeChanged(int)
|
||||
{
|
||||
_step.type = static_cast<SourceInteractionStep::Type>(
|
||||
_typeCombo->currentData().toInt());
|
||||
RebuildFields();
|
||||
emit StepChanged(_step);
|
||||
}
|
||||
|
||||
void SourceInteractionStepEdit::UpdateStep()
|
||||
{
|
||||
emit StepChanged(_step);
|
||||
}
|
||||
|
||||
void SourceInteractionStepEdit::RebuildFields()
|
||||
{
|
||||
while (_fields->count() > 0) {
|
||||
auto w = _fields->widget(0);
|
||||
_fields->removeWidget(w);
|
||||
delete w;
|
||||
}
|
||||
|
||||
auto makeVarSpin = [](int min, int max,
|
||||
const NumberVariable<int> &val) {
|
||||
auto sb = new VariableSpinBox;
|
||||
sb->setMinimum(min);
|
||||
sb->setMaximum(max);
|
||||
sb->SetValue(val);
|
||||
return sb;
|
||||
};
|
||||
|
||||
switch (_step.type) {
|
||||
case SourceInteractionStep::Type::MOUSE_MOVE:
|
||||
case SourceInteractionStep::Type::MOUSE_CLICK:
|
||||
case SourceInteractionStep::Type::MOUSE_WHEEL: {
|
||||
auto page = new QWidget;
|
||||
auto layout = new QVBoxLayout(page);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto xSpin = makeVarSpin(-32768, 32767, _step.x);
|
||||
auto ySpin = makeVarSpin(-32768, 32767, _step.y);
|
||||
layout->addLayout(labelledRow(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.x",
|
||||
xSpin));
|
||||
layout->addLayout(labelledRow(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.y",
|
||||
ySpin));
|
||||
|
||||
connect(xSpin,
|
||||
QOverload<const NumberVariable<int> &>::of(
|
||||
&GenericVariableSpinbox::NumberVariableChanged),
|
||||
this, [this, xSpin](const NumberVariable<int> &) {
|
||||
_step.x = xSpin->Value();
|
||||
UpdateStep();
|
||||
});
|
||||
connect(ySpin,
|
||||
QOverload<const NumberVariable<int> &>::of(
|
||||
&GenericVariableSpinbox::NumberVariableChanged),
|
||||
this, [this, ySpin](const NumberVariable<int> &) {
|
||||
_step.y = ySpin->Value();
|
||||
UpdateStep();
|
||||
});
|
||||
|
||||
if (_step.type == SourceInteractionStep::Type::MOUSE_CLICK) {
|
||||
auto btnCombo = new QComboBox;
|
||||
populateButtonCombo(btnCombo);
|
||||
btnCombo->setCurrentIndex(btnCombo->findData(
|
||||
static_cast<int>(_step.button)));
|
||||
|
||||
auto upCheck = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.mouseUp"));
|
||||
upCheck->setChecked(_step.mouseUp);
|
||||
|
||||
auto countSpin = makeVarSpin(1, 3, _step.clickCount);
|
||||
|
||||
layout->addLayout(labelledRow(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.button",
|
||||
btnCombo));
|
||||
layout->addWidget(upCheck);
|
||||
layout->addLayout(labelledRow(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.clickCount",
|
||||
countSpin));
|
||||
|
||||
connect(btnCombo,
|
||||
QOverload<int>::of(
|
||||
&QComboBox::currentIndexChanged),
|
||||
this, [this, btnCombo]() {
|
||||
_step.button = static_cast<
|
||||
obs_mouse_button_type>(
|
||||
btnCombo->currentData().toInt());
|
||||
UpdateStep();
|
||||
});
|
||||
connect(upCheck, &QCheckBox::stateChanged, this,
|
||||
[this, upCheck]() {
|
||||
_step.mouseUp = upCheck->isChecked();
|
||||
UpdateStep();
|
||||
});
|
||||
connect(countSpin,
|
||||
QOverload<const NumberVariable<int> &>::of(
|
||||
&GenericVariableSpinbox::
|
||||
NumberVariableChanged),
|
||||
this,
|
||||
[this, countSpin](const NumberVariable<int> &) {
|
||||
_step.clickCount = countSpin->Value();
|
||||
UpdateStep();
|
||||
});
|
||||
} else if (_step.type ==
|
||||
SourceInteractionStep::Type::MOUSE_WHEEL) {
|
||||
auto dxSpin =
|
||||
makeVarSpin(-1200, 1200, _step.wheelDeltaX);
|
||||
auto dySpin =
|
||||
makeVarSpin(-1200, 1200, _step.wheelDeltaY);
|
||||
layout->addLayout(labelledRow(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.wheelDx",
|
||||
dxSpin));
|
||||
layout->addLayout(labelledRow(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.wheelDy",
|
||||
dySpin));
|
||||
connect(dxSpin,
|
||||
QOverload<const NumberVariable<int> &>::of(
|
||||
&GenericVariableSpinbox::
|
||||
NumberVariableChanged),
|
||||
this,
|
||||
[this, dxSpin](const NumberVariable<int> &) {
|
||||
_step.wheelDeltaX = dxSpin->Value();
|
||||
UpdateStep();
|
||||
});
|
||||
connect(dySpin,
|
||||
QOverload<const NumberVariable<int> &>::of(
|
||||
&GenericVariableSpinbox::
|
||||
NumberVariableChanged),
|
||||
this,
|
||||
[this, dySpin](const NumberVariable<int> &) {
|
||||
_step.wheelDeltaY = dySpin->Value();
|
||||
UpdateStep();
|
||||
});
|
||||
}
|
||||
|
||||
_fields->addWidget(page);
|
||||
_fields->setCurrentWidget(page);
|
||||
break;
|
||||
}
|
||||
|
||||
case SourceInteractionStep::Type::KEY_PRESS: {
|
||||
auto page = new QWidget;
|
||||
auto layout = new QVBoxLayout(page);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto vkeySpin = makeVarSpin(0, 0xFFFF, _step.nativeVkey);
|
||||
auto upCheck = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.keyUp"));
|
||||
upCheck->setChecked(_step.keyUp);
|
||||
|
||||
layout->addLayout(labelledRow(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.vkey",
|
||||
vkeySpin));
|
||||
layout->addWidget(upCheck);
|
||||
|
||||
connect(vkeySpin,
|
||||
QOverload<const NumberVariable<int> &>::of(
|
||||
&GenericVariableSpinbox::NumberVariableChanged),
|
||||
this, [this, vkeySpin](const NumberVariable<int> &) {
|
||||
_step.nativeVkey = vkeySpin->Value();
|
||||
UpdateStep();
|
||||
});
|
||||
connect(upCheck, &QCheckBox::stateChanged, this,
|
||||
[this, upCheck]() {
|
||||
_step.keyUp = upCheck->isChecked();
|
||||
UpdateStep();
|
||||
});
|
||||
|
||||
_fields->addWidget(page);
|
||||
_fields->setCurrentWidget(page);
|
||||
break;
|
||||
}
|
||||
|
||||
case SourceInteractionStep::Type::TYPE_TEXT: {
|
||||
auto page = new QWidget;
|
||||
auto layout = new QVBoxLayout(page);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto textEdit = new VariableLineEdit(page);
|
||||
textEdit->setText(_step.text);
|
||||
layout->addLayout(labelledRow(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.text",
|
||||
textEdit));
|
||||
|
||||
connect(textEdit, &QLineEdit::editingFinished, this,
|
||||
[this, textEdit]() {
|
||||
_step.text = textEdit->text().toStdString();
|
||||
UpdateStep();
|
||||
});
|
||||
|
||||
_fields->addWidget(page);
|
||||
_fields->setCurrentWidget(page);
|
||||
break;
|
||||
}
|
||||
|
||||
case SourceInteractionStep::Type::WAIT: {
|
||||
auto page = new QWidget;
|
||||
auto layout = new QVBoxLayout(page);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto msSpin = makeVarSpin(0, 60000, _step.waitMs);
|
||||
layout->addLayout(labelledRow(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.edit.waitMs",
|
||||
msSpin));
|
||||
|
||||
connect(msSpin,
|
||||
QOverload<const NumberVariable<int> &>::of(
|
||||
&GenericVariableSpinbox::NumberVariableChanged),
|
||||
this, [this, msSpin](const NumberVariable<int> &) {
|
||||
_step.waitMs = msSpin->Value();
|
||||
UpdateStep();
|
||||
});
|
||||
|
||||
_fields->addWidget(page);
|
||||
_fields->setCurrentWidget(page);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
32
plugins/base/utils/source-interaction-step-edit.hpp
Normal file
32
plugins/base/utils/source-interaction-step-edit.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include "source-interaction-step.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QStackedWidget>
|
||||
#include <QWidget>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class SourceInteractionStepEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SourceInteractionStepEdit(QWidget *parent,
|
||||
const SourceInteractionStep &step);
|
||||
SourceInteractionStep GetStep() const { return _step; }
|
||||
|
||||
signals:
|
||||
void StepChanged(const SourceInteractionStep &);
|
||||
|
||||
private slots:
|
||||
void TypeChanged(int);
|
||||
void UpdateStep();
|
||||
|
||||
private:
|
||||
void RebuildFields();
|
||||
|
||||
QComboBox *_typeCombo;
|
||||
QStackedWidget *_fields;
|
||||
SourceInteractionStep _step;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
99
plugins/base/utils/source-interaction-step-list.cpp
Normal file
99
plugins/base/utils/source-interaction-step-list.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "source-interaction-step-list.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
namespace advss {
|
||||
|
||||
SourceInteractionStepList::SourceInteractionStepList(QWidget *parent)
|
||||
: ListEditor(parent)
|
||||
{
|
||||
_list->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
connect(_list, &QListWidget::currentRowChanged, this,
|
||||
&SourceInteractionStepList::RowSelected);
|
||||
SetPlaceholderText(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.record.placeholder"));
|
||||
SetMaxListHeight(350);
|
||||
}
|
||||
|
||||
void SourceInteractionStepList::SetSteps(
|
||||
const std::vector<SourceInteractionStep> &steps)
|
||||
{
|
||||
_steps = steps;
|
||||
|
||||
_list->clear();
|
||||
for (const auto &step : _steps) {
|
||||
_list->addItem(QString::fromStdString(step.ToString()));
|
||||
}
|
||||
|
||||
UpdateListSize();
|
||||
|
||||
_list->setCurrentRow(-1);
|
||||
}
|
||||
|
||||
void SourceInteractionStepList::UpdateStep(int row,
|
||||
const SourceInteractionStep &step)
|
||||
{
|
||||
if (row < 0 || row >= (int)_steps.size()) {
|
||||
return;
|
||||
}
|
||||
_steps[row] = step;
|
||||
_list->item(row)->setText(QString::fromStdString(step.ToString()));
|
||||
}
|
||||
|
||||
void SourceInteractionStepList::Add()
|
||||
{
|
||||
_steps.emplace_back();
|
||||
_list->addItem(QString::fromStdString(_steps.back().ToString()));
|
||||
QTimer::singleShot(0, this, [this]() { UpdateListSize(); });
|
||||
emit StepsChanged(_steps);
|
||||
_list->setCurrentRow((int)_steps.size() - 1);
|
||||
}
|
||||
|
||||
void SourceInteractionStepList::Remove()
|
||||
{
|
||||
const QList<QListWidgetItem *> selected = _list->selectedItems();
|
||||
if (selected.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
std::vector<int> rows;
|
||||
rows.reserve(selected.size());
|
||||
for (const auto *item : selected) {
|
||||
rows.push_back(_list->row(item));
|
||||
}
|
||||
std::sort(rows.begin(), rows.end(), std::greater<int>());
|
||||
for (int row : rows) {
|
||||
if (row >= 0 && row < (int)_steps.size()) {
|
||||
_steps.erase(_steps.begin() + row);
|
||||
delete _list->takeItem(row);
|
||||
}
|
||||
}
|
||||
QTimer::singleShot(0, this, [this]() { UpdateListSize(); });
|
||||
emit StepsChanged(_steps);
|
||||
}
|
||||
|
||||
void SourceInteractionStepList::Up()
|
||||
{
|
||||
int row = _list->currentRow();
|
||||
if (row <= 0 || row >= (int)_steps.size()) {
|
||||
return;
|
||||
}
|
||||
std::swap(_steps[row], _steps[row - 1]);
|
||||
_list->insertItem(row - 1, _list->takeItem(row));
|
||||
_list->setCurrentRow(row - 1);
|
||||
emit StepsChanged(_steps);
|
||||
}
|
||||
|
||||
void SourceInteractionStepList::Down()
|
||||
{
|
||||
int row = _list->currentRow();
|
||||
if (row < 0 || row >= (int)_steps.size() - 1) {
|
||||
return;
|
||||
}
|
||||
std::swap(_steps[row], _steps[row + 1]);
|
||||
_list->insertItem(row + 1, _list->takeItem(row));
|
||||
_list->setCurrentRow(row + 1);
|
||||
emit StepsChanged(_steps);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
38
plugins/base/utils/source-interaction-step-list.hpp
Normal file
38
plugins/base/utils/source-interaction-step-list.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include "list-editor.hpp"
|
||||
#include "source-interaction-step.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class SourceInteractionStepList : public ListEditor {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SourceInteractionStepList(QWidget *parent = nullptr);
|
||||
|
||||
void SetSteps(const std::vector<SourceInteractionStep> &steps);
|
||||
void UpdateStep(int row, const SourceInteractionStep &step);
|
||||
int CurrentRow() const { return _list->currentRow(); }
|
||||
void SetCurrentRow(int row) { _list->setCurrentRow(row); }
|
||||
void Clear() const { _list->clear(); }
|
||||
void HideControls() const { _controls->hide(); }
|
||||
void Insert(const QString &value) const { _list->addItem(value); }
|
||||
void ScrollToBottom() const { _list->scrollToBottom(); }
|
||||
void AddControlWidget(QWidget *widget) { _controls->AddWidget(widget); }
|
||||
|
||||
signals:
|
||||
void StepsChanged(const std::vector<SourceInteractionStep> &);
|
||||
void RowSelected(int row);
|
||||
|
||||
private slots:
|
||||
void Add() override;
|
||||
void Remove() override;
|
||||
void Up() override;
|
||||
void Down() override;
|
||||
|
||||
private:
|
||||
std::vector<SourceInteractionStep> _steps;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
190
plugins/base/utils/source-interaction-step.cpp
Normal file
190
plugins/base/utils/source-interaction-step.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
#include "source-interaction-step.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "variable.hpp"
|
||||
|
||||
#include <obs-interaction.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace advss {
|
||||
|
||||
static QString varOrNum(const NumberVariable<int> &v)
|
||||
{
|
||||
if (!v.IsFixedType()) {
|
||||
return QString::fromStdString(
|
||||
GetWeakVariableName(v.GetVariable()));
|
||||
}
|
||||
return QString::number(v.GetFixedValue());
|
||||
}
|
||||
|
||||
bool SourceInteractionStep::Save(obs_data_t *obj) const
|
||||
{
|
||||
obs_data_set_int(obj, "type", static_cast<int>(type));
|
||||
x.Save(obj, "x");
|
||||
y.Save(obj, "y");
|
||||
obs_data_set_int(obj, "button", static_cast<int>(button));
|
||||
obs_data_set_bool(obj, "mouseUp", mouseUp);
|
||||
clickCount.Save(obj, "clickCount");
|
||||
wheelDeltaX.Save(obj, "wheelDeltaX");
|
||||
wheelDeltaY.Save(obj, "wheelDeltaY");
|
||||
nativeVkey.Save(obj, "nativeVkey");
|
||||
obs_data_set_int(obj, "modifiers", modifiers);
|
||||
obs_data_set_bool(obj, "keyUp", keyUp);
|
||||
text.Save(obj, "text");
|
||||
waitMs.Save(obj, "waitMs");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SourceInteractionStep::Load(obs_data_t *obj)
|
||||
{
|
||||
type = static_cast<Type>(obs_data_get_int(obj, "type"));
|
||||
x.Load(obj, "x");
|
||||
y.Load(obj, "y");
|
||||
button = static_cast<obs_mouse_button_type>(
|
||||
obs_data_get_int(obj, "button"));
|
||||
mouseUp = obs_data_get_bool(obj, "mouseUp");
|
||||
clickCount.Load(obj, "clickCount");
|
||||
wheelDeltaX.Load(obj, "wheelDeltaX");
|
||||
wheelDeltaY.Load(obj, "wheelDeltaY");
|
||||
nativeVkey.Load(obj, "nativeVkey");
|
||||
modifiers = static_cast<uint32_t>(obs_data_get_int(obj, "modifiers"));
|
||||
keyUp = obs_data_get_bool(obj, "keyUp");
|
||||
text.Load(obj, "text");
|
||||
waitMs.Load(obj, "waitMs");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string SourceInteractionStep::ToString() const
|
||||
{
|
||||
switch (type) {
|
||||
case Type::MOUSE_MOVE:
|
||||
return QString(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseMove"))
|
||||
.arg(varOrNum(x))
|
||||
.arg(varOrNum(y))
|
||||
.toStdString();
|
||||
case Type::MOUSE_CLICK: {
|
||||
const char *btnKey =
|
||||
(button == MOUSE_LEFT)
|
||||
? "AdvSceneSwitcher.action.sourceInteraction.button.left"
|
||||
: (button == MOUSE_MIDDLE)
|
||||
? "AdvSceneSwitcher.action.sourceInteraction.button.middle"
|
||||
: "AdvSceneSwitcher.action.sourceInteraction.button.right";
|
||||
const char *dirKey =
|
||||
mouseUp ? "AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseUp"
|
||||
: "AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseDown";
|
||||
QString result =
|
||||
QString(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseClick"))
|
||||
.arg(obs_module_text(dirKey))
|
||||
.arg(obs_module_text(btnKey))
|
||||
.arg(varOrNum(x))
|
||||
.arg(varOrNum(y));
|
||||
if (!clickCount.IsFixedType() ||
|
||||
clickCount.GetFixedValue() > 1) {
|
||||
result +=
|
||||
QString(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseClickCount"))
|
||||
.arg(varOrNum(clickCount));
|
||||
}
|
||||
return result.toStdString();
|
||||
}
|
||||
case Type::MOUSE_WHEEL:
|
||||
return QString(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.listEntry.mouseWheel"))
|
||||
.arg(varOrNum(x))
|
||||
.arg(varOrNum(y))
|
||||
.arg(varOrNum(wheelDeltaX))
|
||||
.arg(varOrNum(wheelDeltaY))
|
||||
.toStdString();
|
||||
case Type::KEY_PRESS: {
|
||||
const char *dirKey =
|
||||
keyUp ? "AdvSceneSwitcher.action.sourceInteraction.step.listEntry.keyUp"
|
||||
: "AdvSceneSwitcher.action.sourceInteraction.step.listEntry.keyDown";
|
||||
if (text.empty()) {
|
||||
return QString(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.listEntry.keyPress"))
|
||||
.arg(obs_module_text(dirKey))
|
||||
.toStdString();
|
||||
}
|
||||
return QString(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.listEntry.keyPressWithText"))
|
||||
.arg(obs_module_text(dirKey))
|
||||
.arg(QString::fromStdString(text))
|
||||
.toStdString();
|
||||
}
|
||||
case Type::TYPE_TEXT:
|
||||
return QString(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.listEntry.typeText"))
|
||||
.arg(QString::fromStdString(text))
|
||||
.toStdString();
|
||||
case Type::WAIT:
|
||||
return QString(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sourceInteraction.step.listEntry.wait"))
|
||||
.arg(varOrNum(waitMs))
|
||||
.toStdString();
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
void PerformInteractionStep(obs_source_t *source,
|
||||
const SourceInteractionStep &step)
|
||||
{
|
||||
switch (step.type) {
|
||||
case SourceInteractionStep::Type::MOUSE_MOVE: {
|
||||
obs_mouse_event e{};
|
||||
e.x = step.x;
|
||||
e.y = step.y;
|
||||
obs_source_send_mouse_move(source, &e, false);
|
||||
break;
|
||||
}
|
||||
case SourceInteractionStep::Type::MOUSE_CLICK: {
|
||||
obs_mouse_event e{};
|
||||
e.modifiers = step.modifiers;
|
||||
e.x = step.x;
|
||||
e.y = step.y;
|
||||
obs_source_send_mouse_click(source, &e, step.button,
|
||||
step.mouseUp, step.clickCount);
|
||||
break;
|
||||
}
|
||||
case SourceInteractionStep::Type::MOUSE_WHEEL: {
|
||||
obs_mouse_event e{};
|
||||
e.modifiers = step.modifiers;
|
||||
e.x = step.x;
|
||||
e.y = step.y;
|
||||
obs_source_send_mouse_wheel(source, &e, step.wheelDeltaX,
|
||||
step.wheelDeltaY);
|
||||
break;
|
||||
}
|
||||
case SourceInteractionStep::Type::KEY_PRESS: {
|
||||
std::string textCopy = step.text;
|
||||
obs_key_event e{};
|
||||
e.modifiers = step.modifiers;
|
||||
e.text = textCopy.data();
|
||||
e.native_vkey =
|
||||
static_cast<uint32_t>(step.nativeVkey.GetValue());
|
||||
obs_source_send_key_click(source, &e, step.keyUp);
|
||||
break;
|
||||
}
|
||||
case SourceInteractionStep::Type::TYPE_TEXT: {
|
||||
const std::string resolvedText = step.text;
|
||||
for (const char c : resolvedText) {
|
||||
std::string ch(1, c);
|
||||
obs_key_event e{};
|
||||
e.text = ch.data();
|
||||
obs_source_send_key_click(source, &e, false);
|
||||
obs_source_send_key_click(source, &e, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SourceInteractionStep::Type::WAIT:
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(step.waitMs));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
48
plugins/base/utils/source-interaction-step.hpp
Normal file
48
plugins/base/utils/source-interaction-step.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
#include "variable-number.hpp"
|
||||
#include "variable-string.hpp"
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <obs-interaction.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
struct SourceInteractionStep {
|
||||
enum class Type {
|
||||
MOUSE_MOVE,
|
||||
MOUSE_CLICK,
|
||||
MOUSE_WHEEL,
|
||||
KEY_PRESS,
|
||||
TYPE_TEXT,
|
||||
WAIT,
|
||||
};
|
||||
|
||||
Type type = Type::MOUSE_MOVE;
|
||||
|
||||
// Mouse fields
|
||||
NumberVariable<int> x = 0;
|
||||
NumberVariable<int> y = 0;
|
||||
obs_mouse_button_type button = MOUSE_LEFT;
|
||||
bool mouseUp = false;
|
||||
NumberVariable<int> clickCount = 1;
|
||||
NumberVariable<int> wheelDeltaX = 0;
|
||||
NumberVariable<int> wheelDeltaY = 120;
|
||||
|
||||
// Key / text fields
|
||||
NumberVariable<int> nativeVkey = 0;
|
||||
uint32_t modifiers = 0; // INTERACT_*
|
||||
bool keyUp = false;
|
||||
StringVariable text; // for TYPE_TEXT or key text
|
||||
|
||||
// Wait field (ms)
|
||||
NumberVariable<int> waitMs = 100;
|
||||
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string ToString() const;
|
||||
};
|
||||
|
||||
void PerformInteractionStep(obs_source_t *source,
|
||||
const SourceInteractionStep &step);
|
||||
|
||||
} // namespace advss
|
||||
149
plugins/base/utils/source-preview-widget.cpp
Normal file
149
plugins/base/utils/source-preview-widget.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
#include "source-preview-widget.hpp"
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <graphics/graphics.h>
|
||||
|
||||
#include <QResizeEvent>
|
||||
|
||||
namespace advss {
|
||||
|
||||
static int32_t clampToSource(int32_t v, uint32_t max)
|
||||
{
|
||||
if (v < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (v > (int32_t)max) {
|
||||
return (int32_t)max;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
SourcePreviewWidget::SourcePreviewWidget(QWidget *parent,
|
||||
obs_weak_source_t *source)
|
||||
: QWidget(parent),
|
||||
_source(source)
|
||||
{
|
||||
setAttribute(Qt::WA_PaintOnScreen);
|
||||
setAttribute(Qt::WA_NoSystemBackground);
|
||||
setMinimumSize(320, 240);
|
||||
}
|
||||
|
||||
SourcePreviewWidget::~SourcePreviewWidget()
|
||||
{
|
||||
if (_display) {
|
||||
obs_display_remove_draw_callback(
|
||||
_display, SourcePreviewWidget::DrawCallback, this);
|
||||
obs_display_destroy(_display);
|
||||
_display = nullptr;
|
||||
}
|
||||
|
||||
if (_showing) {
|
||||
OBSSourceAutoRelease source =
|
||||
obs_weak_source_get_source(_source);
|
||||
if (source) {
|
||||
obs_source_dec_showing(source);
|
||||
}
|
||||
_showing = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SourcePreviewWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
|
||||
if (!_display) {
|
||||
WId wid = winId();
|
||||
if (!wid) {
|
||||
return;
|
||||
}
|
||||
|
||||
gs_init_data info{};
|
||||
#if defined(_WIN32)
|
||||
info.window.hwnd = reinterpret_cast<void *>(wid);
|
||||
#elif defined(__APPLE__)
|
||||
info.window.view = reinterpret_cast<id>(wid);
|
||||
#else
|
||||
info.window.id = static_cast<uint32_t>(wid);
|
||||
info.window.display = nullptr;
|
||||
#endif
|
||||
info.format = GS_BGRA;
|
||||
info.zsformat = GS_ZS_NONE;
|
||||
info.cx = static_cast<uint32_t>(width());
|
||||
info.cy = static_cast<uint32_t>(height());
|
||||
|
||||
_display = obs_display_create(&info, 0x000000);
|
||||
if (_display) {
|
||||
obs_display_add_draw_callback(
|
||||
_display, SourcePreviewWidget::DrawCallback,
|
||||
this);
|
||||
OBSSourceAutoRelease source =
|
||||
obs_weak_source_get_source(_source);
|
||||
if (source) {
|
||||
obs_source_inc_showing(source);
|
||||
_showing = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
obs_display_resize(_display, static_cast<uint32_t>(width()),
|
||||
static_cast<uint32_t>(height()));
|
||||
}
|
||||
}
|
||||
|
||||
void SourcePreviewWidget::DrawCallback(void *param, uint32_t cx, uint32_t cy)
|
||||
{
|
||||
auto self = static_cast<SourcePreviewWidget *>(param);
|
||||
|
||||
OBSSourceAutoRelease source = obs_weak_source_get_source(self->_source);
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t srcW = std::max(obs_source_get_width(source), 1u);
|
||||
uint32_t srcH = std::max(obs_source_get_height(source), 1u);
|
||||
|
||||
float scaleX = static_cast<float>(cx) / srcW;
|
||||
float scaleY = static_cast<float>(cy) / srcH;
|
||||
float scale = std::min(scaleX, scaleY);
|
||||
|
||||
int newW = static_cast<int>(scale * srcW);
|
||||
int newH = static_cast<int>(scale * srcH);
|
||||
int offX = (static_cast<int>(cx) - newW) / 2;
|
||||
int offY = (static_cast<int>(cy) - newH) / 2;
|
||||
|
||||
self->_offsetX = offX;
|
||||
self->_offsetY = offY;
|
||||
self->_scale = scale;
|
||||
|
||||
gs_viewport_push();
|
||||
gs_projection_push();
|
||||
gs_ortho(0.0f, static_cast<float>(srcW), 0.0f, static_cast<float>(srcH),
|
||||
-100.0f, 100.0f);
|
||||
gs_set_viewport(offX, offY, newW, newH);
|
||||
|
||||
obs_source_video_render(source);
|
||||
|
||||
gs_projection_pop();
|
||||
gs_viewport_pop();
|
||||
}
|
||||
|
||||
bool SourcePreviewWidget::GetSourceRelativeXY(int widgetX, int widgetY,
|
||||
int &srcX, int &srcY) const
|
||||
{
|
||||
OBSSourceAutoRelease source = obs_weak_source_get_source(_source);
|
||||
if (!source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t srcW = std::max(obs_source_get_width(source), 1u);
|
||||
uint32_t srcH = std::max(obs_source_get_height(source), 1u);
|
||||
|
||||
srcX = static_cast<int>((widgetX - _offsetX) / _scale);
|
||||
srcY = static_cast<int>((widgetY - _offsetY) / _scale);
|
||||
srcX = static_cast<int>(
|
||||
clampToSource(static_cast<int32_t>(srcX), srcW));
|
||||
srcY = static_cast<int>(
|
||||
clampToSource(static_cast<int32_t>(srcY), srcH));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
40
plugins/base/utils/source-preview-widget.hpp
Normal file
40
plugins/base/utils/source-preview-widget.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <obs.h>
|
||||
#include <graphics/graphics.h>
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QResizeEvent;
|
||||
|
||||
namespace advss {
|
||||
|
||||
// A native window widget that renders an OBS source via obs_display.
|
||||
// Stores letterbox geometry for coordinate mapping.
|
||||
class SourcePreviewWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SourcePreviewWidget(QWidget *parent, obs_weak_source_t *source);
|
||||
~SourcePreviewWidget();
|
||||
|
||||
// Maps a widget-local point to source-space coordinates.
|
||||
bool GetSourceRelativeXY(int widgetX, int widgetY, int &srcX,
|
||||
int &srcY) const;
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *) override;
|
||||
QPaintEngine *paintEngine() const override { return nullptr; }
|
||||
|
||||
private:
|
||||
static void DrawCallback(void *param, uint32_t cx, uint32_t cy);
|
||||
|
||||
obs_weak_source_t *_source;
|
||||
obs_display_t *_display = nullptr;
|
||||
bool _showing = false;
|
||||
|
||||
int _offsetX = 0;
|
||||
int _offsetY = 0;
|
||||
float _scale = 1.0f;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
@@ -48,8 +48,17 @@ if(MSVC)
|
||||
endif()
|
||||
|
||||
target_sources(
|
||||
${PROJECT_NAME} PRIVATE macro-action-http.cpp macro-action-http.hpp
|
||||
key-value-list.cpp key-value-list.hpp)
|
||||
${PROJECT_NAME}
|
||||
PRIVATE macro-action-http.cpp
|
||||
macro-action-http.hpp
|
||||
macro-condition-http.cpp
|
||||
macro-condition-http.hpp
|
||||
http-server.cpp
|
||||
http-server.hpp
|
||||
http-server-tab.cpp
|
||||
http-server-tab.hpp
|
||||
key-value-list.cpp
|
||||
key-value-list.hpp)
|
||||
|
||||
setup_advss_plugin(${PROJECT_NAME})
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user