Compare commits

..

3 Commits

Author SHA1 Message Date
WarmUpTill
189fa5c314 toolbar 2026-01-20 20:10:21 +01:00
WarmUpTill
393d70be33 widget wrapper 2026-01-19 19:08:33 +01:00
WarmUpTill
422c1a39cb layout stuff 2026-01-18 12:30:07 +01:00
156 changed files with 1991 additions and 9096 deletions

View File

@@ -166,7 +166,6 @@ target_sources(
lib/macro/macro-tab.cpp
lib/macro/macro-tree.cpp
lib/macro/macro-tree.hpp
lib/macro/macro-websocket-trigger.cpp
lib/macro/macro.cpp
lib/macro/macro.hpp)
@@ -204,8 +203,6 @@ target_sources(
lib/utils/file-selection.hpp
lib/utils/filter-combo-box.cpp
lib/utils/filter-combo-box.hpp
lib/utils/first-run-wizard.cpp
lib/utils/first-run-wizard.hpp
lib/utils/help-icon.hpp
lib/utils/help-icon.cpp
lib/utils/item-selection-helpers.cpp
@@ -456,29 +453,14 @@ else()
endif()
if(PROCPS2_INCLUDE_DIR)
find_package(PkgConfig REQUIRED)
pkg_check_modules(libproc2 IMPORTED_TARGET libproc2<4.0.5 QUIET)
if(libproc2_FOUND)
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS2_USE_INFO)
endif()
pkg_check_modules(libproc2 REQUIRED IMPORTED_TARGET libproc2)
set(PROC_INCLUDE_DIR "${PROCPS2_INCLUDE_DIR}")
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS2_AVAILABLE)
set(PROCESS_CONDITION_SUPPORTED 1)
# Check if PIDS_VAL takes 4 arguments (old API, pre-4.0.5) or 3 (new API)
include(CheckCSourceCompiles)
set(CMAKE_REQUIRED_INCLUDES "${PROCPS2_INCLUDE_DIR}")
set(CMAKE_REQUIRED_LIBRARIES proc2)
check_c_source_compiles(
"
#include <libproc2/pids.h>
int main(void) {
struct pids_stack *s = 0;
struct pids_info *i = 0;
(void)PIDS_VAL(0, str, s, i);
return 0;
}
"
PROCPS2_PIDS_VAL_TAKES_INFO)
if(PROCPS2_PIDS_VAL_TAKES_INFO)
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS2_USE_INFO)
endif()
endif()
if(NOT DEFINED PROCESS_CONDITION_SUPPORTED)
message(

View File

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

View File

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

View File

@@ -76,6 +76,7 @@ AdvSceneSwitcher.macroTab.name="Name:"
AdvSceneSwitcher.macroTab.run="Makro ausführen"
AdvSceneSwitcher.macroTab.runFail="Ausführen von \"%1\" fehlgeschlagen!\nEntweder ist eine der Aktionen fehlgeschlagen oder das Makro wird bereits ausgeführt.\nSoll die aktuelle Ausführung gestoppt werden?"
AdvSceneSwitcher.macroTab.runInParallel="Parallel zu anderen Makros ausführen"
AdvSceneSwitcher.macroTab.onChange="Nur bei Änderung ausführen"
AdvSceneSwitcher.macroTab.defaultname="Makro %1"
AdvSceneSwitcher.macroTab.defaultGroupName="Gruppe %1"
AdvSceneSwitcher.macroTab.removeGroupPopup.text="Sicher, dass \"%1\" und alle zugehörigen Elemente gelöscht werden?"
@@ -145,7 +146,11 @@ 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.layout="{{filePath}}{{conditions}}{{regex}}"
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.media="Medien"
AdvSceneSwitcher.condition.media.source="Quelle"
AdvSceneSwitcher.condition.media.anyOnScene="Beliebige Medienquelle in"
@@ -286,14 +291,14 @@ AdvSceneSwitcher.condition.replay.state.started="Replay Buffer gestartet"
AdvSceneSwitcher.condition.replay.state.saved="Replay Buffer gespeichert"
AdvSceneSwitcher.condition.replay.entry="{{state}}"
AdvSceneSwitcher.condition.date="Datum"
AdvSceneSwitcher.day.any="Beliebiger Tag"
AdvSceneSwitcher.day.monday="Montag"
AdvSceneSwitcher.day.tuesday="Dienstag"
AdvSceneSwitcher.day.wednesday="Mittwoch"
AdvSceneSwitcher.day.thursday="Donnerstag"
AdvSceneSwitcher.day.friday="Freitag"
AdvSceneSwitcher.day.saturday="Samstag"
AdvSceneSwitcher.day.sunday="Sonntag"
AdvSceneSwitcher.condition.date.anyDay="Beliebiger Tag"
AdvSceneSwitcher.condition.date.monday="Montag"
AdvSceneSwitcher.condition.date.tuesday="Dienstag"
AdvSceneSwitcher.condition.date.wednesday="Mittwoch"
AdvSceneSwitcher.condition.date.thursday="Donnerstag"
AdvSceneSwitcher.condition.date.friday="Freitag"
AdvSceneSwitcher.condition.date.saturday="Samstag"
AdvSceneSwitcher.condition.date.sunday="Sonntag"
AdvSceneSwitcher.condition.date.state.at="Am"
AdvSceneSwitcher.condition.date.state.after="Nach"
AdvSceneSwitcher.condition.date.state.before="Vor"
@@ -304,13 +309,12 @@ AdvSceneSwitcher.condition.date.ignoreDate="Wenn diese Option nicht aktiviert is
AdvSceneSwitcher.condition.date.ignoreTime="Wenn diese Option nicht aktiviert ist, wird die Zeitkomponente ignoriert"
AdvSceneSwitcher.condition.date.showAdvancedSettings="Erweiterte Einstellungen anzeigen"
AdvSceneSwitcher.condition.date.showSimpleSettings="Einfache Einstellungen anzeigen"
AdvSceneSwitcher.condition.date.layout.simple.day="Am{{dayOfWeek}}"
AdvSceneSwitcher.condition.date.layout.simple.time="{{ignoreWeekTime}}{{weekCondition}}{{weekTime}}"
AdvSceneSwitcher.condition.date.layout.advanced="{{condition}} {{ignoreDate}}{{date}} {{ignoreTime}}{{time}} {{separator}} {{date2}} {{time2}}"
AdvSceneSwitcher.condition.date.layout.repeat="{{repeat}} Wiederholen alle {{duration}} bei Datumsübereinstimmung"
AdvSceneSwitcher.condition.date.layout.pattern="Aktuelles Datum \"{{currentDate}}\" entspricht dem Muster {{pattern}}"
AdvSceneSwitcher.condition.date.layout.nextMatchDate="Nächster Treffer bei: %1"
AdvSceneSwitcher.condition.date.layout.updateOnRepeat="{{updateOnRepeat}} Bei Wiederholung ausgewähltes Datum auf Wiederholungsdatum aktualisieren"
AdvSceneSwitcher.condition.date.entry.simple="Am {{dayOfWeek}} {{weekCondition}} {{ignoreWeekTime}}{{weekTime}}"
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}} {{ignoreDate}}{{date}} {{ignoreTime}}{{time}} {{separator}} {{date2}} {{time2}}"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}} Wiederholen alle {{duration}} bei Datumsübereinstimmung"
AdvSceneSwitcher.condition.date.entry.pattern="Aktuelles Datum \"{{currentDate}}\" entspricht dem Muster {{pattern}}"
AdvSceneSwitcher.condition.date.entry.nextMatchDate="Nächster Treffer bei: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} Bei Wiederholung ausgewähltes Datum auf Wiederholungsdatum aktualisieren"
AdvSceneSwitcher.condition.sceneTransform="Szenenelement transformieren"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Transformation erhalten"
AdvSceneSwitcher.condition.sceneTransform.condition.match="entspricht Transformation"
@@ -408,6 +412,7 @@ 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"
@@ -424,7 +429,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="Verstecken"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="Umschalten"
AdvSceneSwitcher.action.sceneVisibility.type.source="Quelle"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Beliebig"
AdvSceneSwitcher.action.sceneVisibility.layout="Auf{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.sceneVisibility.entry="Auf{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="Filter"
AdvSceneSwitcher.action.filter.type.enable="Aktivieren"
AdvSceneSwitcher.action.filter.type.disable="Deaktivieren"
@@ -500,9 +505,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.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.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.timer="Timer"
AdvSceneSwitcher.action.timer.type.pause="Pausieren"
AdvSceneSwitcher.action.timer.type.continue="Fortsetzen"
@@ -829,6 +834,8 @@ AdvSceneSwitcher.status.inactive="Inaktiv"
AdvSceneSwitcher.running="Plugin läuft"
AdvSceneSwitcher.stopped="Plugin gestoppt"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Dies scheint das erste Mal zu sein, dass der Erweiterte Szenenwechsler gestartet wurde.<br>Bitte schaue ins <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> für eine Liste von Anleitungen und Beispielen.<br>Nicht zögern und Fragen im <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">Thread</span></a> des Plugins im OBS-Forum stellen!!</p></body></html>"
AdvSceneSwitcher.deprecatedTabWarning="Die Entwicklung für dieses Tab wurde gestoppt!\nBitte stattdessen zur Verwendung des Makros-Tab übergehen.\nDieser Hinweis kann im Allgemein-Tab deaktiviert werden."
AdvSceneSwitcher.unit.milliseconds="Millisekunden"

View File

@@ -40,7 +40,6 @@ 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"
@@ -158,15 +157,11 @@ AdvSceneSwitcher.mqttConnectionTab.removeMultipleConnectionsPopup.text="Are you
# Macro Tab
AdvSceneSwitcher.macroTab.title="Macro"
AdvSceneSwitcher.macroTab.macros="Macros"
AdvSceneSwitcher.macroTab.priorityWarning="Note: It is recommended to configure macros to be the highest priority functionality.\nThis setting can be changed on the General tab."
AdvSceneSwitcher.macroTab.help="Macros allow you to execute a string of actions depending on multiple conditions.\n\nClick on the highlighted plus symbol to add a new Macro."
AdvSceneSwitcher.macroTab.macroLastExecutedTooltip="Was last executed %1 seconds ago."
AdvSceneSwitcher.macroTab.macroNotYetExecutedTooltip="Was not yet executed."
AdvSceneSwitcher.macroTab.editConditionHelp="This section allows you to define macro conditions.\n\nSelect an existing or add a new macro on the left.\nThen click the plus button below to add a new condition."
AdvSceneSwitcher.macroTab.editActionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are met.\n\nSelect an existing or add a new macro on the left.\nThen click the plus button below to add a new action."
AdvSceneSwitcher.macroTab.editElseActionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are *not* met.\n\nSelect an existing or add a new macro on the left.\nThen click the plus button below to add a new action."
AdvSceneSwitcher.macroTab.edit="Edit macro"
AdvSceneSwitcher.macroTab.edit.logic="Logic type:"
AdvSceneSwitcher.macroTab.edit.condition="Condition type:"
AdvSceneSwitcher.macroTab.edit.action="Action type:"
@@ -178,12 +173,7 @@ AdvSceneSwitcher.macroTab.run.tooltip="Run all macro actions regardless of condi
AdvSceneSwitcher.macroTab.runElse="Run macro (else)"
AdvSceneSwitcher.macroTab.runFail="Running \"%1\" failed!\nEither one of the actions failed or the macro is running already.\nDo you want to stop it?"
AdvSceneSwitcher.macroTab.runInParallel="Run macro in parallel to other macros"
AdvSceneSwitcher.macroTab.actionTriggerMode.label="Perform actions:"
AdvSceneSwitcher.macroTab.actionTriggerMode.tooltip="Controls when the actions of this macro are performed.\n\n\"Always\" - actions are performed every time the conditions are met.\n\"Macro result changed\" - actions are only performed when the macro transitions between matching and not matching.\n\"Any condition changed\" - actions are only performed when any individual condition changes its result.\n\"Any condition triggered\" - actions are only performed when any individual condition changes from false to true."
AdvSceneSwitcher.macroTab.actionTriggerMode.always="always"
AdvSceneSwitcher.macroTab.actionTriggerMode.onOverallChange="only when result changes"
AdvSceneSwitcher.macroTab.actionTriggerMode.onAnyConditionChange="only when any condition changes"
AdvSceneSwitcher.macroTab.actionTriggerMode.onAnyConditionTriggered="only when any condition becomes true"
AdvSceneSwitcher.macroTab.onChange="Perform actions only on condition change"
AdvSceneSwitcher.macroTab.defaultname="Macro %1"
AdvSceneSwitcher.macroTab.defaultGroupName="Group %1"
AdvSceneSwitcher.macroTab.macroNameExists="The name \"%1\" is already used by a macro."
@@ -369,7 +359,11 @@ 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.layout="{{filePath}}{{conditions}}{{regex}}"
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.media="Media"
AdvSceneSwitcher.condition.media.checkType.state="State matches"
AdvSceneSwitcher.condition.media.checkType.time="Time restriction matches"
@@ -591,6 +585,14 @@ AdvSceneSwitcher.condition.replay.state.started="Replay buffer started"
AdvSceneSwitcher.condition.replay.state.saved="Replay buffer saved"
AdvSceneSwitcher.condition.replay.entry="{{state}}"
AdvSceneSwitcher.condition.date="Date"
AdvSceneSwitcher.condition.date.anyDay="Any day"
AdvSceneSwitcher.condition.date.monday="Monday"
AdvSceneSwitcher.condition.date.tuesday="Tuesday"
AdvSceneSwitcher.condition.date.wednesday="Wednesday"
AdvSceneSwitcher.condition.date.thursday="Thursday"
AdvSceneSwitcher.condition.date.friday="Friday"
AdvSceneSwitcher.condition.date.saturday="Saturday"
AdvSceneSwitcher.condition.date.sunday="Sunday"
AdvSceneSwitcher.condition.date.state.at="At"
AdvSceneSwitcher.condition.date.state.after="After"
AdvSceneSwitcher.condition.date.state.before="Before"
@@ -601,13 +603,12 @@ AdvSceneSwitcher.condition.date.ignoreDate="If unchecked the date component will
AdvSceneSwitcher.condition.date.ignoreTime="If unchecked the time component will be ignored"
AdvSceneSwitcher.condition.date.showAdvancedSettings="Show advanced settings"
AdvSceneSwitcher.condition.date.showSimpleSettings="Show simple settings"
AdvSceneSwitcher.condition.date.layout.simple.day="On{{dayOfWeek}}"
AdvSceneSwitcher.condition.date.layout.simple.time="{{ignoreWeekTime}}{{weekCondition}}{{weekTime}}"
AdvSceneSwitcher.condition.date.layout.advanced="{{condition}}{{ignoreDate}}{{date}}{{ignoreTime}}{{time}}{{separator}}{{date2}}{{time2}}"
AdvSceneSwitcher.condition.date.layout.repeat="{{repeat}}Repeat every{{duration}}on date match"
AdvSceneSwitcher.condition.date.layout.pattern="Current date \"{{currentDate}}\" matches pattern{{pattern}}"
AdvSceneSwitcher.condition.date.layout.nextMatchDate="Next match at: %1"
AdvSceneSwitcher.condition.date.layout.updateOnRepeat="{{updateOnRepeat}}On repeat update selected date to repeat date"
AdvSceneSwitcher.condition.date.entry.simple="On{{dayOfWeek}}{{weekCondition}}{{ignoreWeekTime}}{{weekTime}}"
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}}{{ignoreDate}}{{date}}{{ignoreTime}}{{time}}{{separator}}{{date2}}{{time2}}"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}}Repeat every{{duration}}on date match"
AdvSceneSwitcher.condition.date.entry.pattern="Current date \"{{currentDate}}\" matches pattern{{pattern}}"
AdvSceneSwitcher.condition.date.entry.nextMatchDate="Next match at: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}}On repeat update selected date to repeat date"
AdvSceneSwitcher.condition.sceneTransform="Scene item transform"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
AdvSceneSwitcher.condition.sceneTransform.getCurrentValue="Get current value"
@@ -673,34 +674,6 @@ 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"
@@ -904,15 +877,6 @@ 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"
@@ -921,10 +885,9 @@ 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}}{{chapterName}}{{pauseHint}}{{splitHint}}"
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{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!"
@@ -951,9 +914,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="Hide"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="Toggle"
AdvSceneSwitcher.action.sceneVisibility.type.source="Source"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Any"
AdvSceneSwitcher.action.sceneVisibility.layout="On{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.sceneVisibility.layout.transition="{{updateTransition}}Set transition to{{transitions}}"
AdvSceneSwitcher.action.sceneVisibility.layout.duration="{{updateDuration}}Set transition duration to{{duration}}seconds"
AdvSceneSwitcher.action.sceneVisibility.entry="On{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="Filter"
AdvSceneSwitcher.action.filter.type.enable="Enable"
AdvSceneSwitcher.action.filter.type.disable="Disable"
@@ -984,8 +945,6 @@ 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\""
@@ -1008,49 +967,6 @@ 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"
@@ -1085,7 +1001,6 @@ 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.nestedMacro="Nested macro"
AdvSceneSwitcher.action.macro.actionSelectionType.index="at index"
AdvSceneSwitcher.action.macro.actionSelectionType.label="with label"
@@ -1168,16 +1083,13 @@ 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="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.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.timer="Timer"
AdvSceneSwitcher.action.timer.type.pause="Pause"
AdvSceneSwitcher.action.timer.type.continue="Continue"
@@ -1278,8 +1190,6 @@ AdvSceneSwitcher.action.variable.type.swapValues="Swap variable values"
AdvSceneSwitcher.action.variable.type.trim="Trim whitespace"
AdvSceneSwitcher.action.variable.type.changeCase="Change case"
AdvSceneSwitcher.action.variable.type.randomNumber="Generate random number"
AdvSceneSwitcher.action.variable.type.randomListValue="Select random value"
AdvSceneSwitcher.action.variable.type.allowRepeat="Allow repeat values"
AdvSceneSwitcher.action.variable.case.type.lowerCase="lowercase"
AdvSceneSwitcher.action.variable.case.type.upperCase="UPPERCASE"
AdvSceneSwitcher.action.variable.case.type.capitalized="Capitalized"
@@ -1337,52 +1247,23 @@ 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}}{{nonModDelayDuration}}{{channel}}{{pointsReward}}"
AdvSceneSwitcher.action.twitch.layout.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{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.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.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.title.title="Enter title"
AdvSceneSwitcher.action.twitch.marker.description="Describe marker"
AdvSceneSwitcher.action.twitch.clip.hasDelay="Add a slight delay before capturing the clip"
@@ -1403,8 +1284,6 @@ 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."
@@ -1510,7 +1389,6 @@ 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"
@@ -1780,7 +1658,6 @@ 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!"
@@ -2298,18 +2175,6 @@ 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"
@@ -2330,8 +2195,6 @@ AdvSceneSwitcher.tempVar.recording.durationSeconds.description="Recording durati
AdvSceneSwitcher.tempVar.recording.lastSavePath="Last save path"
AdvSceneSwitcher.tempVar.recording.lastSavePath.description="The file path of the last saved recording."
AdvSceneSwitcher.tempVar.video.similarity="Similarity Rating"
AdvSceneSwitcher.tempVar.video.similarity.description="Values range from 0 to 1, where 0 means dissimilar and 1 means highly similar."
AdvSceneSwitcher.tempVar.video.patternCount="Pattern count"
AdvSceneSwitcher.tempVar.video.patternCount.description="The number of times the given pattern has been found in a given video input frame."
AdvSceneSwitcher.tempVar.video.objectCount="Object count"
@@ -2342,8 +2205,6 @@ 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"
@@ -2473,14 +2334,9 @@ 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"
@@ -2490,14 +2346,6 @@ 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"
@@ -2587,6 +2435,8 @@ AdvSceneSwitcher.status.inactive="Inactive"
AdvSceneSwitcher.running="Plugin running"
AdvSceneSwitcher.stopped="Plugin stopped"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>This seems to be the first time the Advanced Scene Switcher was started.<br>Please have a look at the <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> for a list of guides and examples.<br>Do not hesitate to ask questions in the plugin's <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">thread</span></a> on the OBS forums!</p></body></html>"
AdvSceneSwitcher.deprecatedTabWarning="Development for this tab stopped!\nPlease consider transitioning to using Macros instead.\nThis hint can be disabled on the General tab."
AdvSceneSwitcher.unit.milliseconds="milliseconds"
@@ -2616,47 +2466,6 @@ AdvSceneSwitcher.clearBufferOnMatch="Clear message buffer when matching message
AdvSceneSwitcher.script.settings="Settings"
AdvSceneSwitcher.script.timeout="Script timeout:{{timeout}}"
AdvSceneSwitcher.day.monday="Monday"
AdvSceneSwitcher.day.tuesday="Tuesday"
AdvSceneSwitcher.day.wednesday="Wednesday"
AdvSceneSwitcher.day.thursday="Thursday"
AdvSceneSwitcher.day.friday="Friday"
AdvSceneSwitcher.day.saturday="Saturday"
AdvSceneSwitcher.day.sunday="Sunday"
AdvSceneSwitcher.day.none="No day"
AdvSceneSwitcher.day.any="Any day"
# First run wizard
FirstRunWizard.windowTitle="Advanced Scene Switcher - Setup Wizard"
FirstRunWizard.welcome.title="Welcome to Advanced Scene Switcher"
FirstRunWizard.welcome.subtitle="Let's create your first automation in a few easy steps."
FirstRunWizard.welcome.body="<p>Advanced Scene Switcher lets you build <b>Macros</b> - rules of the form:</p><blockquote><i>When [condition] -> perform [action]</i></blockquote><p>This wizard creates a macro that <b>switches to a chosen OBS scene whenever a specific window comes into focus</b>.</p><p>You can skip at any time. Re-open this wizard later from the <b>General</b> tab inside the Advanced Scene Switcher dialog.</p>"
FirstRunWizard.scene.title="Choose a Target Scene"
FirstRunWizard.scene.subtitle="Which OBS scene should become active when your chosen window comes into focus?"
FirstRunWizard.scene.label="Switch to scene:"
FirstRunWizard.window.title="Choose a Trigger Window"
FirstRunWizard.window.subtitle="Enter the title of the window that should trigger the scene switch. Partial matches are fine - \"Firefox\" will match any Firefox window."
FirstRunWizard.window.label="Window title contains:"
FirstRunWizard.window.placeholder="e.g. \"Firefox\", \"Visual Studio Code\""
FirstRunWizard.window.autoDetect="Auto-detect focused window"
FirstRunWizard.window.autoDetectTooltip="Click, then switch to the target window. The title will be captured automatically after 3 seconds."
FirstRunWizard.window.autoDetectCountdown="Detecting in %1 s - focus your window now..."
FirstRunWizard.window.hint="<small>Tip: a short partial title is more robust than the full title, which often changes when you switch tabs.</small>"
FirstRunWizard.review.title="Review Your Macro"
FirstRunWizard.review.subtitle="Click Back to make changes, or Finish to create the macro."
FirstRunWizard.review.summary="<b>Macro name:</b> Window -> %1<br><br><b>Condition:</b> Focused window title contains <i>\"%2\"</i> (case-insensitive)<br><br><b>Action:</b> Switch to scene <i>\"%1\"</i>"
FirstRunWizard.review.errorTitle="Macro Creation Failed"
FirstRunWizard.review.errorBody="The macro could not be created automatically.\n\nYou can create it manually on the Macros tab:\n Condition: Window -> contains \"%1\"\n Action: Switch scene -> \"%2\""
FirstRunWizard.done.title="You're all set!"
FirstRunWizard.done.subtitle="Your first macro has been created and is now active."
FirstRunWizard.done.body="<p>You can view and edit it on the <b>Macros</b> tab of the Advanced Scene Switcher dialog.</p><p>To learn more:</p><ul><li><a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\">Plugin wiki</a></li><li><a href=\"https://obsproject.com/forum/resources/automatic-scene-switching.395/\">OBS forum thread</a></li></ul>"
FirstRunWizard.openButton="Open Setup Wizard..."
# This secion is copied from the OBS locale files
# OBS commonly shared locale

View File

@@ -72,6 +72,7 @@ AdvSceneSwitcher.macroTab.add="Agregar nueva macro"
AdvSceneSwitcher.macroTab.name="Nombre:"
AdvSceneSwitcher.macroTab.run="Ejecutar macro"
AdvSceneSwitcher.macroTab.runInParallel="Ejecutar macro en paralelo a otras macros"
AdvSceneSwitcher.macroTab.onChange="Realizar acciones solo en el cambio de condición"
AdvSceneSwitcher.macroTab.defaultname="Macro %1"
AdvSceneSwitcher.macroTab.copy="Crear copia"
AdvSceneSwitcher.macroTab.expandAll="Expandir todo"
@@ -117,7 +118,9 @@ 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.layout="Contenido de{{filePath}}{{conditions}}{{regex}}"
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.media="Medios"
AdvSceneSwitcher.condition.media.anyOnScene="Cualquier fuente multimedia activada"
AdvSceneSwitcher.condition.media.allOnScene="Todas las fuentes de medios activadas"
@@ -238,14 +241,14 @@ AdvSceneSwitcher.condition.replay.state.started="Búfer de reproducción iniciad
AdvSceneSwitcher.condition.replay.state.saved="Búfer de reproducción guardado"
AdvSceneSwitcher.condition.replay.entry="{{state}}"
AdvSceneSwitcher.condition.date="Fecha"
AdvSceneSwitcher.day.any="Cualquier día"
AdvSceneSwitcher.day.monday="Lunes"
AdvSceneSwitcher.day.tuesday="Martes"
AdvSceneSwitcher.day.wednesday="Miércoles"
AdvSceneSwitcher.day.thursday="Jueves"
AdvSceneSwitcher.day.friday="Viernes"
AdvSceneSwitcher.day.saturday="Sábado"
AdvSceneSwitcher.day.sunday="Domingo"
AdvSceneSwitcher.condition.date.anyDay="Cualquier día"
AdvSceneSwitcher.condition.date.monday="Lunes"
AdvSceneSwitcher.condition.date.tuesday="Martes"
AdvSceneSwitcher.condition.date.wednesday="Miércoles"
AdvSceneSwitcher.condition.date.thursday="Jueves"
AdvSceneSwitcher.condition.date.friday="Viernes"
AdvSceneSwitcher.condition.date.saturday="Sábado"
AdvSceneSwitcher.condition.date.sunday="Domingo"
AdvSceneSwitcher.condition.date.state.at="A las"
AdvSceneSwitcher.condition.date.state.after="Después"
AdvSceneSwitcher.condition.date.state.before="Antes"
@@ -255,12 +258,11 @@ AdvSceneSwitcher.condition.date.ignoreDate="Si no se marca, se ignorará el comp
AdvSceneSwitcher.condition.date.ignoreTime="Si no se marca, se ignorará el componente de tiempo"
AdvSceneSwitcher.condition.date.showAdvancedSettings="Mostrar configuración avanzada"
AdvSceneSwitcher.condition.date.showSimpleSettings="Mostrar configuración simple"
AdvSceneSwitcher.condition.date.layout.simple.day="El{{dayOfWeek}}"
AdvSceneSwitcher.condition.date.layout.simple.time="{{ignoreWeekTime}}{{weekCondition}}{{weekTime}}"
AdvSceneSwitcher.condition.date.layout.advanced="{{condition}} {{ignoreDate}}{{date}} {{ignoreTime}}{{time}} {{separator}} {{date2}} {{time2}}"
AdvSceneSwitcher.condition.date.layout.repeat="{{repeat}} Repetir cada {{duration}} en la coincidencia de fechas"
AdvSceneSwitcher.condition.date.layout.nextMatchDate="Próxima coincidencia en: %1"
AdvSceneSwitcher.condition.date.layout.updateOnRepeat="{{updateOnRepeat}} Al repetir actualizar la fecha seleccionada para repetir la fecha"
AdvSceneSwitcher.condition.date.entry.simple="El {{dayOfWeek}} {{weekCondition}} {{ignoreWeekTime}}{{weekTime}}"
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}} {{ignoreDate}}{{date}} {{ignoreTime}}{{time}} {{separator}} {{date2}} {{time2}}"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}} Repetir cada {{duration}} en la coincidencia de fechas"
AdvSceneSwitcher.condition.date.entry.nextMatchDate="Próxima coincidencia en: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} Al repetir actualizar la fecha seleccionada para repetir la fecha"
AdvSceneSwitcher.condition.sceneTransform="Transformar elemento de escena"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Obtener transformación"
AdvSceneSwitcher.condition.sceneTransform.condition.match="coincide con la transformación"
@@ -332,6 +334,7 @@ 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"
@@ -347,7 +350,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.show="Mostrar"
AdvSceneSwitcher.action.sceneVisibility.type.hide="Ocultar"
AdvSceneSwitcher.action.sceneVisibility.type.source="Fuente"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Cualquiera"
AdvSceneSwitcher.action.sceneVisibility.layout="En{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.sceneVisibility.entry="En{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="Filtro"
AdvSceneSwitcher.action.filter.type.enable="Habilitar"
AdvSceneSwitcher.action.filter.type.disable="Deshabilitar"
@@ -420,9 +423,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.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.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.timer="Temporizador"
AdvSceneSwitcher.action.timer.type.pause="Pausa"
AdvSceneSwitcher.action.timer.type.continue="Continuar"
@@ -677,6 +680,8 @@ AdvSceneSwitcher.status.inactive="Inactivo"
AdvSceneSwitcher.running="Iniciar complemento"
AdvSceneSwitcher.stopped="Complemento de detención"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Esta parece ser la primera vez que se inicia el conmutador de escena avanzado.<br>Por favor, eche un vistazo a <a href=\"https:/ /github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> para obtener una lista de guías y ejemplos.<br>No dude en hacer preguntas en el complemento <a href=\"https://obsproject.com /forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">hilo</span></a> en los foros de OBS!</p></body></html>"
AdvSceneSwitcher.deprecatedTabWarning="¡Se detuvo el desarrollo de esta pestaña!\nConsidere cambiar a macros en su lugar.\nEsta sugerencia se puede desactivar en la pestaña General".
AdvSceneSwitcher.unit.milliseconds="milisegundos"

View File

@@ -78,6 +78,7 @@ AdvSceneSwitcher.macroTab.add="Ajouter une nouvelle macro"
AdvSceneSwitcher.macroTab.name="Nom :"
AdvSceneSwitcher.macroTab.run="Exécuter la macro"
AdvSceneSwitcher.macroTab.runInParallel="Exécuter la macro en parallèle avec d'autres macros"
AdvSceneSwitcher.macroTab.onChange="Exécuter des actions uniquement en cas de changement de condition"
AdvSceneSwitcher.macroTab.defaultname="Macro %1"
AdvSceneSwitcher.macroTab.defaultGroupName="Groupe %1"
AdvSceneSwitcher.macroTab.removeSingleMacroPopup.text="Êtes-vous sûr de vouloir supprimer \"%1\" ?"
@@ -187,6 +188,8 @@ 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"
@@ -353,14 +356,14 @@ AdvSceneSwitcher.condition.replay.state.stopped="Tampon de répétition arrêté
AdvSceneSwitcher.condition.replay.state.started="Tampon de répétition démarré"
AdvSceneSwitcher.condition.replay.state.saved="Tampon de répétition enregistré"
AdvSceneSwitcher.condition.date="Date"
AdvSceneSwitcher.day.any="N'importe quel jour"
AdvSceneSwitcher.day.monday="Lundi"
AdvSceneSwitcher.day.tuesday="Mardi"
AdvSceneSwitcher.day.wednesday="Mercredi"
AdvSceneSwitcher.day.thursday="Jeudi"
AdvSceneSwitcher.day.friday="Vendredi"
AdvSceneSwitcher.day.saturday="Samedi"
AdvSceneSwitcher.day.sunday="Dimanche"
AdvSceneSwitcher.condition.date.anyDay="N'importe quel jour"
AdvSceneSwitcher.condition.date.monday="Lundi"
AdvSceneSwitcher.condition.date.tuesday="Mardi"
AdvSceneSwitcher.condition.date.wednesday="Mercredi"
AdvSceneSwitcher.condition.date.thursday="Jeudi"
AdvSceneSwitcher.condition.date.friday="Vendredi"
AdvSceneSwitcher.condition.date.saturday="Samedi"
AdvSceneSwitcher.condition.date.sunday="Dimanche"
AdvSceneSwitcher.condition.date.state.at="À"
AdvSceneSwitcher.condition.date.state.after="Après"
AdvSceneSwitcher.condition.date.state.before="Avant"
@@ -371,12 +374,11 @@ AdvSceneSwitcher.condition.date.ignoreDate="Si non cochée, la composante date s
AdvSceneSwitcher.condition.date.ignoreTime="Si non cochée, la composante heure sera ignorée"
AdvSceneSwitcher.condition.date.showAdvancedSettings="Afficher les paramètres avancés"
AdvSceneSwitcher.condition.date.showSimpleSettings="Afficher les paramètres simples"
AdvSceneSwitcher.condition.date.layout.simple.day="Le{{dayOfWeek}}"
AdvSceneSwitcher.condition.date.layout.simple.time="{{ignoreWeekTime}}{{weekCondition}}{{weekTime}}"
AdvSceneSwitcher.condition.date.layout.repeat="{{repeat}}Répéter toutes les{{duration}}lorsque la date correspond"
AdvSceneSwitcher.condition.date.layout.pattern="La date actuelle \"{{currentDate}}\" correspond au motif{{pattern}}"
AdvSceneSwitcher.condition.date.layout.nextMatchDate="Prochaine correspondance à : %1"
AdvSceneSwitcher.condition.date.layout.updateOnRepeat="{{updateOnRepeat}}À la répétition, mettre à jour la date sélectionnée pour la date de répétition"
AdvSceneSwitcher.condition.date.entry.simple="Le{{dayOfWeek}}{{weekCondition}}{{ignoreWeekTime}}{{weekTime}}"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}}Répéter toutes les{{duration}}lorsque la date correspond"
AdvSceneSwitcher.condition.date.entry.pattern="La date actuelle \"{{currentDate}}\" correspond au motif{{pattern}}"
AdvSceneSwitcher.condition.date.entry.nextMatchDate="Prochaine correspondance à : %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}}À la répétition, mettre à jour la date sélectionnée pour la date de répétition"
AdvSceneSwitcher.condition.sceneTransform="Transformation de l'élément de la scène"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Obtenir la transformation"
AdvSceneSwitcher.condition.sceneTransform.condition.match="correspond à la transformation"
@@ -508,7 +510,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="Masquer"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="Basculer"
AdvSceneSwitcher.action.sceneVisibility.type.source="Source"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="N'importe"
AdvSceneSwitcher.action.sceneVisibility.layout="Sur{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.sceneVisibility.entry="Sur{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="Filtre"
AdvSceneSwitcher.action.filter.type.enable="Activer"
AdvSceneSwitcher.action.filter.type.disable="Désactiver"
@@ -620,9 +622,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.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.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.timer="Minuterie"
AdvSceneSwitcher.action.timer.type.pause="Mettre en pause"
AdvSceneSwitcher.action.timer.type.continue="Continuer"
@@ -1120,6 +1122,8 @@ AdvSceneSwitcher.status.inactive="Inactif"
AdvSceneSwitcher.running="Plugin en cours d'exécution"
AdvSceneSwitcher.stopped="Plugin arrêté"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Il semble que ce soit la première fois que l'Advanced Scene Switcher est démarré.<br>Veuillez consulter le <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> pour obtenir une liste de guides et d'exemples.<br>N'hésitez pas à poser des questions dans le <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">fil de discussion</span></a> du plugin sur les forums OBS !</p></body></html>"
AdvSceneSwitcher.deprecatedTabWarning="Le développement de cet onglet est terminé !\nVeuillez envisager de passer à l'utilisation des Macros à la place.\nCette astuce peut être désactivée dans l'onglet Général."
AdvSceneSwitcher.unit.milliseconds="millisecondes"

View File

@@ -164,6 +164,7 @@ AdvSceneSwitcher.macroTab.run.tooltip="条件に関係なくすべてのマク
AdvSceneSwitcher.macroTab.runElse="マクロ実行else"
AdvSceneSwitcher.macroTab.runFail="\"%1\" の実行に失敗しました!\nいずれかのアクションが失敗したか、マクロがすでに実行されています。\n停止しますか"
AdvSceneSwitcher.macroTab.runInParallel="他のマクロと並行してマクロを実行する"
AdvSceneSwitcher.macroTab.onChange="条件変更時のみアクションを実行"
AdvSceneSwitcher.macroTab.defaultname="マクロ %1"
AdvSceneSwitcher.macroTab.defaultGroupName="グループ %1"
AdvSceneSwitcher.macroTab.macroNameExists="名前 \"%1\" は既にマクロで使用されています。"
@@ -331,6 +332,14 @@ 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="時間が一致"
@@ -546,14 +555,14 @@ AdvSceneSwitcher.condition.replay.state.started="リプレイバッファを開
AdvSceneSwitcher.condition.replay.state.saved="リプレイバッファを保存"
; AdvSceneSwitcher.condition.replay.entry="{{state}}"
AdvSceneSwitcher.condition.date="日付条件"
AdvSceneSwitcher.day.any="いつでも"
AdvSceneSwitcher.day.monday="月曜日"
AdvSceneSwitcher.day.tuesday="火曜日"
AdvSceneSwitcher.day.wednesday="水曜日"
AdvSceneSwitcher.day.thursday="木曜日"
AdvSceneSwitcher.day.friday="金曜日"
AdvSceneSwitcher.day.saturday="土曜日"
AdvSceneSwitcher.day.sunday="日曜日"
AdvSceneSwitcher.condition.date.anyDay="いつでも"
AdvSceneSwitcher.condition.date.monday="月曜日"
AdvSceneSwitcher.condition.date.tuesday="火曜日"
AdvSceneSwitcher.condition.date.wednesday="水曜日"
AdvSceneSwitcher.condition.date.thursday="木曜日"
AdvSceneSwitcher.condition.date.friday="金曜日"
AdvSceneSwitcher.condition.date.saturday="土曜日"
AdvSceneSwitcher.condition.date.sunday="日曜日"
; AdvSceneSwitcher.condition.date.state.at="At"
AdvSceneSwitcher.condition.date.state.after="アフター"
AdvSceneSwitcher.condition.date.state.before="前"
@@ -564,9 +573,12 @@ AdvSceneSwitcher.condition.date.ignoreDate="チェックを外すと日付部分
AdvSceneSwitcher.condition.date.ignoreTime="チェックを外すと時間要素は無視されます"
AdvSceneSwitcher.condition.date.showAdvancedSettings="詳細設定を表示"
AdvSceneSwitcher.condition.date.showSimpleSettings="簡単設定を表示"
AdvSceneSwitcher.condition.date.layout.repeat="{{repeat}}日付の一致ごとに{{duration}}繰り返します"
AdvSceneSwitcher.condition.date.layout.pattern="現在の日付「{{currentDate}}」はパターン{{pattern}}と一致します"
AdvSceneSwitcher.condition.date.layout.updateOnRepeat="{{updateOnRepeat}}繰り返しの場合、選択した日付を繰り返しの日付に更新します"
; AdvSceneSwitcher.condition.date.entry.simple="On{{dayOfWeek}}{{weekCondition}}{{ignoreWeekTime}}{{weekTime}}"
; AdvSceneSwitcher.condition.date.entry.advanced="{{condition}}{{ignoreDate}}{{date}}{{ignoreTime}}{{time}}{{separator}}{{date2}}{{time2}}"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}}日付の一致ごとに{{duration}}繰り返します"
AdvSceneSwitcher.condition.date.entry.pattern="現在の日付「{{currentDate}}」はパターン{{pattern}}と一致します"
; AdvSceneSwitcher.condition.date.entry.nextMatchDate="Next match at: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}}繰り返しの場合、選択した日付を繰り返しの日付に更新します"
; AdvSceneSwitcher.condition.sceneTransform="Scene item transform"
; AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
AdvSceneSwitcher.condition.sceneTransform.getCurrentValue="現在の値を取得"
@@ -858,6 +870,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="非表示"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="切り替え"
; AdvSceneSwitcher.action.sceneVisibility.type.source="Source"
; AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Any"
; AdvSceneSwitcher.action.sceneVisibility.entry="On{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="フィルタ"
AdvSceneSwitcher.action.filter.type.enable="有効にする"
AdvSceneSwitcher.action.filter.type.disable="無効化"
@@ -1012,9 +1025,9 @@ AdvSceneSwitcher.action.transition="トランジション"
; AdvSceneSwitcher.action.transition.type.sceneOverride="シーントランジションオーバーライド"
; AdvSceneSwitcher.action.transition.type.sourceShow="ソース表示トランジション"
; AdvSceneSwitcher.action.transition.type.sourceHide="ソース非表示トランジション"
; AdvSceneSwitcher.action.transition.layout.type="{{type}}{{scenes}}{{sources}} の変更"
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}トランジションタイプを{{transitions}}に設定します"
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}移行時間を {{duration}} 秒に設定します"
; AdvSceneSwitcher.action.transition.entry.line1="{{type}}{{scenes}}{{sources}} の変更"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}トランジションタイプを{{transitions}}に設定します"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}移行時間を {{duration}} 秒に設定します"
AdvSceneSwitcher.action.timer="タイマー"
AdvSceneSwitcher.action.timer.type.pause="一時停止"
AdvSceneSwitcher.action.timer.type.continue="続行"
@@ -1182,15 +1195,8 @@ 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.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.layout.user.getInfo="{{userInfoQueryType}}{{userLogin}}{{userId}}でアカウント{{account}}{{actions}}を使用"
AdvSceneSwitcher.action.twitch.layout.reward.getInfo="チャンネル{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}でアカウント{{account}}{{actions}}を使用"
AdvSceneSwitcher.action.twitch.title.title="タイトルを入力"
AdvSceneSwitcher.action.twitch.marker.description="マーカーの説明"
AdvSceneSwitcher.action.twitch.clip.hasDelay="クリップをキャプチャする前にわずかな遅延を追加します"
@@ -2250,6 +2256,8 @@ AdvSceneSwitcher.status.inactive="停止中"
AdvSceneSwitcher.running="プラグイン実行中"
AdvSceneSwitcher.stopped="プラグイン停止しました"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>高機能シーンスイッチャーが初めて起動したようです。<br>ガイドと使用例については <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> をご覧ください。<br>質問は <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">OBSフォーラムのプラグインのスレッド</span></a> で遠慮なくどうぞ!</p></body></html>"
AdvSceneSwitcher.deprecatedTabWarning="このタブの開発は停止しました!\n代わりにマクロの使用に移行することを検討してください。\nこのヒントは [全般] タブで無効にすることができます。"
AdvSceneSwitcher.unit.milliseconds="ミリ秒"

View File

@@ -147,6 +147,7 @@ AdvSceneSwitcher.macroTab.run.tooltip="Execute todas as ações da macro indepen
AdvSceneSwitcher.macroTab.runElse="Executar macro (alternativa)"
AdvSceneSwitcher.macroTab.runFail="Falha ao executar \"%1\"!\nUma das ações falhou ou a macro já está em execução.\nDeseja interrompê-la?"
AdvSceneSwitcher.macroTab.runInParallel="Executar macro em paralelo com outras macros"
AdvSceneSwitcher.macroTab.onChange="Executar ações apenas quando houver mudança na condição"
AdvSceneSwitcher.macroTab.defaultname="Macro %1"
AdvSceneSwitcher.macroTab.defaultGroupName="Grupo %1"
AdvSceneSwitcher.macroTab.macroNameExists="O nome \"%1\" já está em uso por uma macro."
@@ -295,6 +296,11 @@ 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"
@@ -496,14 +502,14 @@ AdvSceneSwitcher.condition.replay.state.started="Buffer de replay iniciado"
AdvSceneSwitcher.condition.replay.state.saved="Buffer de replay salvo"
AdvSceneSwitcher.condition.replay.entry="{{state}}"
AdvSceneSwitcher.condition.date="Data"
AdvSceneSwitcher.day.any="Qualquer dia"
AdvSceneSwitcher.day.monday="Segunda-feira"
AdvSceneSwitcher.day.tuesday="Terça-feira"
AdvSceneSwitcher.day.wednesday="Quarta-feira"
AdvSceneSwitcher.day.thursday="Quinta-feira"
AdvSceneSwitcher.day.friday="Sexta-feira"
AdvSceneSwitcher.day.saturday="Sábado"
AdvSceneSwitcher.day.sunday="Domingo"
AdvSceneSwitcher.condition.date.anyDay="Qualquer dia"
AdvSceneSwitcher.condition.date.monday="Segunda-feira"
AdvSceneSwitcher.condition.date.tuesday="Terça-feira"
AdvSceneSwitcher.condition.date.wednesday="Quarta-feira"
AdvSceneSwitcher.condition.date.thursday="Quinta-feira"
AdvSceneSwitcher.condition.date.friday="Sexta-feira"
AdvSceneSwitcher.condition.date.saturday="Sábado"
AdvSceneSwitcher.condition.date.sunday="Domingo"
AdvSceneSwitcher.condition.date.state.at="Às"
AdvSceneSwitcher.condition.date.state.after="Após"
AdvSceneSwitcher.condition.date.state.before="Antes"
@@ -514,13 +520,12 @@ AdvSceneSwitcher.condition.date.ignoreDate="Se desmarcado, o componente de data
AdvSceneSwitcher.condition.date.ignoreTime="Se desmarcado, o componente de tempo será ignorado"
AdvSceneSwitcher.condition.date.showAdvancedSettings="Mostrar configurações avançadas"
AdvSceneSwitcher.condition.date.showSimpleSettings="Mostrar configurações simples"
AdvSceneSwitcher.condition.date.layout.simple.day="Em{{dayOfWeek}}"
AdvSceneSwitcher.condition.date.layout.simple.time="{{ignoreWeekTime}}{{weekCondition}}{{weekTime}}"
AdvSceneSwitcher.condition.date.layout.advanced="{{condition}}{{ignoreDate}}{{date}}{{ignoreTime}}{{time}}{{separator}}{{date2}}{{time2}}"
AdvSceneSwitcher.condition.date.layout.repeat="{{repeat}}Repetir a cada{{duration}}em correspondência de data"
AdvSceneSwitcher.condition.date.layout.pattern="Data atual \"{{currentDate}}\" corresponde ao padrão{{pattern}}"
AdvSceneSwitcher.condition.date.layout.nextMatchDate="Próxima correspondência em: %1"
AdvSceneSwitcher.condition.date.layout.updateOnRepeat="{{updateOnRepeat}}Ao repetir, atualize a data selecionada para a data de repetição"
AdvSceneSwitcher.condition.date.entry.simple="Em{{dayOfWeek}}{{weekCondition}}{{ignoreWeekTime}}{{weekTime}}"
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}}{{ignoreDate}}{{date}}{{ignoreTime}}{{time}}{{separator}}{{date2}}{{time2}}"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}}Repetir a cada{{duration}}em correspondência de data"
AdvSceneSwitcher.condition.date.entry.pattern="Data atual \"{{currentDate}}\" corresponde ao padrão{{pattern}}"
AdvSceneSwitcher.condition.date.entry.nextMatchDate="Próxima correspondência em: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}}Ao repetir, atualize a data selecionada para a data de repetição"
AdvSceneSwitcher.condition.sceneTransform="Transformação do item de cena"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Obter transformação"
AdvSceneSwitcher.condition.sceneTransform.getCurrentValue="Obter valor atual"
@@ -749,6 +754,7 @@ 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!"
@@ -775,7 +781,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="Ocultar"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="Alternar"
AdvSceneSwitcher.action.sceneVisibility.type.source="Fonte"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Qualquer"
AdvSceneSwitcher.action.sceneVisibility.layout="Em{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.sceneVisibility.entry="Em{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="Filtro"
AdvSceneSwitcher.action.filter.type.enable="Habilitar"
AdvSceneSwitcher.action.filter.type.disable="Desabilitar"
@@ -927,9 +933,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.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.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.timer="Temporizador"
AdvSceneSwitcher.action.timer.type.pause="Pausar"
AdvSceneSwitcher.action.timer.type.continue="Continuar"
@@ -1059,7 +1065,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}}{{nonModDelayDuration}}"
AdvSceneSwitcher.action.twitch.layout.default="Em{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}"
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"
@@ -1865,6 +1871,8 @@ AdvSceneSwitcher.status.inactive="Inativo"
AdvSceneSwitcher.running="Plugin em execução"
AdvSceneSwitcher.stopped="Plugin parado"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Parece que esta é a primeira vez que o Advanced Scene Switcher está sendo iniciado.<br>Por favor, consulte o <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> para uma lista de guias e exemplos.<br>Não hesite em fazer perguntas no <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">tópico</span></a> do plugin nos fóruns do OBS!</p></body></html>"
AdvSceneSwitcher.deprecatedTabWarning="Desenvolvimento para esta aba interrompido!\nPor favor, considere a transição para o uso de Macros em vez disso.\nEsta dica pode ser desativada na aba Geral."
AdvSceneSwitcher.unit.milliseconds="milissegundos"

View File

@@ -85,6 +85,8 @@ 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="точно соответствует"
@@ -128,6 +130,7 @@ 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="Начать воспроизведение буфера"

View File

@@ -72,6 +72,7 @@ AdvSceneSwitcher.macroTab.add="Yeni Makro ekle"
AdvSceneSwitcher.macroTab.name="İsim:"
AdvSceneSwitcher.macroTab.run="Makro Çalıştırma"
AdvSceneSwitcher.macroTab.runInParallel="Makroyu diğer makrolara paralel olarak çalıştırın"
AdvSceneSwitcher.macroTab.onChange="Eylemleri yalnızca koşul değişikliğinde gerçekleştirin"
AdvSceneSwitcher.macroTab.defaultname="Makro %1"
AdvSceneSwitcher.macroTab.copy="Kopya oluştur"
AdvSceneSwitcher.macroTab.expandAll="Hepsini Genişlet"
@@ -107,7 +108,9 @@ 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.layout="İçerik{{filePath}}{{conditions}}{{regex}}"
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.media="Medya"
AdvSceneSwitcher.condition.media.anyOnScene="Herhangi bir medya kaynağı"
AdvSceneSwitcher.condition.media.allOnScene="Tüm medya kaynakları "
@@ -263,6 +266,7 @@ 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"
@@ -277,7 +281,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.show="Göster"
AdvSceneSwitcher.action.sceneVisibility.type.hide="Gizle"
AdvSceneSwitcher.action.sceneVisibility.type.source="Kayıt"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Herhangi"
AdvSceneSwitcher.action.sceneVisibility.layout="Açık{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.sceneVisibility.entry="Açık{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="Filtrele"
AdvSceneSwitcher.action.filter.type.enable="Etkin"
AdvSceneSwitcher.action.filter.type.disable="Etkisiz"
@@ -338,8 +342,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.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.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.timer="Zamanlayıcı"
AdvSceneSwitcher.action.timer.type.pause="Duraklat"
AdvSceneSwitcher.action.timer.type.continue="Devam et"
@@ -583,6 +587,8 @@ AdvSceneSwitcher.status.inactive="İnaktif"
AdvSceneSwitcher.running="Eklenti çalışıyor"
AdvSceneSwitcher.stopped="Eklenti durdu"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Gelişmiş Sahne Değiştirici ilk kez başlatılıyor gibi görünüyor.<br>Lütfen <a href=\"https://github.com/ adresine bir göz atın. WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> için kılavuzlar ve örnekler listesi.<br>Yapmayın. eklentinin <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:# sayfasında soru sormaktan çekinmeyin OBS forumlarında 268bd2;\">konu</span></a>!</p></body></html>"
AdvSceneSwitcher.unit.milliseconds="millisaniye"
AdvSceneSwitcher.unit.seconds="saniye"
AdvSceneSwitcher.unit.minutes="dakika"

View File

@@ -151,6 +151,7 @@ AdvSceneSwitcher.macroTab.run.tooltip="无论条件如何,都运行所有宏
AdvSceneSwitcher.macroTab.runElse="运行宏(不满足条件)"
AdvSceneSwitcher.macroTab.runFail="运行 \"%1\" 失败!\n要么其中一个操作失败要么宏已在运行中.\n你想停止它吗?"
AdvSceneSwitcher.macroTab.runInParallel="与其他宏并行运行宏"
AdvSceneSwitcher.macroTab.onChange="仅在条件结果发生变化时执行操作(条件结果不变时只执行一次操作)"
AdvSceneSwitcher.macroTab.defaultname="宏 %1"
AdvSceneSwitcher.macroTab.defaultGroupName="分组 %1"
AdvSceneSwitcher.macroTab.macroNameExists="名称 \"%1\" 已被宏使用."
@@ -318,7 +319,11 @@ AdvSceneSwitcher.condition.file="文件"
AdvSceneSwitcher.condition.file.type.match="内容匹配"
AdvSceneSwitcher.condition.file.type.contentChange="内容发生改变"
AdvSceneSwitcher.condition.file.type.dateChange="修改日期发生改变"
AdvSceneSwitcher.condition.file.layout="{{filePath}}{{conditions}}{{regex}}"
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="媒体"
AdvSceneSwitcher.condition.media.checkType.state="状态匹配"
AdvSceneSwitcher.condition.media.checkType.time="时间限制匹配"
@@ -525,14 +530,14 @@ AdvSceneSwitcher.condition.replay.state.started="回放缓存已启动"
AdvSceneSwitcher.condition.replay.state.saved="已保存回放缓存"
AdvSceneSwitcher.condition.replay.entry="{{state}}"
AdvSceneSwitcher.condition.date="日期"
AdvSceneSwitcher.day.any="每一天"
AdvSceneSwitcher.day.monday="周一"
AdvSceneSwitcher.day.tuesday="周二"
AdvSceneSwitcher.day.wednesday="周三"
AdvSceneSwitcher.day.thursday="周四"
AdvSceneSwitcher.day.friday="周五"
AdvSceneSwitcher.day.saturday="周六"
AdvSceneSwitcher.day.sunday="周日"
AdvSceneSwitcher.condition.date.anyDay="每一天"
AdvSceneSwitcher.condition.date.monday="周一"
AdvSceneSwitcher.condition.date.tuesday="周二"
AdvSceneSwitcher.condition.date.wednesday="周三"
AdvSceneSwitcher.condition.date.thursday="周四"
AdvSceneSwitcher.condition.date.friday="周五"
AdvSceneSwitcher.condition.date.saturday="周六"
AdvSceneSwitcher.condition.date.sunday="周日"
AdvSceneSwitcher.condition.date.state.at="现在"
AdvSceneSwitcher.condition.date.state.after="之后"
AdvSceneSwitcher.condition.date.state.before="之前"
@@ -543,13 +548,12 @@ AdvSceneSwitcher.condition.date.ignoreDate="如果未选中,日期组件将被
AdvSceneSwitcher.condition.date.ignoreTime="如果未选中,时间组件将被忽略"
AdvSceneSwitcher.condition.date.showAdvancedSettings="显示高级设置"
AdvSceneSwitcher.condition.date.showSimpleSettings="显示简单设置"
AdvSceneSwitcher.condition.date.layout.simple.day="{{dayOfWeek}}"
AdvSceneSwitcher.condition.date.layout.simple.time="{{ignoreWeekTime}}{{weekCondition}}{{weekTime}}"
AdvSceneSwitcher.condition.date.layout.advanced="{{condition}}{{ignoreDate}}{{date}}{{ignoreTime}}{{time}}{{separator}}{{date2}}{{time2}}"
AdvSceneSwitcher.condition.date.layout.repeat="{{repeat}} 每 {{duration}} 重复一次时间/日期匹配"
AdvSceneSwitcher.condition.date.layout.pattern="当前日期 \"{{currentDate}}\" 与 {{pattern}} 模式匹配 , 例子:2025 03 18 24 00 00 = .... .. .. .. .. .. ,难崩,我测试了一段时间才明白"
AdvSceneSwitcher.condition.date.layout.nextMatchDate="下一次匹配 在: %1"
AdvSceneSwitcher.condition.date.layout.updateOnRepeat="{{updateOnRepeat}} 在重复时将选定日期更新为重复日期"
AdvSceneSwitcher.condition.date.entry.simple="{{dayOfWeek}}{{weekCondition}}{{ignoreWeekTime}}{{weekTime}}"
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}}{{ignoreDate}}{{date}}{{ignoreTime}}{{time}}{{separator}}{{date2}}{{time2}}"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}} 每 {{duration}} 重复一次时间/日期匹配"
AdvSceneSwitcher.condition.date.entry.pattern="当前日期 \"{{currentDate}}\" 与 {{pattern}} 模式匹配 , 例子:2025 03 18 24 00 00 = .... .. .. .. .. .. ,难崩,我测试了一段时间才明白"
AdvSceneSwitcher.condition.date.entry.nextMatchDate="下一次匹配 在: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} 在重复时将选定日期更新为重复日期"
AdvSceneSwitcher.condition.sceneTransform="场景项目变换"
AdvSceneSwitcher.condition.sceneTransform.getTransform="获取变换"
AdvSceneSwitcher.condition.sceneTransform.getCurrentValue="获取当前值"
@@ -809,6 +813,7 @@ 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="警告:更改回放缓存时长上限,仅适用于下次回放缓存开启时!"
@@ -835,7 +840,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="隐藏"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="切换可见性"
AdvSceneSwitcher.action.sceneVisibility.type.source="源"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="任何"
AdvSceneSwitcher.action.sceneVisibility.layout="{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.sceneVisibility.entry="{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="滤镜"
AdvSceneSwitcher.action.filter.type.enable="开启"
AdvSceneSwitcher.action.filter.type.disable="关闭"
@@ -989,9 +994,9 @@ AdvSceneSwitcher.action.transition.type.scene="场景转场动画"
AdvSceneSwitcher.action.transition.type.sceneOverride="覆盖场景转场动画"
AdvSceneSwitcher.action.transition.type.sourceShow="显示源转场动画"
AdvSceneSwitcher.action.transition.type.sourceHide="隐藏源转场动画"
AdvSceneSwitcher.action.transition.layout.type="更改 {{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.layout.transition="{{setTransition}}将转场动画类型设置为 {{transitions}}"
AdvSceneSwitcher.action.transition.layout.duration="{{setDuration}}将转场动画时长设置为 {{duration}}秒"
AdvSceneSwitcher.action.transition.entry.line1="更改 {{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}将转场动画类型设置为 {{transitions}}"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}将转场动画时长设置为 {{duration}}秒"
AdvSceneSwitcher.action.timer="计时器"
AdvSceneSwitcher.action.timer.type.pause="暂停"
AdvSceneSwitcher.action.timer.type.continue="继续"
@@ -1134,17 +1139,10 @@ 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}}{{nonModDelayDuration}}"
AdvSceneSwitcher.action.twitch.layout.default="{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}"
AdvSceneSwitcher.action.twitch.layout.chat="使用账户{{account}}{{actions}}{{channel}}"
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.layout.user.getInfo="使用账户{{account}}{{actions}}{{userInfoQueryType}}{{userLogin}}{{userId}}"
AdvSceneSwitcher.action.twitch.layout.reward.getInfo="使用账户{{account}}{{actions}} 频道{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
AdvSceneSwitcher.action.twitch.title.title="输入标题"
AdvSceneSwitcher.action.twitch.marker.description="标记描述"
AdvSceneSwitcher.action.twitch.clip.hasDelay="在捕捉视频片段前稍加延迟"
@@ -2117,6 +2115,8 @@ AdvSceneSwitcher.status.inactive="已停止"
AdvSceneSwitcher.running="插件正在运行"
AdvSceneSwitcher.stopped="插件已停止"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>这似乎是您第一次启动高级场景切换器.<br>请看一下 <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> 查看指南和示例列表.<br>如果有问题在在OBS论坛插件帖子内提问 <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">thread</span></a></p></body></html>"
AdvSceneSwitcher.deprecatedTabWarning="此选项卡的开发已停止!请考虑转换为使用宏来代替。\n可以在“常规”选项卡上禁用此提示."
AdvSceneSwitcher.unit.milliseconds="毫秒"

View File

@@ -68,10 +68,22 @@
<x>0</x>
<y>0</y>
<width>962</width>
<height>1190</height>
<height>1142</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_19">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="statusBox">
<property name="title">
@@ -251,13 +263,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="suppressCrashRecoveryDialog">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.suppressCrashRecoveryDialog</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -572,13 +577,6 @@
</layout>
</widget>
</item>
<item>
<widget class="QPushButton" name="openSetupWizard">
<property name="text">
<string>FirstRunWizard.openButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
@@ -604,11 +602,8 @@
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QGroupBox" name="macroListBox">
<property name="title">
<string>AdvSceneSwitcher.macroTab.macros</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_36">
<widget class="QWidget" name="wrapper">
<layout class="QVBoxLayout" name="verticalLayout_10">
<item>
<widget class="QLabel" name="macroPriorityWarning">
<property name="text">
@@ -691,66 +686,34 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="macroControlLayout">
<item>
<widget class="QToolButton" name="macroAdd">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.macroAddButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">addIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-plus</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="macroRemove">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.macroRemoveButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">removeIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-trash</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="macroUp">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.macroUpButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">upArrowIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-up</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="macroDown">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.macroDownButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">downArrowIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-down</string>
</property>
</widget>
</item>
</layout>
<widget class="QToolBar" name="macrosToolbar">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="floatable">
<bool>false</bool>
</property>
<addaction name="macroAdd"/>
<addaction name="macroRemove"/>
<addaction name="separator"/>
<addaction name="macroUp"/>
<addaction name="macroDown"/>
</widget>
</item>
</layout>
</widget>
<widget class="QGroupBox" name="macroEditGroup">
<property name="title">
<string>AdvSceneSwitcher.macroTab.edit</string>
<string>AdvSceneSwitcher.macroTab.title</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_38">
<item>
@@ -800,19 +763,25 @@
</widget>
</item>
<item>
<widget class="QLabel" name="label_18">
<property name="text">
<string>AdvSceneSwitcher.macroTab.actionTriggerMode.label</string>
<widget class="QCheckBox" name="runMacroOnChange">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.onChange</string>
</property>
<property name="wordWrap">
<bool>true</bool>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="actionTriggerMode">
<widget class="QLabel" name="label_18">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.actionTriggerMode.tooltip</string>
<string>AdvSceneSwitcher.macroTab.onChange</string>
</property>
<property name="text">
<string>AdvSceneSwitcher.macroTab.onChange</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
@@ -4053,6 +4022,78 @@
</widget>
</item>
</layout>
<action name="macroAdd">
<property name="icon">
<iconset>
<normaloff>:/res/images/plus.svg</normaloff>:/res/images/plus.svg</iconset>
</property>
<property name="text">
<string>Add</string>
</property>
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.macroAddButton</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-plus</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">addIconSmall</string>
</property>
</action>
<action name="macroRemove">
<property name="icon">
<iconset>
<normaloff>:/res/images/minus.svg</normaloff>:/res/images/minus.svg</iconset>
</property>
<property name="text">
<string>Remove</string>
</property>
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.macroRemoveButton</string>
</property>
<property name="shortcut">
<string>Del</string>
</property>
<property name="shortcutContext">
<enum>Qt::WidgetWithChildrenShortcut</enum>
</property>
<property name="iconVisibleInMenu">
<bool>false</bool>
</property>
<property name="class" stdset="0">
<string notr="true">icon-trash</string>
</property>
</action>
<action name="macroUp">
<property name="icon">
<iconset>
<normaloff>:/res/images/up.svg</normaloff>:/res/images/up.svg</iconset>
</property>
<property name="text">
<string>MoveUp</string>
</property>
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.macroUpButton</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-up</string>
</property>
</action>
<action name="macroDown">
<property name="icon">
<iconset>
<normaloff>:/res/images/down.svg</normaloff>:/res/images/down.svg</iconset>
</property>
<property name="text">
<string>MoveDown</string>
</property>
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.macroDownButton</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-down</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
@@ -4118,15 +4159,10 @@
<tabstop>priorityUp</tabstop>
<tabstop>priorityDown</tabstop>
<tabstop>threadPriority</tabstop>
<tabstop>macros</tabstop>
<tabstop>macroAdd</tabstop>
<tabstop>macroRemove</tabstop>
<tabstop>macroUp</tabstop>
<tabstop>macroDown</tabstop>
<tabstop>macroName</tabstop>
<tabstop>runMacro</tabstop>
<tabstop>runMacroInParallel</tabstop>
<tabstop>actionTriggerMode</tabstop>
<tabstop>runMacroOnChange</tabstop>
<tabstop>macroSettings</tabstop>
<tabstop>macroEdit</tabstop>
<tabstop>sceneGroups</tabstop>

View File

@@ -192,7 +192,17 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
switcher->m.lock();
if (switcher->VersionChanged(data, g_GIT_SHA1)) {
AskForBackup(data);
auto json = obs_data_get_json(data);
static QString jsonQString = json ? json : "";
std::thread t([]() {
obs_queue_task(
OBS_TASK_UI,
[](void *) {
AskForBackup(jsonQString);
},
nullptr, false);
});
t.detach();
}
switcher->LoadSettings(data);

View File

@@ -66,12 +66,10 @@ public slots:
void on_uiHintsDisable_stateChanged(int state);
void on_disableComboBoxFilter_stateChanged(int state);
void on_warnPluginLoadFailure_stateChanged(int state);
void on_suppressCrashRecoveryDialog_stateChanged(int state);
void on_hideLegacyTabs_stateChanged(int state);
void on_priorityUp_clicked();
void on_priorityDown_clicked();
void on_threadPriority_currentTextChanged(const QString &text);
void on_openSetupWizard_clicked();
/* --- End of legacy tab section --- */
@@ -103,13 +101,13 @@ public:
void HighlightMacroSettingsButton(bool enable = true);
public slots:
void on_macroAdd_clicked();
void on_macroRemove_clicked();
void on_macroUp_clicked() const;
void on_macroDown_clicked() const;
void on_macroAdd_triggered();
void on_macroRemove_triggered();
void on_macroUp_triggered() const;
void on_macroDown_triggered() const;
void on_macroName_editingFinished();
void on_runMacroInParallel_stateChanged(int value) const;
void on_actionTriggerMode_currentIndexChanged(int index) const;
void on_runMacroOnChange_stateChanged(int value) const;
void MacroSelectionChanged();
void ShowMacroContextMenu(const QPoint &);
void CopyMacro();

View File

@@ -1,19 +1,19 @@
#include "advanced-scene-switcher.hpp"
#include "crash-handler.hpp"
#include "file-selection.hpp"
#include "filter-combo-box.hpp"
#include "first-run-wizard.hpp"
#include "layout-helpers.hpp"
#include "macro.hpp"
#include "macro-search.hpp"
#include "macro-settings.hpp"
#include "path-helpers.hpp"
#include "selection-helpers.hpp"
#include "source-helpers.hpp"
#include "splitter-helpers.hpp"
#include "status-control.hpp"
#include "switcher-data.hpp"
#include "tab-helpers.hpp"
#include "ui-helpers.hpp"
#include "utility.hpp"
#include "variable.hpp"
#include "version.h"
@@ -195,15 +195,6 @@ 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(
@@ -370,46 +361,14 @@ void AdvSceneSwitcher::RestoreWindowGeo()
}
}
static void renameMacroIfNecessary(const std::shared_ptr<Macro> &macro)
{
if (!GetMacroByName(macro->Name().c_str())) {
return;
}
auto name = macro->Name();
int i = 2;
while (GetMacroByName((name + " " + std::to_string(i)).c_str())) {
i++;
}
macro->SetName(name + " " + std::to_string(i));
}
void AdvSceneSwitcher::CheckFirstTimeSetup()
{
if (!IsFirstRun() || !GetTopLevelMacros().empty()) {
return;
if (switcher->firstBoot && !switcher->disableHints) {
switcher->firstBoot = false;
DisplayMessage(
obs_module_text("AdvSceneSwitcher.firstBootMessage"));
switcher->Start();
}
auto macro = FirstRunWizard::ShowWizard(this);
if (macro) {
renameMacroIfNecessary(macro);
QTimer::singleShot(0, this,
[this, macro]() { ui->macros->Add(macro); });
}
switcher->Start();
}
void AdvSceneSwitcher::on_openSetupWizard_clicked()
{
auto macro = FirstRunWizard::ShowWizard(this);
if (!macro) {
return;
}
renameMacroIfNecessary(macro);
ui->macros->Add(macro);
ui->tabWidget->setCurrentWidget(ui->macroTab);
}
void AdvSceneSwitcher::on_tabWidget_currentChanged(int)
@@ -956,7 +915,6 @@ void AdvSceneSwitcher::SetupGeneralTab()
FilterComboBox::SetFilterBehaviourEnabled(
!switcher->disableFilterComboboxFilter);
ui->warnPluginLoadFailure->setChecked(switcher->warnPluginLoadFailure);
ui->suppressCrashRecoveryDialog->setChecked(GetSuppressCrashDialog());
ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs);
populatePriorityFunctionList(ui->priorityList);

View File

@@ -4,8 +4,6 @@
#include "macro.hpp"
#include "macro-action-factory.hpp"
#include <chrono>
namespace advss {
const std::string MacroActionMacro::id = "macro";
@@ -120,36 +118,6 @@ bool MacroActionMacro::PerformAction()
case Action::TOGGLE_ACTION:
AdjustActionState(macro);
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;
}
@@ -207,9 +175,6 @@ 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;
default:
break;
}
@@ -237,8 +202,8 @@ bool MacroActionMacro::Save(obs_data_t *obj) const
bool MacroActionMacro::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
SetAction(static_cast<MacroActionMacro::Action>(
obs_data_get_int(obj, "action")));
_action = static_cast<MacroActionMacro::Action>(
obs_data_get_int(obj, "action"));
_macro.Load(obj);
_actionSelectionType = static_cast<SelectionType>(
obs_data_get_int(obj, "actionSelectionType"));
@@ -338,56 +303,6 @@ 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>>
@@ -412,8 +327,6 @@ 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) {
@@ -583,7 +496,7 @@ void MacroActionMacroEdit::UpdateEntryData()
}
_actions->setCurrentIndex(
_actions->findData(static_cast<int>(_entryData->GetAction())));
_actions->findData(static_cast<int>(_entryData->_action)));
_actionSelectionType->setCurrentIndex(_actionSelectionType->findData(
static_cast<int>(_entryData->_actionSelectionType)));
_actionIndex->SetValue(_entryData->_actionIndex);
@@ -632,8 +545,8 @@ void MacroActionMacroEdit::MacroChanged(const QString &text)
void MacroActionMacroEdit::ActionChanged(int idx)
{
GUARD_LOADING_AND_LOCK();
_entryData->SetAction(static_cast<MacroActionMacro::Action>(
_actions->itemData(idx).toInt()));
_entryData->_action = static_cast<MacroActionMacro::Action>(
_actions->itemData(idx).toInt());
SetWidgetVisibility();
}
@@ -752,7 +665,7 @@ void MacroActionMacroEdit::SetWidgetVisibility()
};
const auto action = _entryData->GetAction();
const auto action = _entryData->_action;
const char *layoutText = "";
switch (action) {
case MacroActionMacro::Action::PAUSE:
@@ -761,7 +674,6 @@ 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_ACTIONS:

View File

@@ -56,14 +56,11 @@ public:
TOGGLE_ACTION,
TOGGLE_PAUSE,
NESTED_MACRO,
GET_INFO,
};
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;
@@ -75,12 +72,9 @@ 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;
};

View File

@@ -17,14 +17,6 @@ namespace advss {
const std::string MacroActionVariable::id = "variable";
std::vector<TempVariableRef> MacroActionVariable::GetTempVarRefs() const
{
if (!_tempVar.HasValidID()) {
return {};
}
return {_tempVar};
}
bool MacroActionVariable::_registered = MacroActionFactory::Register(
MacroActionVariable::id,
{MacroActionVariable::Create, MacroActionVariableEdit::Create,
@@ -227,43 +219,6 @@ void MacroActionVariable::GenerateRandomNumber(Variable *var)
}
}
void MacroActionVariable::PickRandomValue(Variable *var)
{
static std::random_device rd;
static std::mt19937 gen(rd());
static const uint8_t maxIter = 255;
if (_randomValues.isEmpty()) {
return;
}
if (_randomValues.size() == 1) {
const auto &value = _randomValues.at(0);
var->SetValue(value);
_lastRandomValue = value;
}
std::uniform_int_distribution<int> dis(0, _randomValues.size() - 1);
StringVariable value = _randomValues.at(dis(gen));
uint8_t iter = 0;
for (; !_allowRepeatValues && iter < maxIter &&
_lastRandomValue.has_value() &&
std::string(value) == _lastRandomValue.value();
iter++) {
value = _randomValues.at(dis(gen));
}
if (iter == maxIter) {
blog(LOG_INFO,
"giving up picking non-repeat random value after %d tries",
maxIter);
}
var->SetValue(value);
_lastRandomValue = value;
}
struct AskForInputParams {
AskForInputParams(const QString &prompt_, const QString &placeholder_)
: prompt(prompt_),
@@ -503,9 +458,6 @@ bool MacroActionVariable::PerformAction()
case Action::RANDOM_NUMBER:
GenerateRandomNumber(var.get());
return true;
case Action::RANDOM_LIST_VALUE:
PickRandomValue(var.get());
return true;
}
return true;
@@ -546,8 +498,6 @@ bool MacroActionVariable::Save(obs_data_t *obj) const
_randomNumberStart.Save(obj, "randomNumberStart");
_randomNumberEnd.Save(obj, "randomNumberEnd");
obs_data_set_bool(obj, "generateInteger", _generateInteger);
_randomValues.Save(obj, "randomValues", "value");
obs_data_set_bool(obj, "allowRepeatValues", _allowRepeatValues);
_jsonQuery.Save(obj, "jsonQuery");
_jsonIndex.Save(obj, "jsonIndex");
@@ -606,8 +556,6 @@ bool MacroActionVariable::Load(obs_data_t *obj)
_randomNumberStart.Load(obj, "randomNumberStart");
_randomNumberEnd.Load(obj, "randomNumberEnd");
_generateInteger = obs_data_get_bool(obj, "generateInteger");
_randomValues.Load(obj, "randomValues", "value");
_allowRepeatValues = obs_data_get_bool(obj, "allowRepeatValues");
_jsonQuery.Load(obj, "jsonQuery");
_jsonIndex.Load(obj, "jsonIndex");
@@ -776,8 +724,6 @@ static inline void populateActionSelection(QComboBox *list)
"AdvSceneSwitcher.action.variable.type.roundToInt"},
{MacroActionVariable::Action::RANDOM_NUMBER,
"AdvSceneSwitcher.action.variable.type.randomNumber"},
{MacroActionVariable::Action::RANDOM_LIST_VALUE,
"AdvSceneSwitcher.action.variable.type.randomListValue"},
{true, ""}, // Separator
{MacroActionVariable::Action::USER_INPUT,
@@ -890,13 +836,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
obs_module_text(
"AdvSceneSwitcher.action.variable.generateInteger"),
this)),
_randomNumberLayout(new QVBoxLayout()),
_randomValues(new StringListEdit(this)),
_allowRepeatValues(new QCheckBox(
obs_module_text(
"AdvSceneSwitcher.action.variable.type.allowRepeat"),
this)),
_randomValueLayout(new QVBoxLayout()),
_randomLayout(new QVBoxLayout()),
_jsonQuery(new VariableLineEdit(this)),
_jsonQueryHelp(new HelpIcon(
obs_module_text(
@@ -932,7 +872,6 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_randomNumberStart->setMaximum(9999999999);
_randomNumberEnd->setMinimum(-9999999999);
_randomNumberEnd->setMaximum(9999999999);
_randomValues->SetMaxStringSize(99999999);
_jsonIndex->setMaximum(999);
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
@@ -1021,11 +960,6 @@ MacroActionVariableEdit::MacroActionVariableEdit(
SLOT(RandomNumberEndChanged(const NumberVariable<double> &)));
QWidget::connect(_generateInteger, SIGNAL(stateChanged(int)), this,
SLOT(GenerateIntegerChanged(int)));
QWidget::connect(_randomValues,
SIGNAL(StringListChanged(const StringList &)), this,
SLOT(RandomValueListChanged(const StringList &)));
QWidget::connect(_allowRepeatValues, SIGNAL(stateChanged(int)), this,
SLOT(AllowRepeatValuesChanged(int)));
QWidget::connect(_jsonQuery, SIGNAL(editingFinished()), this,
SLOT(JsonQueryChanged()));
QWidget::connect(
@@ -1092,11 +1026,8 @@ MacroActionVariableEdit::MacroActionVariableEdit(
obs_module_text(
"AdvSceneSwitcher.action.variable.layout.randomNumber"),
randomLayout, widgetPlaceholders);
_randomNumberLayout->addLayout(randomLayout);
_randomNumberLayout->addWidget(_generateInteger);
_randomValueLayout->addWidget(_randomValues);
_randomValueLayout->addWidget(_allowRepeatValues);
_randomLayout->addLayout(randomLayout);
_randomLayout->addWidget(_generateInteger);
auto regexConfigLayout = new QHBoxLayout;
regexConfigLayout->addWidget(_subStringRegex);
@@ -1115,8 +1046,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
layout->addWidget(_mathExpressionResult);
layout->addLayout(_promptLayout);
layout->addLayout(_placeholderLayout);
layout->addLayout(_randomNumberLayout);
layout->addLayout(_randomValueLayout);
layout->addLayout(_randomLayout);
setLayout(layout);
_entryData = entryData;
@@ -1175,8 +1105,6 @@ void MacroActionVariableEdit::UpdateEntryData()
_randomNumberStart->SetValue(_entryData->_randomNumberStart);
_randomNumberEnd->SetValue(_entryData->_randomNumberEnd);
_generateInteger->setChecked(_entryData->_generateInteger);
_allowRepeatValues->setChecked(_entryData->_allowRepeatValues);
_randomValues->SetStringList(_entryData->_randomValues);
_jsonQuery->setText(_entryData->_jsonQuery);
_jsonIndex->SetValue(_entryData->_jsonIndex);
@@ -1458,7 +1386,6 @@ void MacroActionVariableEdit::SelectionChanged(const TempVariableRef &var)
{
GUARD_LOADING_AND_LOCK();
_entryData->_tempVar = var;
IncrementTempVarInUseGeneration();
SetWidgetVisibility();
}
@@ -1520,18 +1447,6 @@ void MacroActionVariableEdit::GenerateIntegerChanged(int value)
_entryData->_generateInteger = value;
}
void MacroActionVariableEdit::RandomValueListChanged(const StringList &values)
{
GUARD_LOADING_AND_LOCK();
_entryData->_randomValues = values;
}
void MacroActionVariableEdit::AllowRepeatValuesChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_allowRepeatValues = value;
}
void MacroActionVariableEdit::JsonQueryChanged()
{
GUARD_LOADING_AND_LOCK();
@@ -1726,13 +1641,9 @@ void MacroActionVariableEdit::SetWidgetVisibility()
MacroActionVariable::Action::PAD);
_caseType->setVisible(_entryData->_action ==
MacroActionVariable::Action::CHANGE_CASE);
SetLayoutVisible(_randomNumberLayout,
SetLayoutVisible(_randomLayout,
_entryData->_action ==
MacroActionVariable::Action::RANDOM_NUMBER);
SetLayoutVisible(
_randomValueLayout,
_entryData->_action ==
MacroActionVariable::Action::RANDOM_LIST_VALUE);
_jsonQuery->setVisible(_entryData->_action ==
MacroActionVariable::Action::QUERY_JSON);
_jsonQueryHelp->setVisible(_entryData->_action ==

View File

@@ -6,7 +6,6 @@
#include "resizing-text-edit.hpp"
#include "scene-selection.hpp"
#include "single-char-selection.hpp"
#include "string-list.hpp"
#include "variable-line-edit.hpp"
#include "variable-text-edit.hpp"
#include "variable-spinbox.hpp"
@@ -23,7 +22,6 @@ 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);
@@ -58,7 +56,6 @@ public:
QUERY_JSON,
ARRAY_JSON,
COPY_VAR,
RANDOM_LIST_VALUE,
};
Action _action = Action::SET_VALUE;
@@ -110,10 +107,6 @@ public:
DoubleVariable _randomNumberEnd = 100;
bool _generateInteger = true;
StringList _randomValues = {"value1", "value2", "value3"};
bool _allowRepeatValues = true;
std::optional<std::string> _lastRandomValue;
StringVariable _jsonQuery = "$.some.nested.value";
IntVariable _jsonIndex = 0;
@@ -126,7 +119,6 @@ private:
void HandleCaseChange(Variable *);
void SetToSceneItemName(Variable *);
void GenerateRandomNumber(Variable *);
void PickRandomValue(Variable *);
std::weak_ptr<MacroSegment> _macroSegment;
int _segmentIdxLoadValue = -1;
@@ -183,8 +175,6 @@ private slots:
void RandomNumberStartChanged(const NumberVariable<double> &);
void RandomNumberEndChanged(const NumberVariable<double> &);
void GenerateIntegerChanged(int);
void RandomValueListChanged(const StringList &);
void AllowRepeatValuesChanged(int);
void JsonQueryChanged();
void JsonIndexChanged(const NumberVariable<int> &);
@@ -234,10 +224,7 @@ private:
VariableDoubleSpinBox *_randomNumberStart;
VariableDoubleSpinBox *_randomNumberEnd;
QCheckBox *_generateInteger;
QVBoxLayout *_randomNumberLayout;
StringListEdit *_randomValues;
QCheckBox *_allowRepeatValues;
QVBoxLayout *_randomValueLayout;
QVBoxLayout *_randomLayout;
VariableLineEdit *_jsonQuery;
QLabel *_jsonQueryHelp;
VariableSpinBox *_jsonIndex;

View File

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

View File

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

View File

@@ -7,17 +7,6 @@ MacroCondition::MacroCondition(Macro *m, bool supportsVariableValue)
{
}
bool MacroCondition::EvaluateCondition()
{
bool newValue = CheckCondition();
_changed = _previousValue.has_value() && (*_previousValue != newValue);
const bool negate = _logic.IsNegationType(GetLogicType());
_risingEdge = _changed &&
((!negate && newValue) || (negate && !newValue));
_previousValue = newValue;
return newValue;
}
bool MacroCondition::Save(obs_data_t *obj) const
{
MacroSegment::Save(obj);

View File

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

View File

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

View File

@@ -8,6 +8,9 @@ namespace advss {
class Macro;
class MacroSegment;
/*******************************************************************************
* Advanced Scene Switcher window
*******************************************************************************/
class MacroEdit : public QWidget {
Q_OBJECT

View File

@@ -242,11 +242,6 @@ bool RunMacroActions(Macro *macro)
return macro && macro->PerformActions(true);
}
bool RunMacroElseActions(Macro *macro)
{
return macro && macro->PerformActions(false);
}
void ResetMacroConditionTimers(Macro *macro)
{
if (!macro) {

View File

@@ -8,6 +8,7 @@
#include <condition_variable>
#include <deque>
#include <optional>
#include <string_view>
#include <thread>
struct obs_data;
@@ -68,7 +69,6 @@ EXPORT void AddMacroHelperThread(Macro *, std::thread &&);
EXPORT bool CheckMacros();
EXPORT bool RunMacroActions(Macro *);
bool RunMacroElseActions(Macro *);
EXPORT bool RunMacros();
void StopAllMacros();

View File

@@ -13,11 +13,6 @@
namespace advss {
std::vector<TempVariableRef> MacroSegment::GetTempVarRefs() const
{
return {};
}
MacroSegment::MacroSegment(Macro *m, bool supportsVariableValue)
: _macro(m),
_supportsVariableValue(supportsVariableValue)
@@ -157,34 +152,6 @@ 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::CopyTempVarValuesFrom(const MacroSegment &other)
{
for (const auto &src : other._tempVariables) {
for (auto &dst : _tempVariables) {
if (dst.ID() != src.ID()) {
continue;
}
auto value = src.Value();
if (value) {
dst.SetValue(*value);
} else {
dst.InvalidateValue();
}
break;
}
}
}
void MacroSegment::SetTempVarValue(const std::string &id,
const std::string &value)
{

View File

@@ -34,7 +34,6 @@ public:
std::string GetCustomLabel() const { return _customLabel; }
virtual bool Save(obs_data_t *obj) const = 0;
virtual bool Load(obs_data_t *obj) = 0;
virtual std::vector<TempVariableRef> GetTempVarRefs() const;
virtual bool PostLoad();
virtual std::string GetShortDesc() const;
virtual std::string GetId() const = 0;
@@ -55,9 +54,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);
void CopyTempVarValuesFrom(const MacroSegment &other);
template<typename T, typename = std::enable_if_t<
std::is_same<std::decay_t<T>, bool>::value>>
@@ -67,14 +64,6 @@ 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>

View File

@@ -51,7 +51,6 @@ void MacroSelection::HideSelectedMacro()
return;
}
#ifndef UNIT_TEST
const auto m = ssWindow->ui->macros->GetCurrentMacro();
if (!m) {
return;
@@ -62,7 +61,6 @@ void MacroSelection::HideSelectedMacro()
}
qobject_cast<QListView *>(view())->setRowHidden(idx, true);
#endif // !UNIT_TEST
}
void MacroSelection::HideGroups()

View File

@@ -25,8 +25,6 @@ namespace advss {
static QObject *addPulse = nullptr;
static QTimer onChangeHighlightTimer;
static std::chrono::high_resolution_clock::time_point
lastOnChangeHighlightCheckTime{};
static void disableAddButtonHighlight()
{
@@ -99,7 +97,7 @@ bool AdvSceneSwitcher::AddNewMacro(std::shared_ptr<Macro> &res,
return true;
}
void AdvSceneSwitcher::on_macroAdd_clicked()
void AdvSceneSwitcher::on_macroAdd_triggered()
{
// Hotkey to add new macro will also use this function.
// Since we don't want the hotkey to have an effect if the macro tab is
@@ -249,12 +247,12 @@ void AdvSceneSwitcher::RenameMacro(std::shared_ptr<Macro> &macro,
MacroSignalManager::Instance()->Rename(oldName, name);
}
void AdvSceneSwitcher::on_macroRemove_clicked()
void AdvSceneSwitcher::on_macroRemove_triggered()
{
RemoveSelectedMacros();
}
void AdvSceneSwitcher::on_macroUp_clicked() const
void AdvSceneSwitcher::on_macroUp_triggered() const
{
auto macro = GetSelectedMacro();
if (!macro) {
@@ -263,7 +261,7 @@ void AdvSceneSwitcher::on_macroUp_clicked() const
ui->macros->Up(macro);
}
void AdvSceneSwitcher::on_macroDown_clicked() const
void AdvSceneSwitcher::on_macroDown_triggered() const
{
auto macro = GetSelectedMacro();
if (!macro) {
@@ -474,17 +472,14 @@ void AdvSceneSwitcher::on_runMacroInParallel_stateChanged(int value) const
macro->SetRunInParallel(value);
}
void AdvSceneSwitcher::on_actionTriggerMode_currentIndexChanged(int index) const
void AdvSceneSwitcher::on_runMacroOnChange_stateChanged(int value) const
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
auto lock = LockContext();
const auto mode = static_cast<Macro::ActionTriggerMode>(
ui->actionTriggerMode->itemData(index).toInt());
macro->SetActionTriggerMode(mode);
macro->SetMatchOnChange(value);
}
void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable) const
@@ -492,7 +487,7 @@ void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable) const
ui->macroName->setDisabled(disable);
ui->runMacro->setDisabled(disable);
ui->runMacroInParallel->setDisabled(disable);
ui->actionTriggerMode->setDisabled(disable);
ui->runMacroOnChange->setDisabled(disable);
ui->macroEdit->SetControlsDisabled(disable);
}
@@ -522,12 +517,10 @@ void AdvSceneSwitcher::MacroSelectionChanged()
{
const QSignalBlocker b1(ui->macroName);
const QSignalBlocker b2(ui->runMacroInParallel);
const QSignalBlocker b3(ui->actionTriggerMode);
const QSignalBlocker b3(ui->runMacroOnChange);
ui->macroName->setText(macro->Name().c_str());
ui->runMacroInParallel->setChecked(macro->RunInParallel());
ui->actionTriggerMode->setCurrentIndex(
ui->actionTriggerMode->findData(static_cast<int>(
macro->GetActionTriggerMode())));
ui->runMacroOnChange->setChecked(macro->MatchOnChange());
}
macro->ResetUIHelpers();
@@ -557,14 +550,10 @@ void AdvSceneSwitcher::HighlightOnChange() const
return;
}
if (macro->ActionTriggerModePreventedActionsSince(
lastOnChangeHighlightCheckTime)) {
HighlightWidget(ui->actionTriggerMode, Qt::yellow,
if (macro->OnChangePreventedActionsRecently()) {
HighlightWidget(ui->runMacroOnChange, Qt::yellow,
Qt::transparent, true);
}
lastOnChangeHighlightCheckTime =
std::chrono::high_resolution_clock::now();
}
void AdvSceneSwitcher::on_macroSettings_clicked()
@@ -643,13 +632,11 @@ void AdvSceneSwitcher::SetupMacroTab()
auto &macros = GetTopLevelMacros();
if (macros.size() == 0 && !switcher->disableHints) {
addPulse = HighlightWidget(ui->macroAdd, QColor(Qt::green));
addPulse = HighlightWidget(
ui->macrosToolbar->widgetForAction(ui->macroAdd),
QColor(Qt::green));
}
auto macroControls = setupToolBar({{ui->macroAdd, ui->macroRemove},
{ui->macroUp, ui->macroDown}});
ui->macroControlLayout->addWidget(macroControls);
ui->macros->Reset(macros, GetGlobalMacroSettings()._highlightExecuted);
connect(ui->macros, SIGNAL(MacroSelectionChanged()), this,
SLOT(MacroSelectionChanged()));
@@ -663,56 +650,28 @@ void AdvSceneSwitcher::SetupMacroTab()
ui->macroPriorityWarning->setVisible(
switcher->functionNamesByPriority[0] != macro_func);
lastOnChangeHighlightCheckTime =
std::chrono::high_resolution_clock::now();
onChangeHighlightTimer.setInterval(1500);
connect(&onChangeHighlightTimer, SIGNAL(timeout()), this,
SLOT(HighlightOnChange()));
onChangeHighlightTimer.start();
// Reserve more space for macro edit area than for the macro list
ui->macroListMacroEditSplitter->setStretchFactor(0, 1);
ui->macroListMacroEditSplitter->setStretchFactor(1, 4);
if (switcher->saveWindowGeo) {
if (shouldRestoreSplitter(
switcher->macroListMacroEditSplitterPosition)) {
ui->macroListMacroEditSplitter->setSizes(
switcher->macroListMacroEditSplitterPosition);
}
}
SetupMacroSearchWidgets(ui->macroSearchLayout, ui->macroSearchText,
ui->macroSearchClear, ui->macroSearchType,
ui->macroSearchRegex,
ui->macroSearchShowSettings,
[this]() { ui->macros->RefreshFilter(); });
static const std::vector<
std::pair<Macro::ActionTriggerMode, const char *>>
actionTriggerModes = {
{Macro::ActionTriggerMode::ALWAYS,
"AdvSceneSwitcher.macroTab.actionTriggerMode.always"},
{Macro::ActionTriggerMode::MACRO_RESULT_CHANGED,
"AdvSceneSwitcher.macroTab.actionTriggerMode.onOverallChange"},
{Macro::ActionTriggerMode::ANY_CONDITION_CHANGED,
"AdvSceneSwitcher.macroTab.actionTriggerMode.onAnyConditionChange"},
{Macro::ActionTriggerMode::ANY_CONDITION_TRIGGERED,
"AdvSceneSwitcher.macroTab.actionTriggerMode.onAnyConditionTriggered"},
};
for (const auto &[mode, name] : actionTriggerModes) {
ui->actionTriggerMode->addItem(obs_module_text(name),
static_cast<int>(mode));
}
ui->macroListBox->setSizePolicy(QSizePolicy::Ignored,
QSizePolicy::Preferred);
ui->macroListBox->setMinimumWidth(0);
ui->macroEditGroup->setSizePolicy(QSizePolicy::Ignored,
QSizePolicy::Preferred);
ui->macroEditGroup->setMinimumWidth(0);
if (shouldRestoreSplitter(
switcher->macroListMacroEditSplitterPosition)) {
ui->macroListMacroEditSplitter->setSizes(
switcher->macroListMacroEditSplitterPosition);
} else {
QTimer::singleShot(0, this, [this]() {
const auto totalWidth =
ui->macroListMacroEditSplitter->width();
ui->macroListMacroEditSplitter->setSizes(
{totalWidth / 5, totalWidth * 4 / 5});
});
}
}
void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
@@ -722,7 +681,7 @@ void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.contextMenuAdd"),
this, &AdvSceneSwitcher::on_macroAdd_clicked);
this, &AdvSceneSwitcher::on_macroAdd_triggered);
auto copy = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.copy"), this,
@@ -738,7 +697,7 @@ void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
auto remove = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.remove"), this,
&AdvSceneSwitcher::on_macroRemove_clicked);
&AdvSceneSwitcher::on_macroRemove_triggered);
remove->setDisabled(ui->macros->SelectionEmpty());
menu.addSeparator();

View File

@@ -5,17 +5,18 @@
#include "path-helpers.hpp"
#include "sync-helpers.hpp"
#include "ui-helpers.hpp"
#include "utility.hpp"
#include <obs.h>
#include <string>
#include <QLabel>
#include <QLineEdit>
#include <QSpacerItem>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QStylePainter>
#include <QToolTip>
Q_DECLARE_METATYPE(std::shared_ptr<advss::Macro>);
@@ -89,35 +90,6 @@ MacroTreeItem::MacroTreeItem(MacroTree *tree, std::shared_ptr<Macro> macroItem,
_timer.start(1500);
}
bool MacroTreeItem::event(QEvent *event)
{
if (event->type() != QEvent::ToolTip) {
return QFrame::event(event);
}
if (_macro->IsGroup()) {
return true;
}
QString text;
if (!_macro->WasExecutedSince({})) {
text = obs_module_text(
"AdvSceneSwitcher.macroTab.macroNotYetExecutedTooltip");
} else {
const QString formatStr = obs_module_text(
"AdvSceneSwitcher.macroTab.macroLastExecutedTooltip");
const auto secondsSinceLastRun =
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::high_resolution_clock::now() -
_macro->GetLastExecutionTime());
text = formatStr.arg(secondsSinceLastRun.count());
}
auto helpEvent = static_cast<QHelpEvent *>(event);
QToolTip::showText(helpEvent->globalPos(), text, this);
return true;
}
void MacroTreeItem::EnableHighlight(bool enable)
{
_highlight = enable;
@@ -215,20 +187,10 @@ void MacroTreeItem::HighlightIfExecuted()
return;
}
bool wasHighlighted = false;
if (_lastHighlightCheckTime.time_since_epoch().count() != 0 &&
_macro->WasExecutedSince(_lastHighlightCheckTime)) {
HighlightWidget(this, Qt::green, QColor(0, 0, 0, 0), true);
wasHighlighted = true;
}
if (!wasHighlighted &&
_lastHighlightCheckTime.time_since_epoch().count() != 0 &&
_macro->ActionTriggerModePreventedActionsSince(
_lastHighlightCheckTime)) {
HighlightWidget(this, Qt::yellow, QColor(0, 0, 0, 0), true);
}
_lastHighlightCheckTime = std::chrono::high_resolution_clock::now();
}
@@ -305,7 +267,7 @@ void MacroTreeItem::Update(bool force)
_expand->blockSignals(true);
_expand->setChecked(_macro->IsCollapsed());
_expand->blockSignals(false);
connect(_expand, &QCheckBox::toggled, this,
connect(_expand, &QPushButton::toggled, this,
&MacroTreeItem::ExpandClicked);
} else {
_spacer = new QSpacerItem(3, 1);

View File

@@ -34,9 +34,6 @@ public:
explicit MacroTreeItem(MacroTree *tree, std::shared_ptr<Macro> macro,
bool highlight);
protected:
bool event(QEvent *) override;
private slots:
void ExpandClicked(bool checked);
void EnableHighlight(bool enable);

View File

@@ -1,111 +0,0 @@
#include "websocket-api.hpp"
#include "log-helper.hpp"
#include "plugin-state-helpers.hpp"
#include "variable.hpp"
#include "macro-helpers.hpp"
#include <obs.hpp>
namespace advss {
static bool setup();
static bool setupDone = setup();
static void receiveRunMacroMessage(obs_data_t *data, obs_data_t *);
static void receiveSetVariablesMessage(obs_data_t *data, obs_data_t *);
static const char *runMacroMessage = "AdvancedSceneSwitcherRunMacro";
static const char *setVariablesMessage = "AdvancedSceneSwitcherSetVariables";
static bool setup()
{
AddPluginInitStep([]() {
RegisterWebsocketRequest(runMacroMessage,
receiveRunMacroMessage);
RegisterWebsocketRequest(setVariablesMessage,
receiveSetVariablesMessage);
});
return true;
}
static void setVariables(obs_data_t *data)
{
OBSDataArrayAutoRelease variables =
obs_data_get_array(data, "variables");
size_t count = obs_data_array_count(variables);
for (size_t i = 0; i < count; i++) {
OBSDataAutoRelease item = obs_data_array_item(variables, i);
if (!obs_data_has_user_value(item, "name")) {
blog(LOG_INFO,
"ignoring invalid variable entry in \"%s\" websocket message (missing \"name\")",
runMacroMessage);
continue;
}
if (!obs_data_has_user_value(item, "value")) {
blog(LOG_INFO,
"ignoring invalid variable entry in \"%s\" websocket message (missing \"value\")",
runMacroMessage);
continue;
}
auto varName = obs_data_get_string(item, "name");
auto value = obs_data_get_string(item, "value");
auto weakVar = GetWeakVariableByName(varName);
auto var = weakVar.lock();
if (!var) {
blog(LOG_INFO,
"ignoring invalid variable name \"%s\" in \"%s\" websocket message",
varName, runMacroMessage);
continue;
}
var->SetValue(value);
}
}
static void receiveRunMacroMessage(obs_data_t *data, obs_data_t *)
{
if (!obs_data_has_user_value(data, "name")) {
blog(LOG_INFO,
"ignoring invalid \"%s\" websocket message (missing \"name\")",
runMacroMessage);
return;
}
auto name = obs_data_get_string(data, "name");
const auto weakMacro =
GetWeakMacroByName(obs_data_get_string(data, "name"));
const auto macro = weakMacro.lock();
if (!macro) {
blog(LOG_INFO,
"ignoring invalid \"%s\" websocket message (macro \"%s\") not found",
runMacroMessage, name);
return;
}
if (obs_data_has_user_value(data, "variables")) {
setVariables(data);
}
const bool runElse = obs_data_get_bool(data, "runElseActions");
if (runElse) {
RunMacroElseActions(macro.get());
} else {
RunMacroActions(macro.get());
}
}
static void receiveSetVariablesMessage(obs_data_t *data, obs_data_t *)
{
if (!obs_data_has_user_value(data, "variables")) {
blog(LOG_INFO,
"ignoring invalid \"%s\" websocket message (missing \"variables\")",
setVariablesMessage);
return;
}
setVariables(data);
}
} // namespace advss

View File

@@ -111,7 +111,7 @@ static bool checkCondition(const std::shared_ptr<MacroCondition> &condition)
const auto startTime = std::chrono::high_resolution_clock::now();
bool conditionMatched = false;
condition->WithLock([&condition, &conditionMatched]() {
conditionMatched = condition->EvaluateCondition();
conditionMatched = condition->CheckCondition();
});
const auto endTime = std::chrono::high_resolution_clock::now();
const auto timeSpent = endTime - startTime;
@@ -241,37 +241,9 @@ bool Macro::CheckConditions(bool ignorePause)
vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched);
_actionModeMatch = false;
switch (_actionTriggerMode) {
case Macro::ActionTriggerMode::ALWAYS:
_actionModeMatch = true;
break;
case Macro::ActionTriggerMode::MACRO_RESULT_CHANGED:
_actionModeMatch = _lastMatched != _matched;
break;
case Macro::ActionTriggerMode::ANY_CONDITION_CHANGED:
for (const auto &condition : _conditions) {
if (condition->HasChanged()) {
_actionModeMatch = true;
}
}
break;
case Macro::ActionTriggerMode::ANY_CONDITION_TRIGGERED:
for (const auto &condition : _conditions) {
if (condition->IsRisingEdge()) {
_actionModeMatch = true;
}
}
break;
default:
break;
}
const bool hasActionsToExecute = _matched ? (_actions.size() > 0)
: (_elseActions.size() > 0);
if (!_actionModeMatch && hasActionsToExecute) {
_lastActionRunModePreventTime =
std::chrono::high_resolution_clock::now();
_conditionSateChanged = _lastMatched != _matched;
if (!_conditionSateChanged && _performActionsOnChange) {
_onPreventedActionExecution = true;
}
_lastMatched = _matched;
@@ -330,16 +302,6 @@ bool Macro::WasExecutedSince(const TimePoint &time) const
return _lastExecutionTime > time;
}
bool Macro::ActionTriggerModePreventedActionsSince(const TimePoint &time) const
{
return _lastActionRunModePreventTime > time;
}
Macro::TimePoint Macro::GetLastExecutionTime() const
{
return _lastExecutionTime;
}
bool Macro::ConditionsShouldBeChecked() const
{
if (!_useCustomConditionCheckInterval) {
@@ -366,9 +328,10 @@ bool Macro::ShouldRunActions() const
const bool hasActionsToExecute =
!_paused && (_matched || _elseActions.size() > 0) &&
_actionModeMatch;
(!_performActionsOnChange || _conditionSateChanged);
if (VerboseLoggingEnabled() && !_actionModeMatch) {
if (VerboseLoggingEnabled() && _performActionsOnChange &&
!_conditionSateChanged) {
if (_matched && _actions.size() > 0) {
blog(LOG_INFO, "skip actions for Macro %s (on change)",
_name.c_str());
@@ -399,24 +362,10 @@ void Macro::ResetTimers()
_lastExecutionTime = {};
}
void Macro::SetActionTriggerMode(ActionTriggerMode mode)
{
_actionTriggerMode = mode;
}
Macro::ActionTriggerMode Macro::GetActionTriggerMode() const
{
return _actionTriggerMode;
}
bool Macro::RunActionsHelper(
const std::deque<std::shared_ptr<MacroAction>> &actionsToRun,
bool ignorePause)
{
if (_paused && !ignorePause) {
return true;
}
// Create copy of action list as elements might be removed, inserted, or
// reordered while actions are currently being executed.
auto actions = actionsToRun;
@@ -428,13 +377,9 @@ bool Macro::RunActionsHelper(
}
if (action->Enabled()) {
action->LogAction();
std::shared_ptr<MacroAction> actionCopy;
action->WithLock([&action, &actionCopy]() {
actionCopy = action->Copy();
});
bool actionResult = actionCopy->PerformAction();
action->WithLock([&action, &actionCopy]() {
action->CopyTempVarValuesFrom(*actionCopy);
bool actionResult = false;
action->WithLock([&action, &actionResult]() {
actionResult = action->PerformAction();
});
actionsExecutedSuccessfully =
actionsExecutedSuccessfully && actionResult;
@@ -470,6 +415,11 @@ bool Macro::WasPausedSince(const TimePoint &time) const
return _lastUnpauseTime > time;
}
void Macro::SetMatchOnChange(bool onChange)
{
_performActionsOnChange = onChange;
}
void Macro::SetStopActionsIfNotDone(bool stopActionsIfNotDone)
{
_stopActionsIfNotDone = stopActionsIfNotDone;
@@ -790,8 +740,7 @@ bool Macro::Save(obs_data_t *obj, bool saveForCopy) const
obs_data_set_bool(obj, "pause", _paused);
obs_data_set_bool(obj, "parallel", _runInParallel);
obs_data_set_bool(obj, "checkConditionsInParallel", _checkInParallel);
obs_data_set_int(obj, "actionTriggerMode",
static_cast<int>(_actionTriggerMode));
obs_data_set_bool(obj, "onChange", _performActionsOnChange);
obs_data_set_bool(obj, "skipExecOnStart", _skipExecOnStart);
obs_data_set_bool(obj, "stopActionsIfNotDone", _stopActionsIfNotDone);
obs_data_set_bool(obj, "useShortCircuitEvaluation",
@@ -877,15 +826,7 @@ bool Macro::Load(obs_data_t *obj)
}
_runInParallel = obs_data_get_bool(obj, "parallel");
_checkInParallel = obs_data_get_bool(obj, "checkConditionsInParallel");
if (obs_data_has_user_value(obj, "onChange")) {
const bool onChange = obs_data_get_bool(obj, "onChange");
_actionTriggerMode =
onChange ? ActionTriggerMode::MACRO_RESULT_CHANGED
: ActionTriggerMode::ALWAYS;
} else {
_actionTriggerMode = static_cast<ActionTriggerMode>(
obs_data_get_int(obj, "actionTriggerMode"));
}
_performActionsOnChange = obs_data_get_bool(obj, "onChange");
_skipExecOnStart = obs_data_get_bool(obj, "skipExecOnStart");
_stopActionsIfNotDone = obs_data_get_bool(obj, "stopActionsIfNotDone");
_useShortCircuitEvaluation =
@@ -1045,8 +986,18 @@ bool Macro::HasValidSplitterPositions() const
!_elseActionSplitterPosition.empty();
}
bool Macro::OnChangePreventedActionsRecently()
{
if (_onPreventedActionExecution) {
_onPreventedActionExecution = false;
return _matched ? _actions.size() > 0 : _elseActions.size() > 0;
}
return false;
}
void Macro::ResetUIHelpers()
{
_onPreventedActionExecution = false;
for (auto c : _conditions) {
c->GetHighlightAndReset();
}
@@ -1145,11 +1096,9 @@ void Macro::ClearHotkeys() const
void setHotkeyDescriptionHelper(const char *formatModuleText,
const std::string name, const obs_hotkey_id id)
{
#ifndef UNIT_TEST
QString format{obs_module_text(formatModuleText)};
QString hotkeyDesc = format.arg(QString::fromStdString(name));
obs_hotkey_set_description(id, hotkeyDesc.toStdString().c_str());
#endif // !UNIT_TEST
}
void Macro::SetHotkeysDesc() const

View File

@@ -26,16 +26,6 @@ class Macro {
public:
enum class PauseStateSaveBehavior { PERSIST, PAUSE, UNPAUSE };
enum class ActionTriggerMode {
// Trigger always
ALWAYS,
// Trigger when macro match result flips
MACRO_RESULT_CHANGED,
// Trigger when any individual condition changes
ANY_CONDITION_CHANGED,
// Trigger when any individual condition evaluates to true
ANY_CONDITION_TRIGGERED,
};
Macro(const std::string &name = "");
Macro(const std::string &name, const GlobalMacroSettings &settings);
@@ -64,8 +54,8 @@ public:
bool GetStop() const { return _stop; }
void ResetTimers();
void SetActionTriggerMode(ActionTriggerMode);
ActionTriggerMode GetActionTriggerMode() const;
void SetMatchOnChange(bool onChange);
bool MatchOnChange() const { return _performActionsOnChange; }
void SetSkipExecOnStart(bool skip) { _skipExecOnStart = skip; }
bool SkipExecOnStart() const { return _skipExecOnStart; }
@@ -147,8 +137,7 @@ public:
const QList<int> &GetElseActionSplitterPosition() const;
bool HasValidSplitterPositions() const;
bool WasExecutedSince(const TimePoint &) const;
bool ActionTriggerModePreventedActionsSince(const TimePoint &) const;
TimePoint GetLastExecutionTime() const;
bool OnChangePreventedActionsRecently();
void ResetUIHelpers();
// Hotkeys
@@ -179,7 +168,6 @@ private:
TimePoint _lastCheckTime{};
TimePoint _lastUnpauseTime{};
TimePoint _lastExecutionTime{};
TimePoint _lastActionRunModePreventTime{};
std::vector<std::thread> _helperThreads;
std::deque<std::shared_ptr<MacroCondition>> _conditions;
@@ -194,13 +182,14 @@ private:
bool _useShortCircuitEvaluation = false;
bool _useCustomConditionCheckInterval = false;
Duration _customConditionCheckInterval = 0.3;
bool _actionModeMatch = false;
bool _conditionSateChanged = false;
bool _runInParallel = false;
bool _checkInParallel = false;
bool _matched = false;
std::future<void> _conditionCheckFuture;
bool _lastMatched = false;
bool _performActionsOnChange = true;
bool _skipExecOnStart = false;
bool _stopActionsIfNotDone = false;
bool _paused = false;
@@ -210,14 +199,14 @@ private:
obs_hotkey_id _unpauseHotkey = OBS_INVALID_HOTKEY_ID;
obs_hotkey_id _togglePauseHotkey = OBS_INVALID_HOTKEY_ID;
ActionTriggerMode _actionTriggerMode =
ActionTriggerMode::MACRO_RESULT_CHANGED;
PauseStateSaveBehavior _pauseSaveBehavior =
PauseStateSaveBehavior::PERSIST;
MacroInputVariables _inputVariables;
// UI helpers
bool _onPreventedActionExecution = false;
QList<int> _actionConditionSplitterPosition;
QList<int> _elseActionSplitterPosition;

View File

@@ -152,7 +152,7 @@ bool SwitcherData::VersionChanged(obs_data_t *obj, std::string currentVersion)
if (!obs_data_has_user_value(obj, "version")) {
return false;
}
switcher->firstBoot = false;
std::string previousVersion = obs_data_get_string(obj, "version");
return previousVersion != currentVersion;
}

View File

@@ -94,6 +94,7 @@ public:
bool stop = false;
std::condition_variable cv;
bool firstBoot = true;
bool transitionActive = false;
bool sceneCollectionStop = false;
bool obsIsShuttingDown = false;

View File

@@ -1,7 +1,5 @@
#include "auto-update-tooltip-label.hpp"
#include <QToolTip>
namespace advss {
AutoUpdateHelpIcon::AutoUpdateHelpIcon(
@@ -18,7 +16,6 @@ AutoUpdateHelpIcon::AutoUpdateHelpIcon(
void AutoUpdateHelpIcon::enterEvent(QEnterEvent *event)
{
UpdateTooltip();
_timer->start(_updateIntervalMs);
QLabel::enterEvent(event);
}
@@ -31,12 +28,7 @@ void AutoUpdateHelpIcon::leaveEvent(QEvent *event)
void AutoUpdateHelpIcon::UpdateTooltip()
{
if (!underMouse()) {
return;
}
const QString text = _callback();
QToolTip::showText(QCursor::pos(), text, this);
setToolTip(_callback());
}
} // namespace advss

View File

@@ -8,18 +8,13 @@
#include <obs-frontend-api.h>
#include <obs-module.h>
#include <util/config-file.h>
#include <QFileDialog>
#include <QMainWindow>
#include <QTextStream>
#include <QTimer>
#include <thread>
#include <util/config-file.h>
namespace advss {
static void showBackupDialogs(const QString &json)
void AskForBackup(const QString &json)
{
const bool backupWasConfirmed = DisplayMessage(
obs_module_text("AdvSceneSwitcher.askBackup"), true, false);
@@ -47,27 +42,6 @@ static void showBackupDialogs(const QString &json)
out << json;
}
void AskForBackup(obs_data_t *settings)
{
// This function is called while the plugin settings are being loaded.
// Blocking at this stage can cause issues such as OBS failing to start
// or crashing.
// Therefore, we ask the user whether they want to back up the settings
// asynchronously.
auto json = obs_data_get_json(settings);
static QString jsonQString = json ? json : "";
static const auto askForBackupWrapper = [](void *) {
showBackupDialogs(jsonQString);
};
AddFinishedLoadingStep([]() {
obs_queue_task(OBS_TASK_UI, askForBackupWrapper, nullptr,
false);
});
}
void BackupSettingsOfCurrentVersion()
{
auto sceneCollectionName = obs_frontend_get_current_scene_collection();

View File

@@ -1,9 +1,9 @@
#pragma once
#include <obs-data.h>
#include <QString>
namespace advss {
void AskForBackup(obs_data_t *settings);
void AskForBackup(const QString &json);
void BackupSettingsOfCurrentVersion();
} // namespace advss

View File

@@ -2,18 +2,15 @@
#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 <QVBoxLayout>
#include <thread>
namespace advss {
@@ -26,29 +23,10 @@ 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;
}();
@@ -126,44 +104,10 @@ static bool wasUncleanShutdown()
return true;
}
static void askForStartupSkip()
static bool askForStartupSkip()
{
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();
}
return DisplayMessage(obs_module_text("AdvSceneSwitcher.crashDetected"),
true, false);
}
bool ShouldSkipPluginStartOnUncleanShutdown()
@@ -172,22 +116,18 @@ bool ShouldSkipPluginStartOnUncleanShutdown()
return false;
}
if (suppressCrashDialog) {
return false;
}
// This function is called while the plugin settings are being loaded.
// Blocking at this stage can cause issues such as OBS failing to start
// or crashing.
// Therefore, we ask the user whether they want to start the plugin
// asynchronously.
static const auto showDialogWrapper = [](void *) {
askForStartupSkip();
};
AddFinishedLoadingStep([]() {
obs_queue_task(OBS_TASK_UI, showDialogWrapper, nullptr, false);
std::thread t([]() {
obs_queue_task(
OBS_TASK_UI,
[](void *) {
const bool skipStart = askForStartupSkip();
if (!skipStart) {
StartPlugin();
}
},
nullptr, false);
});
t.detach();
return true;
}

View File

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

View File

@@ -1,5 +1,12 @@
#pragma once
#ifdef UNIT_TEST
#define EXPORT
#define ADVSS_EXPORT
#else
#ifdef _MSC_VER
#define EXPORT __declspec(dllexport)
#else
@@ -12,3 +19,5 @@
#else
#define ADVSS_EXPORT Q_DECL_IMPORT
#endif
#endif // UNIT_TEST

View File

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

View File

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

View File

@@ -1,465 +0,0 @@
#include "first-run-wizard.hpp"
#include "log-helper.hpp"
#include "macro.hpp"
#include "macro-action-factory.hpp"
#include "macro-condition-factory.hpp"
#include "macro-settings.hpp"
#include "platform-funcs.hpp"
#include "selection-helpers.hpp"
#include <obs-frontend-api.h>
#include <obs-data.h>
#include <util/config-file.h>
#include <QFrame>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QRegularExpression>
#include <QVBoxLayout>
namespace advss {
static constexpr char kConditionIdWindow[] = "window";
static constexpr char kActionIdSceneSwitch[] = "scene_switch";
// ---------------------------------------------------------------------------
// OBS global config helpers
// ---------------------------------------------------------------------------
static constexpr char kConfigSection[] = "AdvancedSceneSwitcher";
static constexpr char kFirstRunKey[] = "firstRun";
bool IsFirstRun()
{
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(31, 0, 0)
config_t *cfg = obs_frontend_get_user_config();
#else
config_t *cfg = obs_frontend_get_global_config();
#endif
if (!config_has_user_value(cfg, kConfigSection, kFirstRunKey)) {
return true;
}
return config_get_bool(cfg, kConfigSection, kFirstRunKey);
}
static void WriteFirstRun(bool value)
{
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(31, 0, 0)
config_t *cfg = obs_frontend_get_user_config();
#else
config_t *cfg = obs_frontend_get_global_config();
#endif
config_set_bool(cfg, kConfigSection, kFirstRunKey, value);
config_save_safe(cfg, "tmp", nullptr);
}
static QString DetectFocusedWindow()
{
std::string title;
GetCurrentWindowTitle(title);
return QString::fromStdString(title);
}
// ===========================================================================
// WelcomePage
// ===========================================================================
WelcomePage::WelcomePage(QWidget *parent) : QWizardPage(parent)
{
setTitle(obs_module_text("FirstRunWizard.welcome.title"));
setSubTitle(obs_module_text("FirstRunWizard.welcome.subtitle"));
auto body = new QLabel(obs_module_text("FirstRunWizard.welcome.body"),
this);
body->setWordWrap(true);
body->setTextFormat(Qt::RichText);
auto layout = new QVBoxLayout(this);
layout->addWidget(body);
layout->addStretch();
}
// ===========================================================================
// SceneSelectionPage
// ===========================================================================
SceneSelectionPage::SceneSelectionPage(QWidget *parent) : QWizardPage(parent)
{
setTitle(obs_module_text("FirstRunWizard.scene.title"));
setSubTitle(obs_module_text("FirstRunWizard.scene.subtitle"));
auto label =
new QLabel(obs_module_text("FirstRunWizard.scene.label"), this);
_sceneCombo = new QComboBox(this);
_sceneCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
// registerField with * suffix means the field is mandatory for Next
registerField("targetScene*", _sceneCombo, "currentText",
SIGNAL(currentTextChanged(QString)));
connect(_sceneCombo, &QComboBox::currentTextChanged, this,
&QWizardPage::completeChanged);
auto row = new QHBoxLayout;
row->addWidget(label);
row->addWidget(_sceneCombo, 1);
auto layout = new QVBoxLayout(this);
layout->addLayout(row);
layout->addStretch();
}
void SceneSelectionPage::initializePage()
{
_sceneCombo->clear();
for (const QString &name : GetSceneNames())
_sceneCombo->addItem(name);
}
bool SceneSelectionPage::isComplete() const
{
return _sceneCombo->count() > 0 &&
!_sceneCombo->currentText().isEmpty();
}
// ===========================================================================
// WindowConditionPage
// ===========================================================================
WindowConditionPage::WindowConditionPage(QWidget *parent)
: QWizardPage(parent),
_detectTimer(new QTimer(this))
{
setTitle(obs_module_text("FirstRunWizard.window.title"));
setSubTitle(obs_module_text("FirstRunWizard.window.subtitle"));
auto label = new QLabel(obs_module_text("FirstRunWizard.window.label"),
this);
_windowEdit = new QLineEdit(this);
_windowEdit->setPlaceholderText(
obs_module_text("FirstRunWizard.window.placeholder"));
_autoDetect = new QPushButton(
obs_module_text("FirstRunWizard.window.autoDetect"), this);
_autoDetect->setToolTip(
obs_module_text("FirstRunWizard.window.autoDetectTooltip"));
registerField("windowTitle*", _windowEdit);
connect(_windowEdit, &QLineEdit::textChanged, this,
&QWizardPage::completeChanged);
connect(_autoDetect, &QPushButton::clicked, this,
&WindowConditionPage::onAutoDetectClicked);
connect(_detectTimer, &QTimer::timeout, this,
&WindowConditionPage::onCountdownTick);
auto row = new QHBoxLayout;
row->addWidget(label);
row->addWidget(_windowEdit, 1);
auto hint =
new QLabel(obs_module_text("FirstRunWizard.window.hint"), this);
hint->setTextFormat(Qt::RichText);
hint->setWordWrap(true);
auto layout = new QVBoxLayout(this);
layout->addLayout(row);
layout->addWidget(_autoDetect, 0, Qt::AlignLeft);
layout->addWidget(hint);
layout->addStretch();
}
void WindowConditionPage::initializePage()
{
if (_windowEdit->text().isEmpty()) {
QString detected = DetectFocusedWindow();
if (!detected.isEmpty()) {
_windowEdit->setText(detected);
}
}
}
bool WindowConditionPage::isComplete() const
{
return !_windowEdit->text().trimmed().isEmpty();
}
void WindowConditionPage::onAutoDetectClicked()
{
_countdown = 3;
_autoDetect->setEnabled(false);
_autoDetect->setText(
QString(obs_module_text(
"FirstRunWizard.window.autoDetectCountdown"))
.arg(_countdown));
_detectTimer->start(1000);
}
void WindowConditionPage::onCountdownTick()
{
--_countdown;
if (_countdown > 0) {
_autoDetect->setText(
QString(obs_module_text(
"FirstRunWizard.window.autoDetectCountdown"))
.arg(_countdown));
return;
}
_detectTimer->stop();
QString title = DetectFocusedWindow();
if (!title.isEmpty()) {
_windowEdit->setText(title);
}
_autoDetect->setEnabled(true);
_autoDetect->setText(
obs_module_text("FirstRunWizard.window.autoDetect"));
}
// ===========================================================================
// ReviewPage
// ===========================================================================
ReviewPage::ReviewPage(QWidget *parent, std::shared_ptr<Macro> &macro)
: QWizardPage(parent),
_macro(macro)
{
setTitle(obs_module_text("FirstRunWizard.review.title"));
setSubTitle(obs_module_text("FirstRunWizard.review.subtitle"));
_summary = new QLabel(this);
_summary->setWordWrap(true);
_summary->setTextFormat(Qt::RichText);
_summary->setFrameShape(QFrame::StyledPanel);
_summary->setContentsMargins(12, 12, 12, 12);
auto layout = new QVBoxLayout(this);
layout->addWidget(_summary);
layout->addStretch();
}
void ReviewPage::initializePage()
{
const QString scene = field("targetScene").toString();
const QString window = field("windowTitle").toString();
_summary->setText(
QString(obs_module_text("FirstRunWizard.review.summary"))
.arg(scene.toHtmlEscaped(), window.toHtmlEscaped()));
}
static QString escapeForRegex(const QString &input)
{
return QRegularExpression::escape(input);
}
bool ReviewPage::validatePage()
{
const QString scene = field("targetScene").toString();
const QString window = escapeForRegex(field("windowTitle").toString());
const std::string name = ("Window -> " + scene).toStdString();
// Build condition data blob
// ---------------------------------------------------------------
// Condition blob — mirrors MacroConditionWindow::Save() output:
//
// {
// "segmentSettings": { "enabled": true, "version": 1 },
// "id": "window",
// "checkTitle": true,
// "window": "<user input>",
// "windowRegexConfig": {
// "enable": true, // use regex-style partial matching
// "partial": true, // match anywhere in the title
// "options": 3 // case-insensitive (QRegularExpression flags)
// },
// "focus": true, // only trigger when window is focused
// "version": 1
// }
// ---------------------------------------------------------------
OBSDataAutoRelease condSegment = obs_data_create();
obs_data_set_bool(condSegment, "enabled", true);
obs_data_set_int(condSegment, "version", 1);
OBSDataAutoRelease condRegex = obs_data_create();
obs_data_set_bool(condRegex, "enable", true);
obs_data_set_bool(condRegex, "partial", true);
obs_data_set_int(condRegex, "options", 3); // CaseInsensitiveOption
OBSDataAutoRelease condData = obs_data_create();
obs_data_set_obj(condData, "segmentSettings", condSegment);
obs_data_set_string(condData, "id", "window");
obs_data_set_bool(condData, "checkTitle", true);
obs_data_set_string(condData, "window", window.toUtf8().constData());
obs_data_set_obj(condData, "windowRegexConfig", condRegex);
obs_data_set_bool(condData, "focus", true);
obs_data_set_int(condData, "version", 1);
// Build action data blob
// ---------------------------------------------------------------
// Action blob — mirrors MacroActionSwitchScene::Save() output:
//
// {
// "segmentSettings": { "enabled": true, "version": 1 },
// "id": "scene_switch",
// "action": 0, // 0 = switch scene
// "sceneSelection": {
// "type": 0, // 0 = scene by name
// "name": "<scene>",
// "canvasSelection": "Main"
// },
// "transitionType": 1, // 1 = use scene's default transition
// "blockUntilTransitionDone": false,
// "sceneType": 0
// }
// ---------------------------------------------------------------
OBSDataAutoRelease actionSegment = obs_data_create();
obs_data_set_bool(actionSegment, "enabled", true);
obs_data_set_int(actionSegment, "version", 1);
OBSDataAutoRelease sceneSelection = obs_data_create();
obs_data_set_int(sceneSelection, "type", 0);
obs_data_set_string(sceneSelection, "name", scene.toUtf8().constData());
obs_data_set_string(sceneSelection, "canvasSelection", "Main");
OBSDataAutoRelease actionData = obs_data_create();
obs_data_set_obj(actionData, "segmentSettings", actionSegment);
obs_data_set_string(actionData, "id", "scene_switch");
obs_data_set_int(actionData, "action", 0);
obs_data_set_obj(actionData, "sceneSelection", sceneSelection);
obs_data_set_int(actionData, "transitionType", 1);
obs_data_set_bool(actionData, "blockUntilTransitionDone", false);
obs_data_set_int(actionData, "sceneType", 0);
if (!FirstRunWizard::CreateMacro(_macro, name, kConditionIdWindow,
condData, kActionIdSceneSwitch,
actionData)) {
QMessageBox::warning(
this,
obs_module_text("FirstRunWizard.review.errorTitle"),
QString(obs_module_text(
"FirstRunWizard.review.errorBody"))
.arg(window, scene));
_macro.reset();
// Still advance so the user is not stuck.
}
return true;
}
// ===========================================================================
// DonePage
// ===========================================================================
DonePage::DonePage(QWidget *parent) : QWizardPage(parent)
{
setTitle(obs_module_text("FirstRunWizard.done.title"));
setSubTitle(obs_module_text("FirstRunWizard.done.subtitle"));
auto body =
new QLabel(obs_module_text("FirstRunWizard.done.body"), this);
body->setWordWrap(true);
body->setTextFormat(Qt::RichText);
body->setOpenExternalLinks(true);
auto layout = new QVBoxLayout(this);
layout->addWidget(body);
layout->addStretch();
}
// ===========================================================================
// FirstRunWizard
// ===========================================================================
FirstRunWizard::FirstRunWizard(QWidget *parent) : QWizard(parent)
{
setWindowTitle(obs_module_text("FirstRunWizard.windowTitle"));
setWizardStyle(QWizard::ModernStyle);
setMinimumSize(540, 420);
setPage(PAGE_WELCOME, new WelcomePage(this));
setPage(PAGE_SCENE, new SceneSelectionPage(this));
setPage(PAGE_WINDOW, new WindowConditionPage(this));
setPage(PAGE_REVIEW, new ReviewPage(this, _macro));
setPage(PAGE_DONE, new DonePage(this));
setStartId(PAGE_WELCOME);
setOption(QWizard::NoBackButtonOnLastPage, true);
setOption(QWizard::NoCancelButtonOnLastPage, true);
// Mark done on both Accept (Finish) and Reject (Cancel / close)
connect(this, &QWizard::accepted, this,
&FirstRunWizard::markFirstRunComplete);
connect(this, &QWizard::rejected, this,
&FirstRunWizard::markFirstRunComplete);
}
void FirstRunWizard::markFirstRunComplete()
{
WriteFirstRun(false);
}
// static
std::shared_ptr<Macro> FirstRunWizard::ShowWizard(QWidget *parent)
{
auto wizard = new FirstRunWizard(parent);
wizard->exec();
wizard->deleteLater();
return wizard->_macro;
}
// static
bool FirstRunWizard::CreateMacro(std::shared_ptr<Macro> &macro,
const std::string &macroName,
const std::string &conditionId,
obs_data_t *conditionData,
const std::string &actionId,
obs_data_t *actionData)
{
// 1. Create and register the Macro
macro = std::make_shared<Macro>(macroName, GetGlobalMacroSettings());
if (!macro) {
blog(LOG_WARNING, "FirstRunWizard: Macro allocation failed");
return false;
}
// 2. Instantiate condition via factory, then hydrate via Load()
auto condition =
MacroConditionFactory::Create(conditionId, macro.get());
if (!condition) {
blog(LOG_WARNING,
"FirstRunWizard: condition factory returned null "
"for id '%s' — is the base plugin loaded?",
conditionId.c_str());
return false;
}
if (!condition->Load(conditionData)) {
blog(LOG_WARNING,
"FirstRunWizard: condition Load() failed for id '%s'",
conditionId.c_str());
return false;
}
macro->Conditions().emplace_back(condition);
// 3. Instantiate action via factory, then hydrate via Load()
auto action = MacroActionFactory::Create(actionId, macro.get());
if (!action) {
blog(LOG_WARNING,
"FirstRunWizard: action factory returned null "
"for id '%s' — is the base plugin loaded?",
actionId.c_str());
return false;
}
if (!action->Load(actionData)) {
blog(LOG_WARNING,
"FirstRunWizard: action Load() failed for id '%s'",
actionId.c_str());
return false;
}
macro->Actions().emplace_back(action);
blog(LOG_INFO, "FirstRunWizard: created macro '%s'", macroName.c_str());
return true;
}
} // namespace advss

View File

@@ -1,128 +0,0 @@
#pragma once
#include <obs-data.h>
#include <QComboBox>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QTimer>
#include <QWizard>
#include <QWizardPage>
#include <string>
namespace advss {
class Macro;
bool IsFirstRun();
// ---------------------------------------------------------------------------
// Page IDs
// ---------------------------------------------------------------------------
enum WizardPageId {
PAGE_WELCOME = 0,
PAGE_SCENE,
PAGE_WINDOW,
PAGE_REVIEW,
PAGE_DONE,
};
// ---------------------------------------------------------------------------
// WelcomePage
// ---------------------------------------------------------------------------
class WelcomePage : public QWizardPage {
Q_OBJECT
public:
explicit WelcomePage(QWidget *parent = nullptr);
int nextId() const override { return PAGE_SCENE; }
};
// ---------------------------------------------------------------------------
// SceneSelectionPage
// Registers wizard field "targetScene" (QString).
// ---------------------------------------------------------------------------
class SceneSelectionPage : public QWizardPage {
Q_OBJECT
public:
explicit SceneSelectionPage(QWidget *parent = nullptr);
void initializePage() override;
bool isComplete() const override;
int nextId() const override { return PAGE_WINDOW; }
private:
QComboBox *_sceneCombo;
};
// ---------------------------------------------------------------------------
// WindowConditionPage
// Registers wizard field "windowTitle" (QString).
// Auto-detect button samples the focused window after a countdown.
// ---------------------------------------------------------------------------
class WindowConditionPage : public QWizardPage {
Q_OBJECT
public:
explicit WindowConditionPage(QWidget *parent = nullptr);
void initializePage() override;
bool isComplete() const override;
int nextId() const override { return PAGE_REVIEW; }
private slots:
void onAutoDetectClicked();
void onCountdownTick();
private:
QLineEdit *_windowEdit;
QPushButton *_autoDetect;
QTimer *_detectTimer;
int _countdown = 3;
};
// ---------------------------------------------------------------------------
// ReviewPage
// Displays a summary and calls FirstRunWizard::CreateMacro() on Finish.
// ---------------------------------------------------------------------------
class ReviewPage : public QWizardPage {
Q_OBJECT
public:
explicit ReviewPage(QWidget *parent, std::shared_ptr<Macro> &macro);
void initializePage() override;
bool validatePage() override;
int nextId() const override { return PAGE_DONE; }
private:
QLabel *_summary;
std::shared_ptr<Macro> &_macro;
};
// ---------------------------------------------------------------------------
// DonePage
// ---------------------------------------------------------------------------
class DonePage : public QWizardPage {
Q_OBJECT
public:
explicit DonePage(QWidget *parent = nullptr);
int nextId() const override { return -1; }
};
// ---------------------------------------------------------------------------
// FirstRunWizard
// ---------------------------------------------------------------------------
class FirstRunWizard : public QWizard {
Q_OBJECT
public:
explicit FirstRunWizard(QWidget *parent = nullptr);
static std::shared_ptr<Macro> ShowWizard(QWidget *parent);
static bool
CreateMacro(std::shared_ptr<Macro> &macro, const std::string &macroName,
const std::string &conditionId, obs_data_t *conditionData,
const std::string &actionId, obs_data_t *actionData);
private:
void markFirstRunComplete();
std::shared_ptr<Macro> _macro;
};
} // namespace advss

View File

@@ -4,11 +4,9 @@
#include "ui-helpers.hpp"
#include <algorithm>
#include <QAction>
#include <QLayout>
#include <QMenu>
#include <QTimer>
#include <QLayout>
Q_DECLARE_METATYPE(advss::Item *);
@@ -332,13 +330,6 @@ void ItemSettingsDialog::NameChanged(const QString &text)
SetNameWarning("");
}
void ItemSettingsDialog::showEvent(QShowEvent *)
{
if (_showNameEmptyWarning && _name->text().isEmpty()) {
_name->setFocus(Qt::OtherFocusReason);
}
}
void ItemSettingsDialog::SetNameWarning(const QString warn)
{
if (warn.isEmpty()) {

View File

@@ -52,7 +52,6 @@ private slots:
void NameChanged(const QString &);
protected:
virtual void showEvent(QShowEvent *) override;
void SetNameWarning(const QString);
QLineEdit *_name;

View File

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

View File

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

View File

@@ -1,16 +1,13 @@
#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()),
_placeholder(new QLabel(_list->viewport()))
_mainLayout(new QVBoxLayout())
{
QWidget::connect(_controls, SIGNAL(Add()), this, SLOT(Add()));
QWidget::connect(_controls, SIGNAL(Remove()), this, SLOT(Remove()));
@@ -18,19 +15,6 @@ 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);
@@ -38,40 +22,6 @@ 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);
@@ -114,24 +64,6 @@ int ListEditor::GetIndexOfSignal() const
void ListEditor::UpdateListSize()
{
SetHeightToContentHeight(_list);
if (_list->count() == 0 && !_placeholder->text().isEmpty()) {
auto height = _list->fontMetrics().height() * 3;
_list->setMinimumHeight(height);
_list->setMaximumHeight(height);
}
if (_minHeight >= 0 && _list->minimumHeight() != _minHeight) {
_list->setMinimumHeight(_minHeight);
}
if (_maxHeight >= 0 && _list->maximumHeight() != _maxHeight) {
if (_list->minimumHeight() > _maxHeight) {
_list->setMinimumHeight(_maxHeight);
}
_list->setMaximumHeight(_maxHeight);
}
adjustSize();
updateGeometry();
}

View File

@@ -2,7 +2,6 @@
#include "export-symbol-helper.hpp"
#include "list-controls.hpp"
#include <QLabel>
#include <QListWidget>
#include <QLayout>
@@ -14,13 +13,9 @@ 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;
@@ -28,7 +23,6 @@ private slots:
virtual void Up(){};
virtual void Down(){};
virtual void Clicked(QListWidgetItem *) {}
void UpdatePlaceholder();
protected:
void UpdateListSize();
@@ -37,11 +31,6 @@ protected:
QListWidget *_list;
ListControls *_controls;
QVBoxLayout *_mainLayout;
private:
QLabel *_placeholder;
int _minHeight = -1;
int _maxHeight = -1;
};
} // namespace advss

View File

@@ -1,5 +1,7 @@
#pragma once
#ifndef UNIT_TEST
#include <util/base.h>
#endif
namespace advss {
@@ -7,7 +9,6 @@ namespace advss {
#define blog(level, msg, ...)
#define vblog(level, msg, ...)
#define ablog(level, msg, ...)
#define mblog(level, msg, ...)
#else
// Print log with "[adv-ss] " prefix

View File

@@ -9,15 +9,19 @@ 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);
};
exprtk::symbol_table<double> symbolTable;
symbolTable.add_function("random", randomFunc);
if (!setupDone) {
symbolTable.add_function("random", randomFunc);
setupDone = true;
}
exprtk::expression<double> expression;
expression.register_symbol_table(symbolTable);

View File

@@ -1,12 +1,14 @@
#include "non-modal-dialog.hpp"
#include "obs-module-helper.hpp"
#include <atomic>
#include <mutex>
#include <obs-frontend-api.h>
#include <QDialogButtonBox>
#include <QLabel>
#include <QLayout>
#include <QMainWindow>
#include <QCoreApplication>
namespace advss {

View File

@@ -3,42 +3,12 @@
#include "macro-signals.hpp"
#include "switcher-data.hpp"
#include "obs-frontend-api.h"
namespace advss {
static std::mutex initMutex;
static std::mutex postLoadMutex;
static std::mutex finishLoadMutex;
static std::mutex mutex;
static bool setup();
static bool setupDonw = setup();
bool loadingFinished = false;
static std::vector<std::function<void()>> &getFinishLoadSteps();
static bool setup()
{
static auto handleEvent = [](enum obs_frontend_event event, void *) {
switch (event) {
case OBS_FRONTEND_EVENT_FINISHED_LOADING: {
std::lock_guard<std::mutex> lock(finishLoadMutex);
for (const auto &step : getFinishLoadSteps()) {
step();
}
getFinishLoadSteps().clear();
loadingFinished = true;
break;
}
default:
break;
};
};
obs_frontend_add_event_callback(handleEvent, nullptr);
return true;
}
static std::vector<std::function<void()>> &getPluginInitSteps()
{
static std::vector<std::function<void()>> steps;
@@ -93,12 +63,6 @@ static std::vector<std::function<void()>> &getPostLoadSteps()
return steps;
}
static std::vector<std::function<void()>> &getFinishLoadSteps()
{
static std::vector<std::function<void()>> steps;
return steps;
}
void SavePluginSettings(obs_data_t *obj)
{
GetSwitcher()->SaveSettings(obj);
@@ -172,7 +136,7 @@ void AddPluginInitStep(std::function<void()> step)
void AddPluginPostLoadStep(std::function<void()> step)
{
std::lock_guard<std::mutex> lock(mutex);
std::lock_guard<std::mutex> lock(initMutex);
getPluginPostLoadSteps().emplace_back(step);
}
@@ -214,16 +178,6 @@ void RunIntervalResetSteps()
}
}
void AddFinishedLoadingStep(std::function<void()> step)
{
std::lock_guard<std::mutex> lock(finishLoadMutex);
if (loadingFinished) {
return;
}
getFinishLoadSteps().emplace_back(step);
}
void AddStartStep(std::function<void()> step)
{
std::lock_guard<std::mutex> lock(mutex);

View File

@@ -34,9 +34,6 @@ void RunStartSteps();
void RunStopSteps();
void RunIntervalResetSteps();
// Steps are executed after OBS_FRONTEND_EVENT_FINISHED_LOADING is fired
EXPORT void AddFinishedLoadingStep(std::function<void()>);
enum class NoMatchBehavior { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 };
EXPORT void SetPluginNoMatchBehavior(NoMatchBehavior);
EXPORT NoMatchBehavior GetPluginNoMatchBehavior();

View File

@@ -57,6 +57,7 @@ ResourceTable::ResourceTable(QWidget *parent, const QString &help,
auto layout = new QVBoxLayout();
layout->addLayout(helpAndTableLayout);
layout->addLayout(controlLayout);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
QWidget::connect(_add, SIGNAL(clicked()), this, SLOT(Add()));

View File

@@ -37,8 +37,8 @@ void CenterSplitterPosition(QSplitter *splitter)
void SetSplitterPositionByFraction(QSplitter *splitter, double fraction)
{
int value1 = (int)((double)QWIDGETSIZE_MAX * fraction);
int value2 = (int)((double)QWIDGETSIZE_MAX * (1.0 - fraction));
int value1 = (double)QWIDGETSIZE_MAX * fraction;
int value2 = (double)QWIDGETSIZE_MAX * (1.0 - fraction);
splitter->setSizes(QList<int>() << value1 << value2);
}

View File

@@ -58,9 +58,6 @@ StringListEdit::StringListEdit(
_filterCallback(filter),
_preprocessCallback(preprocess)
{
if (_addString.isEmpty()) {
_addString = obs_module_text("AdvSceneSwitcher.windowTitle");
}
}
void StringListEdit::SetStringList(const StringList &list)

View File

@@ -10,8 +10,6 @@ namespace advss {
class StringList : public QList<StringVariable> {
public:
using QList<StringVariable>::QList;
EXPORT bool Save(obs_data_t *obj, const char *name,
const char *elementName = "string") const;
EXPORT bool Load(obs_data_t *obj, const char *name,

View File

@@ -2,22 +2,8 @@
namespace advss {
#ifdef UNIT_TEST
std::mutex *GetSwitcherMutex()
{
static std::mutex m;
return &m;
}
std::unique_lock<std::mutex> *GetSwitcherLoopLock()
{
static std::mutex m;
static std::unique_lock<std::mutex> lock(m);
return &lock;
}
#else
std::mutex *GetSwitcherMutex();
std::unique_lock<std::mutex> *GetSwitcherLoopLock();
#endif
std::mutex *GetMutex()
{

View File

@@ -2,6 +2,7 @@
#include "export-symbol-helper.hpp"
#include <functional>
#include <memory>
#include <mutex>
namespace advss {

View File

@@ -10,12 +10,9 @@
#include <QVariant>
#include <QAbstractItemView>
#include <atomic>
Q_DECLARE_METATYPE(advss::TempVariableRef);
static std::atomic<uint64_t> tempVarInUseGeneration{0};
namespace advss {
TempVariable::TempVariable(const std::string &id, const std::string &name,
@@ -123,60 +120,6 @@ TempVariableRef TempVariable::GetRef() const
return ref;
}
static bool refsContain(const std::vector<TempVariableRef> &refs,
const TempVariableRef &ref)
{
for (const auto &r : refs) {
if (r == ref) {
return true;
}
}
return false;
}
template<typename T>
static bool segmentsReferTo(const T &segments, const TempVariableRef &ref)
{
for (const auto &segment : segments) {
if (refsContain(segment->GetTempVarRefs(), ref)) {
return true;
}
}
return false;
}
bool TempVariable::IsInUse() const
{
const auto currentGen =
tempVarInUseGeneration.load(std::memory_order_relaxed);
if (_isInUseCacheGeneration == currentGen) {
return _isInUseCache;
}
bool inUse = false;
const auto ref = GetRef();
if (ref.HasValidID()) {
for (const auto &macro : GetAllMacros()) {
if (segmentsReferTo(macro->Conditions(), ref) ||
segmentsReferTo(macro->Actions(), ref) ||
segmentsReferTo(macro->ElseActions(), ref)) {
inUse = true;
break;
}
}
}
_isInUseCache = inUse;
_isInUseCacheGeneration = currentGen;
return inUse;
}
void IncrementTempVarInUseGeneration()
{
tempVarInUseGeneration.fetch_add(1, std::memory_order_relaxed);
}
TempVariableRef::SegmentType TempVariableRef::GetType() const
{
auto segment = _segment.lock();
@@ -722,13 +665,13 @@ TempVarSignalManager *TempVarSignalManager::Instance()
void NotifyUIAboutTempVarChange(MacroSegment *segment)
{
IncrementTempVarInUseGeneration();
QueueUITask(
obs_queue_task(
OBS_TASK_UI,
[](void *segment) {
TempVarSignalManager::Instance()->SegmentTempVarsChanged(
(MacroSegment *)segment);
},
segment);
segment, false);
}
} // namespace advss

View File

@@ -44,7 +44,6 @@ public:
void SetValue(const std::string &val);
void InvalidateValue();
TempVariableRef GetRef() const;
EXPORT bool IsInUse() const;
private:
std::string _id = "";
@@ -54,8 +53,6 @@ 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;
@@ -125,6 +122,5 @@ private:
};
void NotifyUIAboutTempVarChange(MacroSegment *);
EXPORT void IncrementTempVarInUseGeneration();
} // namespace advss

View File

@@ -81,13 +81,13 @@ void GenericVariableSpinbox::DisableVariableSelection()
void GenericVariableSpinbox::setMinimum(double value)
{
_fixedValueInt->setMinimum((int)value);
_fixedValueInt->setMinimum(value);
_fixedValueDouble->setMinimum(value);
}
void GenericVariableSpinbox::setMaximum(double value)
{
_fixedValueInt->setMaximum((int)value);
_fixedValueInt->setMaximum(value);
_fixedValueDouble->setMaximum(value);
}

View File

@@ -9,12 +9,11 @@ void StringVariable::Resolve() const
_resolvedValue = _value;
return;
}
const auto lastChange = GetLastVariableChangeTime();
if (_lastResolve == lastChange) {
if (_lastResolve == GetLastVariableChangeTime()) {
return;
}
_resolvedValue = SubstitueVariables(_value);
_lastResolve = lastChange;
_lastResolve = GetLastVariableChangeTime();
}
StringVariable::operator std::string() const
@@ -82,7 +81,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->MarkAsUsed();
variable->UpdateLastUsed();
}
}
return str;

View File

@@ -12,23 +12,16 @@ 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()
{
setLastVariableChangeTime();
lastVariableChange = std::chrono::high_resolution_clock::now();
}
Variable::~Variable()
{
setLastVariableChangeTime();
lastVariableChange = std::chrono::high_resolution_clock::now();
}
void Variable::Load(obs_data_t *obj)
@@ -44,7 +37,7 @@ void Variable::Load(obs_data_t *obj)
SetValue(_defaultValue);
}
setLastVariableChangeTime();
lastVariableChange = std::chrono::high_resolution_clock::now();
}
void Variable::Save(obs_data_t *obj) const
@@ -52,11 +45,8 @@ void Variable::Save(obs_data_t *obj) const
Item::Save(obj);
obs_data_set_int(obj, "saveAction", static_cast<int>(_saveAction));
{
std::lock_guard<std::mutex> lock(_mutex);
if (_saveAction == SaveAction::SAVE) {
obs_data_set_string(obj, "value", _value.c_str());
}
if (_saveAction == SaveAction::SAVE) {
obs_data_set_string(obj, "value", _value.c_str());
}
obs_data_set_string(obj, "defaultValue", _defaultValue.c_str());
@@ -72,18 +62,6 @@ 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());
@@ -101,11 +79,8 @@ void Variable::SetValue(const std::string &value)
_value = value;
UpdateLastUsed();
if (_previousValue != _value) {
_lastChanged = std::chrono::high_resolution_clock::now();
++_valueChangeCount;
}
setLastVariableChangeTime();
UpdateLastChanged();
lastVariableChange = std::chrono::high_resolution_clock::now();
}
void Variable::SetValue(double value)
@@ -115,7 +90,6 @@ 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 {};
}
@@ -127,7 +101,6 @@ 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 {};
}
@@ -143,10 +116,12 @@ void Variable::UpdateLastUsed() const
_lastUsed = std::chrono::high_resolution_clock::now();
}
void Variable::MarkAsUsed() const
void Variable::UpdateLastChanged()
{
std::lock_guard<std::mutex> lock(_mutex);
UpdateLastUsed();
if (_previousValue != _value) {
_lastChanged = std::chrono::high_resolution_clock::now();
++_valueChangeCount;
}
}
static void populateSaveActionSelection(QComboBox *list)
@@ -231,7 +206,7 @@ bool VariableSettingsDialog::AskForSettings(QWidget *parent, Variable &settings)
dialog._defaultValue->toPlainText().toStdString();
settings._saveAction =
static_cast<Variable::SaveAction>(dialog._save->currentIndex());
setLastVariableChangeTime();
lastVariableChange = std::chrono::high_resolution_clock::now();
return true;
}
@@ -486,7 +461,6 @@ void ImportVariables(obs_data_t *data)
std::chrono::high_resolution_clock::time_point GetLastVariableChangeTime()
{
std::lock_guard<std::mutex> lock(lastVariableChangeMutex);
return lastVariableChange;
}

View File

@@ -34,15 +34,16 @@ 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;
std::string GetPreviousValue() const { return _previousValue; };
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;
int GetValueChangeCount() const { return _valueChangeCount; }
std::optional<uint64_t> GetSecondsSinceLastUse() const;
std::optional<uint64_t> GetSecondsSinceLastChange() const;
void MarkAsUsed() const;
void UpdateLastUsed() const;
void UpdateLastChanged();
private:
SaveAction _saveAction = SaveAction::DONT_SAVE;
@@ -54,8 +55,6 @@ private:
mutable std::chrono::high_resolution_clock::time_point _lastChanged;
mutable std::mutex _mutex;
void UpdateLastUsed() const;
friend VariableSelection;
friend VariableSettingsDialog;
};

View File

@@ -25,8 +25,6 @@ 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
@@ -59,8 +57,6 @@ 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
@@ -148,24 +144,12 @@ target_sources(
target_sources(
${PROJECT_NAME}
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
PRIVATE utils/audio-helpers.cpp
utils/audio-helpers.hpp
utils/connection-manager.cpp
utils/connection-manager.hpp
utils/cursor-helpers.cpp
utils/cursor-helpers.hpp
utils/day-of-week-selector.cpp
utils/day-of-week-selector.hpp
utils/filter-selection.cpp
utils/filter-selection.hpp
utils/hotkey-helpers.cpp
@@ -182,8 +166,6 @@ target_sources(
utils/scene-item-selection.hpp
utils/scene-item-transform-helpers.cpp
utils/scene-item-transform-helpers.hpp
utils/transition-helpers.cpp
utils/transition-helpers.hpp
utils/source-properties-button.cpp
utils/source-properties-button.hpp
utils/source-settings-helpers.cpp

View File

@@ -144,7 +144,7 @@ void MacroActionFileEdit::UpdateEntryData()
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_filePath->SetPath(_entryData->_file);
_filePath->SetPath(QString::fromStdString(_entryData->_file));
_text->setPlainText(_entryData->_text);
adjustSize();

View File

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

View File

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

View File

@@ -1,443 +0,0 @@
#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);
waitForPlaybackToEnd(GetMacro(), source, maxMs);
deactivatePlayback(source, wantsOutput);
return true;
}
// Keep the source alive in a background thread that cleans up
// once playback finishes. Grab an extra strong reference so the
// source survives beyond this stack frame.
auto rawSource = obs_source_get_ref(source);
auto macro = GetMacro();
std::thread cleanupThread([rawSource, wantsOutput, macro, maxMs]() {
waitForPlaybackToEnd(macro, rawSource, maxMs);
deactivatePlayback(rawSource, wantsOutput);
obs_source_release(rawSource);
});
AddMacroHelperThread(macro, std::move(cleanupThread));
return true;
}
void MacroActionPlayAudio::LogAction() const
{
ablog(LOG_INFO,
"playing audio file \"%s\" at %.1f dB (monitoring type %d, wait %d)",
_filePath.UnresolvedValue().c_str(),
static_cast<double>(_volumeDB.GetFixedValue()), _monitorType,
(int)_waitForCompletion);
}
bool MacroActionPlayAudio::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
_filePath.Save(obj, "filePath");
_volumeDB.Save(obj, "volumeDB");
obs_data_set_int(obj, "monitorType", _monitorType);
obs_data_set_int(obj, "audioMixers", _audioMixers);
obs_data_set_bool(obj, "useStartOffset", _useStartOffset);
_startOffset.Save(obj, "startOffset");
obs_data_set_bool(obj, "useDuration", _useDuration);
_playbackDuration.Save(obj, "playbackDuration");
obs_data_set_bool(obj, "waitForCompletion", _waitForCompletion);
return true;
}
bool MacroActionPlayAudio::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_filePath.Load(obj, "filePath");
_volumeDB.Load(obj, "volumeDB");
_monitorType = static_cast<obs_monitoring_type>(
obs_data_get_int(obj, "monitorType"));
_audioMixers =
static_cast<uint32_t>(obs_data_get_int(obj, "audioMixers"));
_useStartOffset = obs_data_get_bool(obj, "useStartOffset");
_startOffset.Load(obj, "startOffset");
_useDuration = obs_data_get_bool(obj, "useDuration");
_playbackDuration.Load(obj, "playbackDuration");
_waitForCompletion = obs_data_get_bool(obj, "waitForCompletion");
return true;
}
std::string MacroActionPlayAudio::GetShortDesc() const
{
return _filePath.UnresolvedValue();
}
std::shared_ptr<MacroAction> MacroActionPlayAudio::Create(Macro *m)
{
return std::make_shared<MacroActionPlayAudio>(m);
}
std::shared_ptr<MacroAction> MacroActionPlayAudio::Copy() const
{
return std::make_shared<MacroActionPlayAudio>(*this);
}
void MacroActionPlayAudio::ResolveVariablesToFixedValues()
{
_filePath.ResolveVariables();
_volumeDB.ResolveVariables();
_startOffset.ResolveVariables();
_playbackDuration.ResolveVariables();
}
MacroActionPlayAudioEdit::MacroActionPlayAudioEdit(
QWidget *parent, std::shared_ptr<MacroActionPlayAudio> entryData)
: QWidget(parent),
_filePath(new FileSelection(
FileSelection::Type::READ, this,
obs_module_text(
"AdvSceneSwitcher.action.playAudio.file.browse"))),
_volumeDB(new VariableDoubleSpinBox),
_monitorTypes(new QComboBox),
_tracksContainer(new QWidget),
_useStartOffset(new QCheckBox(this)),
_startOffset(new DurationSelection(this, true, 0.0)),
_useDuration(new QCheckBox(this)),
_playbackDuration(new DurationSelection(this, true, 0.0)),
_waitForCompletion(new QCheckBox(
obs_module_text("AdvSceneSwitcher.action.playAudio.wait")))
{
_volumeDB->setMinimum(-100.0);
_volumeDB->setMaximum(0.0);
_volumeDB->setSuffix("dB");
_volumeDB->setSpecialValueText("-inf");
if (obs_audio_monitoring_available()) {
PopulateMonitorTypeSelection(_monitorTypes);
} else {
_monitorTypes->addItem(obs_module_text(
"AdvSceneSwitcher.action.playAudio.monitorUnavailable"));
_monitorTypes->setEnabled(false);
}
for (int i = 0; i < 6; ++i) {
_tracks[i] = new QCheckBox(QString::number(i + 1));
QWidget::connect(_tracks[i], SIGNAL(stateChanged(int)), this,
SLOT(TrackChanged()));
}
QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this,
SLOT(FilePathChanged(const QString &)));
QWidget::connect(
_volumeDB,
SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
this, SLOT(VolumeDBChanged(const NumberVariable<double> &)));
QWidget::connect(_monitorTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(MonitorTypeChanged(int)));
QWidget::connect(_useStartOffset, SIGNAL(stateChanged(int)), this,
SLOT(UseStartOffsetChanged(int)));
QWidget::connect(_useDuration, SIGNAL(stateChanged(int)), this,
SLOT(UseDurationChanged(int)));
QWidget::connect(_startOffset,
SIGNAL(DurationChanged(const Duration &)), this,
SLOT(StartOffsetChanged(const Duration &)));
QWidget::connect(_playbackDuration,
SIGNAL(DurationChanged(const Duration &)), this,
SLOT(PlaybackDurationChanged(const Duration &)));
QWidget::connect(_waitForCompletion, SIGNAL(stateChanged(int)), this,
SLOT(WaitChanged(int)));
auto tracksLayout = new QHBoxLayout;
tracksLayout->setContentsMargins(0, 0, 0, 0);
tracksLayout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.action.playAudio.tracks")));
for (auto *cb : _tracks) {
tracksLayout->addWidget(cb);
}
tracksLayout->addStretch();
_tracksContainer->setLayout(tracksLayout);
auto volumeLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.playAudio.layout.volume"),
volumeLayout, {{"{{volumeDB}}", _volumeDB}});
auto monitorLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.playAudio.layout.monitor"),
monitorLayout, {{"{{monitorTypes}}", _monitorTypes}});
auto startOffsetLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.playAudio.layout.startOffset"),
startOffsetLayout,
{{"{{useStartOffset}}", _useStartOffset},
{"{{startOffset}}", _startOffset}});
auto playbackDurationLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.playAudio.layout.playbackDuration"),
playbackDurationLayout,
{{"{{useDuration}}", _useDuration},
{"{{playbackDuration}}", _playbackDuration}});
auto mainLayout = new QVBoxLayout;
mainLayout->addWidget(_filePath);
mainLayout->addLayout(volumeLayout);
mainLayout->addLayout(monitorLayout);
mainLayout->addWidget(_tracksContainer);
mainLayout->addLayout(startOffsetLayout);
mainLayout->addLayout(playbackDurationLayout);
mainLayout->addWidget(_waitForCompletion);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionPlayAudioEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_filePath->SetPath(_entryData->_filePath);
_volumeDB->SetValue(_entryData->_volumeDB);
_monitorTypes->setCurrentIndex(
static_cast<int>(_entryData->_monitorType));
for (int i = 0; i < 6; ++i) {
_tracks[i]->setChecked(_entryData->_audioMixers & (1u << i));
}
_tracksContainer->setVisible(_entryData->_monitorType !=
OBS_MONITORING_TYPE_MONITOR_ONLY);
_useStartOffset->setChecked(_entryData->_useStartOffset);
_startOffset->SetDuration(_entryData->_startOffset);
_startOffset->setEnabled(_entryData->_useStartOffset);
_useDuration->setChecked(_entryData->_useDuration);
_playbackDuration->SetDuration(_entryData->_playbackDuration);
_playbackDuration->setEnabled(_entryData->_useDuration);
_waitForCompletion->setChecked(_entryData->_waitForCompletion);
}
void MacroActionPlayAudioEdit::FilePathChanged(const QString &path)
{
GUARD_LOADING_AND_LOCK();
_entryData->_filePath = path.toStdString();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionPlayAudioEdit::VolumeDBChanged(
const NumberVariable<double> &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_volumeDB = value;
}
void MacroActionPlayAudioEdit::MonitorTypeChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_monitorType = static_cast<obs_monitoring_type>(value);
_tracksContainer->setVisible(value != OBS_MONITORING_TYPE_MONITOR_ONLY);
}
void MacroActionPlayAudioEdit::TrackChanged()
{
GUARD_LOADING_AND_LOCK();
uint32_t mixers = 0;
for (int i = 0; i < 6; ++i) {
if (_tracks[i]->isChecked()) {
mixers |= (1u << i);
}
}
_entryData->_audioMixers = mixers;
}
void MacroActionPlayAudioEdit::UseStartOffsetChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_useStartOffset = value;
_startOffset->setEnabled(_entryData->_useStartOffset);
}
void MacroActionPlayAudioEdit::StartOffsetChanged(const Duration &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_startOffset = value;
}
void MacroActionPlayAudioEdit::UseDurationChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_useDuration = value;
_playbackDuration->setEnabled(_entryData->_useDuration);
}
void MacroActionPlayAudioEdit::PlaybackDurationChanged(const Duration &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_playbackDuration = value;
}
void MacroActionPlayAudioEdit::WaitChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_waitForCompletion = value;
}
} // namespace advss

View File

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

View File

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

View File

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

View File

@@ -22,18 +22,12 @@ 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()
@@ -93,13 +87,6 @@ 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;
}
@@ -123,7 +110,6 @@ 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;
}
@@ -133,7 +119,6 @@ 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;
}
@@ -151,7 +136,6 @@ void MacroActionRecord::ResolveVariablesToFixedValues()
{
_folder.ResolveVariables();
_fileFormat.ResolveVariables();
_chapterName.ResolveVariables();
}
static inline void populateActionSelection(QComboBox *list)
@@ -170,8 +154,7 @@ MacroActionRecordEdit::MacroActionRecordEdit(
_splitHint(new QLabel(obs_module_text(
"AdvSceneSwitcher.action.recording.split.hint"))),
_recordFolder(new FileSelection(FileSelection::Type::FOLDER, this)),
_recordFileFormat(new VariableLineEdit(this)),
_chapterName(new VariableLineEdit(this))
_recordFileFormat(new VariableLineEdit(this))
{
populateActionSelection(_actions);
@@ -181,8 +164,6 @@ 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"),
@@ -191,8 +172,7 @@ MacroActionRecordEdit::MacroActionRecordEdit(
{"{{pauseHint}}", _pauseHint},
{"{{splitHint}}", _splitHint},
{"{{recordFolder}}", _recordFolder},
{"{{recordFileFormat}}", _recordFileFormat},
{"{{chapterName}}", _chapterName}});
{"{{recordFileFormat}}", _recordFileFormat}});
setLayout(mainLayout);
_entryData = entryData;
@@ -208,7 +188,6 @@ void MacroActionRecordEdit::UpdateEntryData()
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_recordFolder->SetPath(_entryData->_folder);
_recordFileFormat->setText(_entryData->_fileFormat);
_chapterName->setText(_entryData->_chapterName);
SetWidgetVisibility();
}
@@ -230,12 +209,6 @@ 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));
@@ -245,8 +218,6 @@ void MacroActionRecordEdit::SetWidgetVisibility()
MacroActionRecord::Action::FOLDER);
_recordFileFormat->setVisible(_entryData->_action ==
MacroActionRecord::Action::FILE_FORMAT);
_chapterName->setVisible(_entryData->_action ==
MacroActionRecord::Action::ADD_CHAPTER);
}
void MacroActionRecordEdit::ActionChanged(int value)

View File

@@ -29,13 +29,11 @@ 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;
@@ -62,7 +60,6 @@ private slots:
void ActionChanged(int value);
void FolderChanged(const QString &);
void FormatStringChanged();
void ChapterNameChanged();
protected:
QComboBox *_actions;
@@ -70,7 +67,6 @@ protected:
QLabel *_splitHint;
FileSelection *_recordFolder;
VariableLineEdit *_recordFileFormat;
VariableLineEdit *_chapterName;
std::shared_ptr<MacroActionRecord> _entryData;
private:

View File

@@ -4,7 +4,6 @@
#include "plugin-state-helpers.hpp"
#include "scene-switch-helpers.hpp"
#include "source-helpers.hpp"
#include "transition-helpers.hpp"
#include <obs-frontend-api.h>
@@ -69,6 +68,14 @@ static int getTransitionOverrideDuration(OBSWeakSource &scene)
return duration;
}
static bool isUsingFixedLengthTransition(const OBSWeakSource &transition)
{
obs_source_t *source = obs_weak_source_get_source(transition);
bool ret = obs_transition_fixed(source);
obs_source_release(source);
return ret;
}
static OBSWeakSource getOverrideTransition(OBSWeakSource &scene)
{
OBSWeakSource transition;
@@ -94,13 +101,13 @@ static int getExpectedTransitionDuration(OBSWeakSource &scene,
auto overrideTransition = getOverrideTransition(scene);
if (overrideTransition) {
transition = overrideTransition;
if (!IsFixedLengthTransition(transition)) {
if (!isUsingFixedLengthTransition(transition)) {
return getTransitionOverrideDuration(scene);
}
}
}
if (IsFixedLengthTransition(transition)) {
if (isUsingFixedLengthTransition(transition)) {
return -1; // no API is available to access the fixed duration
}
if (duration != 0) {
@@ -496,7 +503,7 @@ shouldShowDurationInTransitionLayout(MacroActionSwitchScene::SceneType type,
return true;
}
if (IsFixedLengthTransition(transition.GetTransition())) {
if (isUsingFixedLengthTransition(transition.GetTransition())) {
return false;
}

View File

@@ -1,9 +1,5 @@
#include "macro-action-scene-visibility.hpp"
#include "layout-helpers.hpp"
#include "plugin-state-helpers.hpp"
#include "transition-helpers.hpp"
#include <obs-frontend-api.h>
namespace advss {
@@ -25,168 +21,9 @@ const static std::map<MacroActionSceneVisibility::Action, std::string>
"AdvSceneSwitcher.action.sceneVisibility.type.toggle"},
};
namespace {
struct TransitionRestoreContext;
static std::unordered_map<obs_sceneitem_t *, TransitionRestoreContext *>
restoreContexts;
static std::mutex restoreMutex;
struct TransitionRestoreContext {
public:
obs_sceneitem_t *item;
bool wasVisible;
OBSSource originalTransition = nullptr;
uint32_t originalDuration;
obs_source_t *transition = nullptr;
signal_handler_t *sh = nullptr;
};
} // namespace
static void handleShutdown(enum obs_frontend_event event, void *private_data)
{
if (event != OBS_FRONTEND_EVENT_EXIT) {
return;
}
std::lock_guard<std::mutex> lock(restoreMutex);
for (const auto &[_, ctx] : restoreContexts) {
obs_source_release(ctx->transition);
}
restoreContexts.clear();
}
static bool setup()
{
obs_frontend_add_event_callback(handleShutdown, nullptr);
return true;
}
static bool setupDone = setup();
static void handleSourceDestroyed(void *param, calldata_t *)
{
if (OBSIsShuttingDown()) {
return;
}
auto ctx = static_cast<TransitionRestoreContext *>(param);
ctx->sh = nullptr;
ctx->originalTransition = nullptr;
delete ctx;
}
static void resetSceneItemTransition(void *param, calldata_t *)
{
if (OBSIsShuttingDown()) {
return;
}
auto ctx = static_cast<TransitionRestoreContext *>(param);
if (!ctx) {
return;
}
SetSceneItemTransition(ctx->item, ctx->originalTransition,
!ctx->wasVisible);
obs_sceneitem_set_transition_duration(ctx->item, !ctx->wasVisible,
ctx->originalDuration);
signal_handler_disconnect(ctx->sh, "transition_stop",
resetSceneItemTransition, ctx);
signal_handler_disconnect(ctx->sh, "destroy", handleSourceDestroyed,
ctx);
{
std::lock_guard<std::mutex> lock(restoreMutex);
restoreContexts.erase(ctx->item);
}
obs_source_release(ctx->transition);
delete ctx;
}
static void attachRestoreContext(obs_sceneitem_t *item,
obs_source_t *transition, bool itemWasVisible,
OBSSource originalTransition,
uint32_t originalDuration)
{
signal_handler_t *sh = obs_source_get_signal_handler(transition);
if (!sh) {
return;
}
std::lock_guard<std::mutex> lock(restoreMutex);
auto ctx = new TransitionRestoreContext{
item,
itemWasVisible,
originalTransition,
originalDuration,
obs_source_get_ref(transition),
sh,
};
auto it = restoreContexts.find(item);
if (it != restoreContexts.end()) {
auto *oldCtx = it->second;
signal_handler_disconnect(oldCtx->sh, "transition_stop",
resetSceneItemTransition, oldCtx);
signal_handler_disconnect(oldCtx->sh, "destroy",
handleSourceDestroyed, oldCtx);
ctx->originalTransition = oldCtx->originalTransition;
ctx->originalDuration = oldCtx->originalDuration;
obs_source_release(oldCtx->transition);
delete oldCtx;
restoreContexts.erase(it);
}
restoreContexts[item] = ctx;
signal_handler_connect(sh, "transition_stop", resetSceneItemTransition,
ctx);
signal_handler_connect(sh, "destroy", handleSourceDestroyed, ctx);
}
static void setSceneItemVisibility(obs_sceneitem_t *item,
const bool setTransition,
const OBSWeakSource &transitionWeak,
const bool setDuration,
const Duration &duration,
MacroActionSceneVisibility::Action action)
{
const OBSSourceAutoRelease transition = OBSGetStrongRef(transitionWeak);
const bool itemIsVisible = obs_sceneitem_visible(item);
const OBSSource currentTransition =
obs_sceneitem_get_transition(item, !itemIsVisible);
const uint32_t currentTransitionDuration =
obs_sceneitem_get_transition_duration(item, !itemIsVisible);
OBSSource privateTransitionSource = nullptr;
if (setTransition) {
privateTransitionSource = SetSceneItemTransition(
item, transition, !itemIsVisible);
} else {
privateTransitionSource =
obs_sceneitem_get_transition(item, !itemIsVisible);
}
if (setDuration) {
obs_sceneitem_set_transition_duration(item, !itemIsVisible,
duration.Milliseconds());
}
switch (action) {
case MacroActionSceneVisibility::Action::SHOW:
obs_sceneitem_set_visible(item, true);
@@ -195,43 +32,16 @@ static void setSceneItemVisibility(obs_sceneitem_t *item,
obs_sceneitem_set_visible(item, false);
break;
case MacroActionSceneVisibility::Action::TOGGLE:
obs_sceneitem_set_visible(item, !itemIsVisible);
obs_sceneitem_set_visible(item, !obs_sceneitem_visible(item));
break;
}
if (!setTransition && !setDuration) {
return;
}
if (!privateTransitionSource) {
if (setTransition) {
SetSceneItemTransition(item, currentTransition,
!itemIsVisible);
}
if (setDuration) {
obs_sceneitem_set_transition_duration(
item, !itemIsVisible,
currentTransitionDuration);
}
return;
}
auto sh = obs_source_get_signal_handler(privateTransitionSource);
if (!sh) {
return;
}
attachRestoreContext(item, privateTransitionSource, itemIsVisible,
currentTransition, currentTransitionDuration);
}
bool MacroActionSceneVisibility::PerformAction()
{
auto items = _source.GetSceneItems(_scene);
for (const auto &item : items) {
setSceneItemVisibility(item, _updateTransition,
_transition.GetTransition(),
_updateDuration, _duration, _action);
setSceneItemVisibility(item, _action);
}
return true;
}
@@ -256,10 +66,6 @@ bool MacroActionSceneVisibility::Save(obs_data_t *obj) const
MacroAction::Save(obj);
_scene.Save(obj);
_source.Save(obj);
obs_data_set_bool(obj, "updateTransition", _updateTransition);
_transition.Save(obj);
obs_data_set_bool(obj, "updateDuration", _updateDuration);
_duration.Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
return true;
}
@@ -276,10 +82,6 @@ bool MacroActionSceneVisibility::Load(obs_data_t *obj)
MacroAction::Load(obj);
_scene.Load(obj);
_source.Load(obj);
_updateTransition = obs_data_get_bool(obj, "updateTransition");
_transition.Load(obj);
_updateDuration = obs_data_get_bool(obj, "updateDuration");
_duration.Load(obj);
_action = static_cast<MacroActionSceneVisibility::Action>(
obs_data_get_int(obj, "action"));
@@ -339,17 +141,10 @@ MacroActionSceneVisibilityEdit::MacroActionSceneVisibilityEdit(
SceneItemSelection::Type::ALL,
},
SceneItemSelectionWidget::NameClashMode::ALL)),
_updateTransition(new QCheckBox(this)),
_transitions(new TransitionSelectionWidget(this, false, false)),
_updateDuration(new QCheckBox(this)),
_duration(new DurationSelection(this, false)),
_durationLayout(new QHBoxLayout),
_actions(new QComboBox())
{
populateActionSelection(_actions);
_duration->SpinBox()->setSpecialValueText("-");
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
@@ -359,49 +154,18 @@ MacroActionSceneVisibilityEdit::MacroActionSceneVisibilityEdit(
QWidget::connect(_sources,
SIGNAL(SceneItemChanged(const SceneItemSelection &)),
this, SLOT(SourceChanged(const SceneItemSelection &)));
QWidget::connect(_updateTransition, SIGNAL(stateChanged(int)), this,
SLOT(UpdateTransitionChanged(int)));
QWidget::connect(_transitions,
SIGNAL(TransitionChanged(const TransitionSelection &)),
this,
SLOT(TransitionChanged(const TransitionSelection &)));
QWidget::connect(_updateDuration, SIGNAL(stateChanged(int)), this,
SLOT(UpdateDurationChanged(int)));
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(DurationChanged(const Duration &)));
auto sceneItemLayout = new QHBoxLayout;
auto layout = new QHBoxLayout;
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.sceneVisibility.layout"),
sceneItemLayout,
"AdvSceneSwitcher.action.sceneVisibility.entry"),
layout,
{{"{{scenes}}", _scenes},
{"{{sources}}", _sources},
{"{{actions}}", _actions}});
auto transitionLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.sceneVisibility.layout.transition"),
transitionLayout,
{{"{{updateTransition}}", _updateTransition},
{"{{transitions}}", _transitions}});
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.sceneVisibility.layout.duration"),
_durationLayout,
{{"{{updateDuration}}", _updateDuration},
{"{{duration}}", _duration}});
auto layout = new QVBoxLayout;
layout->addLayout(sceneItemLayout);
layout->addLayout(transitionLayout);
layout->addLayout(_durationLayout);
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
SetWidgetVisibility();
_loading = false;
}
@@ -413,11 +177,7 @@ void MacroActionSceneVisibilityEdit::UpdateEntryData()
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_scenes->SetScene(_entryData->_scene);
_sources->SetSceneItem(_entryData->_source);
_updateTransition->setChecked(_entryData->_updateTransition);
_transitions->SetTransition(_entryData->_transition);
_updateDuration->setChecked(_entryData->_updateDuration);
_duration->SetDuration(_entryData->_duration);
_sources->SetSceneItem((_entryData->_source));
}
void MacroActionSceneVisibilityEdit::SceneChanged(const SceneSelection &s)
@@ -437,34 +197,6 @@ void MacroActionSceneVisibilityEdit::SourceChanged(
updateGeometry();
}
void MacroActionSceneVisibilityEdit::UpdateTransitionChanged(int state)
{
GUARD_LOADING_AND_LOCK();
_entryData->_updateTransition = state;
SetWidgetVisibility();
}
void MacroActionSceneVisibilityEdit::TransitionChanged(
const TransitionSelection &t)
{
GUARD_LOADING_AND_LOCK();
_entryData->_transition = t;
SetWidgetVisibility();
}
void MacroActionSceneVisibilityEdit::UpdateDurationChanged(int state)
{
GUARD_LOADING_AND_LOCK();
_entryData->_updateDuration = state;
SetWidgetVisibility();
}
void MacroActionSceneVisibilityEdit::DurationChanged(const Duration &dur)
{
GUARD_LOADING_AND_LOCK();
_entryData->_duration = dur;
}
void MacroActionSceneVisibilityEdit::ActionChanged(int value)
{
GUARD_LOADING_AND_LOCK();
@@ -472,20 +204,4 @@ void MacroActionSceneVisibilityEdit::ActionChanged(int value)
static_cast<MacroActionSceneVisibility::Action>(value);
}
void MacroActionSceneVisibilityEdit::SetWidgetVisibility()
{
const bool hideDurationSelection =
_entryData->_updateTransition &&
IsFixedLengthTransition(
_entryData->_transition.GetTransition());
SetLayoutVisible(_durationLayout, !hideDurationSelection);
_transitions->setEnabled(_entryData->_updateTransition);
_duration->setEnabled(_entryData->_updateDuration);
adjustSize();
updateGeometry();
}
} // namespace advss

View File

@@ -1,9 +1,7 @@
#pragma once
#include "duration-control.hpp"
#include "macro-action-edit.hpp"
#include "scene-selection.hpp"
#include "scene-item-selection.hpp"
#include "transition-selection.hpp"
namespace advss {
@@ -22,10 +20,6 @@ public:
SceneSelection _scene;
SceneItemSelection _source;
bool _updateTransition = false;
TransitionSelection _transition;
bool _updateDuration = false;
Duration _duration;
enum class Action {
SHOW,
@@ -59,27 +53,17 @@ public:
private slots:
void SceneChanged(const SceneSelection &);
void SourceChanged(const SceneItemSelection &);
void UpdateTransitionChanged(int);
void TransitionChanged(const TransitionSelection &);
void UpdateDurationChanged(int);
void DurationChanged(const Duration &seconds);
void ActionChanged(int value);
signals:
void HeaderInfoChanged(const QString &);
private:
void SetWidgetVisibility();
protected:
SceneSelectionWidget *_scenes;
SceneItemSelectionWidget *_sources;
QCheckBox *_updateTransition;
TransitionSelectionWidget *_transitions;
QCheckBox *_updateDuration;
DurationSelection *_duration;
QHBoxLayout *_durationLayout;
QComboBox *_actions;
std::shared_ptr<MacroActionSceneVisibility> _entryData;
private:
bool _loading = true;
};

View File

@@ -82,22 +82,13 @@ 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;
}
@@ -105,21 +96,18 @@ bool MacroActionSequence::RunSequence()
return RunMacroActions(macro.get());
}
bool MacroActionSequence::SetSequenceIndex()
bool MacroActionSequence::SetSequenceIndex() const
{
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;
@@ -133,34 +121,10 @@ bool MacroActionSequence::SetSequenceIndex()
// -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) {
@@ -198,7 +162,7 @@ bool MacroActionSequence::Load(obs_data_t *obj)
LoadMacroList(obj, _macros);
_restart = obs_data_get_bool(obj, "restart");
_macro.Load(obj);
SetAction(static_cast<Action>(obs_data_get_int(obj, "action")));
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
_resetIndex.Load(obj, "resetIndex");
return true;
}
@@ -292,7 +256,7 @@ void MacroActionSequenceEdit::UpdateEntryData()
_macroList->SetContent(_entryData->_macros);
_restart->setChecked(_entryData->_restart);
_resetIndex->SetValue(_entryData->_resetIndex);
_actions->setCurrentIndex(static_cast<int>(_entryData->GetAction()));
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_macros->SetCurrentMacro(_entryData->_macro);
SetWidgetVisibility();
adjustSize();
@@ -397,7 +361,7 @@ void MacroActionSequenceEdit::UpdateStatusLine()
void MacroActionSequenceEdit::ActionChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->SetAction(static_cast<MacroActionSequence::Action>(value));
_entryData->_action = static_cast<MacroActionSequence::Action>(value);
SetWidgetVisibility();
}
@@ -421,10 +385,10 @@ void MacroActionSequenceEdit::SetWidgetVisibility()
ClearLayout(_layout);
const auto action = _entryData->GetAction();
PlaceWidgets(
obs_module_text(
action == MacroActionSequence::Action::RUN_SEQUENCE
_entryData->_action ==
MacroActionSequence::Action::RUN_SEQUENCE
? "AdvSceneSwitcher.action.sequence.entry.run"
: "AdvSceneSwitcher.action.sequence.entry.setIndex"),
_layout,
@@ -432,14 +396,15 @@ void MacroActionSequenceEdit::SetWidgetVisibility()
{"{{macros}}", _macros},
{"{{index}}", _resetIndex}});
_macroList->setVisible(action ==
_macroList->setVisible(_entryData->_action ==
MacroActionSequence::Action::RUN_SEQUENCE);
_restart->setVisible(action ==
_restart->setVisible(_entryData->_action ==
MacroActionSequence::Action::RUN_SEQUENCE);
_statusLine->setVisible(action ==
_statusLine->setVisible(_entryData->_action ==
MacroActionSequence::Action::RUN_SEQUENCE);
_macros->setVisible(action == MacroActionSequence::Action::SET_INDEX);
_resetIndex->setVisible(action ==
_macros->setVisible(_entryData->_action ==
MacroActionSequence::Action::SET_INDEX);
_resetIndex->setVisible(_entryData->_action ==
MacroActionSequence::Action::SET_INDEX);
adjustSize();

View File

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

View File

@@ -1,286 +0,0 @@
#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::SetCurrentStepEditor(int row)
{
if (_stepEditor) {
delete _stepEditor;
_stepEditor = nullptr;
}
if (!_entryData || row < 0 || row >= (int)_entryData->_steps.size()) {
_noSelectionLabel->setVisible(_stepList->count() > 0);
return;
}
_noSelectionLabel->setVisible(false);
_stepEditor =
new SourceInteractionStepEdit(this, _entryData->_steps[row]);
static_cast<QVBoxLayout *>(layout())->addWidget(_stepEditor);
connect(_stepEditor, &SourceInteractionStepEdit::StepChanged, this,
&MacroActionSourceInteractionEdit::StepChanged);
adjustSize();
updateGeometry();
}
} // namespace advss

View File

@@ -1,78 +0,0 @@
#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 &);
private:
void SetCurrentStepEditor(int row);
SourceSelectionWidget *_sources;
SourceInteractionStepList *_stepList;
QPushButton *_recordButton;
SourceInteractionStepEdit *_stepEditor = nullptr;
QLabel *_noSelectionLabel;
std::shared_ptr<MacroActionSourceInteraction> _entryData;
bool _loading = true;
};
} // namespace advss

View File

@@ -13,14 +13,6 @@ namespace advss {
const std::string MacroActionSource::id = "source";
std::vector<TempVariableRef> MacroActionSource::GetTempVarRefs() const
{
if (!_tempVar.HasValidID()) {
return {};
}
return {_tempVar};
}
bool MacroActionSource::_registered = MacroActionFactory::Register(
MacroActionSource::id,
{MacroActionSource::Create, MacroActionSourceEdit::Create,
@@ -53,10 +45,6 @@ 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 = {
@@ -285,22 +273,6 @@ 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;
}
@@ -388,31 +360,6 @@ 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) {
@@ -555,7 +502,7 @@ void MacroActionSourceEdit::UpdateEntryData()
const auto weakSource = _entryData->_source.GetSource();
_settingsButtons->SetSelection(weakSource, _entryData->_button);
_actions->setCurrentIndex(static_cast<int>(_entryData->GetAction()));
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_sources->SetSource(_entryData->_source);
_sourceSettings->SetSelection(weakSource, _entryData->_setting);
_settingsString->setPlainText(_entryData->_settingsString);
@@ -590,7 +537,7 @@ void MacroActionSourceEdit::SourceChanged(const SourceSelection &source)
void MacroActionSourceEdit::ActionChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->SetAction(static_cast<MacroActionSource::Action>(value));
_entryData->_action = static_cast<MacroActionSource::Action>(value);
SetWidgetVisibility();
}
@@ -660,7 +607,6 @@ void MacroActionSourceEdit::SelectionChanged(const TempVariableRef &var)
{
GUARD_LOADING_AND_LOCK();
_entryData->_tempVar = var;
IncrementTempVarInUseGeneration();
}
void MacroActionSourceEdit::SettingsInputMethodChanged(int idx)
@@ -718,29 +664,19 @@ 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,
isSetSettings || isGetSetting || isGetSettings);
_settingsInputMethods->setVisible(isSetSettings);
_entryData->_action ==
MacroActionSource::Action::SETTINGS);
_sourceSettings->setVisible(
(isSetSettings &&
_entryData->_settingsInputMethod !=
MacroActionSource::SettingsInputMethod::JSON_STRING) ||
isGetSetting);
_entryData->_action == MacroActionSource::Action::SETTINGS &&
_entryData->_settingsInputMethod !=
MacroActionSource::SettingsInputMethod::JSON_STRING);
_settingsString->setVisible(
isSetSettings &&
_entryData->_action == MacroActionSource::Action::SETTINGS &&
_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::JSON_STRING);
_getSettings->setVisible(
isSetSettings &&
_entryData->_action == MacroActionSource::Action::SETTINGS &&
_entryData->_settingsInputMethod !=
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_TEMPVAR);
@@ -749,12 +685,13 @@ void MacroActionSourceEdit::SetWidgetVisibility()
GetIndividualListEntryName(),
_entryData->_setting.IsList());
_tempVars->setVisible(isSetSettings &&
_tempVars->setVisible(_entryData->_action ==
MacroActionSource::Action::SETTINGS &&
_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_TEMPVAR);
if (isSetSettings &&
if (_entryData->_action == MacroActionSource::Action::SETTINGS &&
(_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::INDIVIDUAL_MANUAL ||
_entryData->_settingsInputMethod ==
@@ -767,31 +704,35 @@ void MacroActionSourceEdit::SetWidgetVisibility()
_manualSettingValue->hide();
}
const bool showWarning = action == MacroActionSource::Action::ENABLE ||
action == MacroActionSource::Action::DISABLE;
const bool showWarning =
_entryData->_action == MacroActionSource::Action::ENABLE ||
_entryData->_action == MacroActionSource::Action::DISABLE;
_warning->setVisible(showWarning);
_settingsButtons->setVisible(
action == MacroActionSource::Action::SETTINGS_BUTTON);
_entryData->_action ==
MacroActionSource::Action::SETTINGS_BUTTON);
_deinterlaceMode->setVisible(
action == MacroActionSource::Action::DEINTERLACE_MODE);
_entryData->_action ==
MacroActionSource::Action::DEINTERLACE_MODE);
_deinterlaceOrder->setVisible(
action == MacroActionSource::Action::DEINTERLACE_FIELD_ORDER);
_entryData->_action ==
MacroActionSource::Action::DEINTERLACE_FIELD_ORDER);
_refreshSettingSelection->setVisible(
((isSetSettings &&
(_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_MANUAL ||
_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_LIST_ENTRY)) ||
isGetSetting) &&
(_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_MANUAL ||
_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_LIST_ENTRY) &&
_entryData->_source.GetType() ==
SourceSelection::Type::VARIABLE);
_acceptDialog->setVisible(
action == MacroActionSource::Action::CLOSE_FILTER_DIALOG ||
action == MacroActionSource::Action::CLOSE_PROPERTIES_DIALOG);
_entryData->_action ==
MacroActionSource::Action::CLOSE_FILTER_DIALOG ||
_entryData->_action ==
MacroActionSource::Action::CLOSE_PROPERTIES_DIALOG);
adjustSize();
updateGeometry();

View File

@@ -15,22 +15,26 @@ namespace advss {
class MacroActionSource : public MacroAction {
public:
MacroActionSource(Macro *m) : MacroAction(m) {}
static std::shared_ptr<MacroAction> Create(Macro *m);
std::shared_ptr<MacroAction> Copy() const;
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();
void SetupTempVars();
std::vector<TempVariableRef> GetTempVarRefs() const;
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 Action {
ENABLE,
@@ -46,23 +50,8 @@ public:
CLOSE_INTERACTION_DIALOG,
CLOSE_FILTER_DIALOG,
CLOSE_PROPERTIES_DIALOG,
GET_SETTING,
GET_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;
Action _action = Action::SETTINGS;
enum class SettingsInputMethod {
INDIVIDUAL_MANUAL,
@@ -74,8 +63,6 @@ public:
SettingsInputMethod::INDIVIDUAL_MANUAL;
private:
Action _action = Action::SETTINGS;
static bool _registered;
static const std::string id;
};

View File

@@ -1,9 +1,7 @@
#include "macro-action-transition.hpp"
#include "layout-helpers.hpp"
#include "transition-helpers.hpp"
#include <obs-frontend-api.h>
#include <algorithm>
namespace advss {
@@ -23,10 +21,6 @@ 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()
@@ -81,11 +75,24 @@ static void obs_sceneitem_set_transition_duration(obs_sceneitem_t *item,
}
#endif
void MacroActionTransition::SetTbarPosition()
static void setSceneItemTransition(const OBSSceneItem &item,
const OBSSourceAutoRelease &transition,
bool show)
{
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);
OBSDataAutoRelease settings = obs_source_get_settings(transition);
if (!transition || !settings) {
// Set transition to "None"
obs_sceneitem_set_transition(item, show, nullptr);
return;
}
// We cannot share the transition source between
// scene items without introducing strange graphical
// artifacts so we have to create new ones here
OBSSourceAutoRelease transitionSource = obs_source_create_private(
obs_source_get_id(transition), obs_source_get_name(transition),
settings);
obs_sceneitem_set_transition(item, show, transitionSource);
}
void MacroActionTransition::SetSourceTransition(bool show)
@@ -96,7 +103,7 @@ void MacroActionTransition::SetSourceTransition(bool show)
const auto items = _source.GetSceneItems(_scene);
for (const auto &item : items) {
if (_setTransitionType) {
SetSceneItemTransition(item, transition, show);
setSceneItemTransition(item, transition, show);
}
if (_setDuration) {
obs_sceneitem_set_transition_duration(
@@ -122,12 +129,6 @@ 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;
}
@@ -137,29 +138,22 @@ 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(),
@@ -181,7 +175,6 @@ 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;
}
@@ -195,25 +188,23 @@ 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 "";
}
@@ -233,7 +224,6 @@ void MacroActionTransition::ResolveVariablesToFixedValues()
_source.ResolveVariables();
_scene.ResolveVariables();
_duration.ResolveVariables();
_tbarPosition.ResolveVariables();
}
static inline void populateActionSelection(QComboBox *list)
@@ -265,16 +255,9 @@ MacroActionTransitionEdit::MacroActionTransitionEdit(
_setDuration(new QCheckBox),
_transitions(new TransitionSelectionWidget(this, false)),
_duration(new DurationSelection(this, false)),
_tbarPosition(new VariableDoubleSpinBox),
_transitionLayout(new QHBoxLayout),
_durationLayout(new QHBoxLayout),
_tbarLayout(new QHBoxLayout)
_durationLayout(new QHBoxLayout)
{
_tbarPosition->setMinimum(0.0);
_tbarPosition->setMaximum(100.0);
_tbarPosition->setDecimals(2);
_tbarPosition->setSuffix("%");
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
@@ -296,11 +279,6 @@ 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},
@@ -310,29 +288,22 @@ MacroActionTransitionEdit::MacroActionTransitionEdit(
{"{{duration}}", _duration},
{"{{setTransition}}", _setTransition},
{"{{setDuration}}", _setDuration},
{"{{tbarPosition}}", _tbarPosition},
};
auto typeLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.transition.layout.type"),
"AdvSceneSwitcher.action.transition.entry.line1"),
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.layout.tbar"),
_tbarLayout, widgetPlaceholders);
"AdvSceneSwitcher.action.transition.entry.line2"),
_transitionLayout, widgetPlaceholders);
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.transition.entry.line3"),
_durationLayout, widgetPlaceholders);
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(typeLayout);
mainLayout->addLayout(_transitionLayout);
mainLayout->addLayout(_durationLayout);
mainLayout->addLayout(_tbarLayout);
setLayout(mainLayout);
_entryData = entryData;
@@ -348,14 +319,13 @@ 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();
}
@@ -402,19 +372,11 @@ 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 &&
!isTbar && !isReleaseTbar);
SetLayoutVisible(_transitionLayout, !isTbar && !isReleaseTbar);
SetLayoutVisible(_durationLayout, !isTbar && !isReleaseTbar);
SetLayoutVisible(_tbarLayout, isTbar);
MacroActionTransition::Type::SCENE);
adjustSize();
}
@@ -438,13 +400,4 @@ void MacroActionTransitionEdit::SetDurationChanged(int state)
_duration->setEnabled(state);
}
void MacroActionTransitionEdit::TbarPositionChanged(
const NumberVariable<double> &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_tbarPosition = value;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
} // namespace advss

View File

@@ -4,7 +4,6 @@
#include "transition-selection.hpp"
#include "scene-selection.hpp"
#include "scene-item-selection.hpp"
#include "variable-spinbox.hpp"
#include <QCheckBox>
#include <QHBoxLayout>
@@ -29,8 +28,6 @@ public:
SCENE_OVERRIDE,
SOURCE_SHOW,
SOURCE_HIDE,
TBAR,
RELEASE_TBAR,
};
Type _type = Type::SCENE;
@@ -40,13 +37,11 @@ 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;
@@ -77,7 +72,6 @@ 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 &);
@@ -89,10 +83,8 @@ protected:
QCheckBox *_setDuration;
TransitionSelectionWidget *_transitions;
DurationSelection *_duration;
VariableDoubleSpinBox *_tbarPosition;
QHBoxLayout *_transitionLayout;
QHBoxLayout *_durationLayout;
QHBoxLayout *_tbarLayout;
std::shared_ptr<MacroActionTransition> _entryData;
private:

View File

@@ -38,13 +38,32 @@ const static std::map<MacroConditionDate::Condition, std::string>
"AdvSceneSwitcher.condition.date.state.before"},
};
const static std::map<MacroConditionDate::Day, std::string> dayOfWeekNames = {
{MacroConditionDate::Day::ANY,
"AdvSceneSwitcher.condition.date.anyDay"},
{MacroConditionDate::Day::MONDAY,
"AdvSceneSwitcher.condition.date.monday"},
{MacroConditionDate::Day::TUESDAY,
"AdvSceneSwitcher.condition.date.tuesday"},
{MacroConditionDate::Day::WEDNESDAY,
"AdvSceneSwitcher.condition.date.wednesday"},
{MacroConditionDate::Day::THURSDAY,
"AdvSceneSwitcher.condition.date.thursday"},
{MacroConditionDate::Day::FRIDAY,
"AdvSceneSwitcher.condition.date.friday"},
{MacroConditionDate::Day::SATURDAY,
"AdvSceneSwitcher.condition.date.saturday"},
{MacroConditionDate::Day::SUNDAY,
"AdvSceneSwitcher.condition.date.sunday"},
};
bool MacroConditionDate::CheckDayOfWeek(int64_t msSinceLastCheck)
{
QDateTime cur = QDateTime::currentDateTime();
SetVariables(cur);
if (!_days.contains(static_cast<DayOfWeekSelector::Day>(
cur.date().dayOfWeek()))) {
if (_dayOfWeek != Day::ANY &&
cur.date().dayOfWeek() != static_cast<int>(_dayOfWeek)) {
return false;
}
if (_ignoreTime) {
@@ -182,7 +201,7 @@ bool MacroConditionDate::CheckCondition()
bool MacroConditionDate::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
SaveSelectedDays(obj, _days);
obs_data_set_int(obj, "dayOfWeek", static_cast<int>(_dayOfWeek));
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
const auto &dateToSave = _updateOnRepeat ? _dateTime : _origDateTime;
const auto &dateToSave2 = _updateOnRepeat ? _dateTime2 : _origDateTime2;
@@ -197,14 +216,13 @@ bool MacroConditionDate::Save(obs_data_t *obj) const
_duration.Save(obj);
obs_data_set_bool(obj, "dayOfWeekCheck", _dayOfWeekCheck);
obs_data_set_string(obj, "pattern", _pattern.c_str());
obs_data_set_int(obj, "version", 1);
return true;
}
bool MacroConditionDate::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_days = LoadSelectedDays(obj);
_dayOfWeek = static_cast<Day>(obs_data_get_int(obj, "dayOfWeek"));
_condition = static_cast<MacroConditionDate::Condition>(
obs_data_get_int(obj, "condition"));
_dateTime = QDateTime::fromString(
@@ -227,64 +245,17 @@ bool MacroConditionDate::Load(obs_data_t *obj)
_condition == MacroConditionDate::Condition::BETWEEN) {
_condition = MacroConditionDate::Condition::AT;
}
if (!obs_data_has_user_value(obj, "version")) {
enum class Day {
ANY = 0,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY,
};
auto oldDay =
static_cast<Day>(obs_data_get_int(obj, "dayOfWeek"));
switch (oldDay) {
case Day::ANY:
_days = {DayOfWeekSelector::Monday,
DayOfWeekSelector::Tuesday,
DayOfWeekSelector::Wednesday,
DayOfWeekSelector::Thursday,
DayOfWeekSelector::Friday,
DayOfWeekSelector::Saturday,
DayOfWeekSelector::Sunday};
break;
case Day::MONDAY:
_days = {DayOfWeekSelector::Monday};
break;
case Day::TUESDAY:
_days = {DayOfWeekSelector::Tuesday};
break;
case Day::WEDNESDAY:
_days = {DayOfWeekSelector::Wednesday};
break;
case Day::THURSDAY:
_days = {DayOfWeekSelector::Thursday};
break;
case Day::FRIDAY:
_days = {DayOfWeekSelector::Friday};
break;
case Day::SATURDAY:
_days = {DayOfWeekSelector::Saturday};
break;
case Day::SUNDAY:
_days = {DayOfWeekSelector::Sunday};
break;
default:
break;
}
}
return true;
}
std::string MacroConditionDate::GetShortDesc() const
{
if (_dayOfWeekCheck) {
const auto dayName =
DayOfWeekSelector::ToString(_days).toStdString();
auto it = dayOfWeekNames.find(_dayOfWeek);
if (it == dayOfWeekNames.end()) {
return "";
}
std::string dayName = obs_module_text(it->second.c_str());
if (_ignoreTime) {
return dayName;
}
@@ -376,6 +347,13 @@ void MacroConditionDate::SetupTempVars()
obs_module_text("AdvSceneSwitcher.tempVar.date.dayOfWeek"));
}
static inline void populateDaySelection(QComboBox *list)
{
for (auto entry : dayOfWeekNames) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
static inline void populateConditionSelection(QComboBox *list)
{
for (auto entry : dateConditionTypes) {
@@ -394,7 +372,7 @@ MacroConditionDateEdit::MacroConditionDateEdit(
QWidget *parent, std::shared_ptr<MacroConditionDate> entryData)
: QWidget(parent),
_weekCondition(new QComboBox()),
_days(new DayOfWeekSelector(this)),
_dayOfWeek(new QComboBox()),
_ignoreWeekTime(new QCheckBox()),
_weekTime(new QTimeEdit()),
_condition(new QComboBox()),
@@ -414,7 +392,7 @@ MacroConditionDateEdit::MacroConditionDateEdit(
_currentDate(new QLabel()),
_advancedSettingsTooggle(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.date.showAdvancedSettings"))),
_simpleLayout(new QVBoxLayout()),
_simpleLayout(new QHBoxLayout()),
_advancedLayout(new QHBoxLayout()),
_repeatLayout(new QVBoxLayout()),
_repeatUpdateLayout(new QHBoxLayout()),
@@ -440,8 +418,8 @@ MacroConditionDateEdit::MacroConditionDateEdit(
QWidget::connect(_weekCondition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_days, &DayOfWeekSelector::SelectionChanged, this,
&MacroConditionDateEdit::DaysChanged);
QWidget::connect(_dayOfWeek, SIGNAL(currentIndexChanged(int)), this,
SLOT(DayOfWeekChanged(int)));
QWidget::connect(_ignoreWeekTime, SIGNAL(stateChanged(int)), this,
SLOT(IgnoreTimeChanged(int)));
QWidget::connect(_weekTime, SIGNAL(timeChanged(const QTime &)), this,
@@ -471,12 +449,13 @@ MacroConditionDateEdit::MacroConditionDateEdit(
QWidget::connect(_pattern, SIGNAL(editingFinished()), this,
SLOT(PatternChanged()));
populateDaySelection(_dayOfWeek);
populateConditionSelection(_condition);
populateWeekConditionSelection(_weekCondition);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{weekCondition}}", _weekCondition},
{"{{dayOfWeek}}", _days},
{"{{dayOfWeek}}", _dayOfWeek},
{"{{ignoreWeekTime}}", _ignoreWeekTime},
{"{{weekTime}}", _weekTime},
{"{{condition}}", _condition},
@@ -493,39 +472,28 @@ MacroConditionDateEdit::MacroConditionDateEdit(
{"{{pattern}}", _pattern},
{"{{currentDate}}", _currentDate},
};
auto dayLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.date.layout.simple.day"),
dayLayout, widgetPlaceholders);
auto timeLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.date.layout.simple.time"),
timeLayout, widgetPlaceholders);
_simpleLayout->addLayout(dayLayout);
_simpleLayout->addLayout(timeLayout);
obs_module_text("AdvSceneSwitcher.condition.date.entry.simple"),
_simpleLayout, widgetPlaceholders);
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.date.layout.advanced"),
"AdvSceneSwitcher.condition.date.entry.advanced"),
_advancedLayout, widgetPlaceholders);
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.date.layout.updateOnRepeat"),
"AdvSceneSwitcher.condition.date.entry.updateOnRepeat"),
_repeatUpdateLayout, widgetPlaceholders);
auto repeatLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.date.entry.repeat"),
repeatLayout, widgetPlaceholders);
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.date.layout.repeat"),
repeatLayout, widgetPlaceholders);
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.date.layout.pattern"),
"AdvSceneSwitcher.condition.date.entry.pattern"),
_patternLayout, widgetPlaceholders);
_repeatLayout->addLayout(repeatLayout);
_repeatLayout->addWidget(_nextMatchDate);
_repeatLayout->addLayout(_repeatUpdateLayout);
auto mainLayout = new QVBoxLayout;
auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(_simpleLayout);
mainLayout->addLayout(_advancedLayout);
mainLayout->addLayout(_patternLayout);
@@ -547,11 +515,10 @@ MacroConditionDateEdit::MacroConditionDateEdit(
_loading = false;
}
void MacroConditionDateEdit::DaysChanged(
const QSet<DayOfWeekSelector::Day> &days)
void MacroConditionDateEdit::DayOfWeekChanged(int day)
{
GUARD_LOADING_AND_LOCK();
_entryData->_days = days;
_entryData->_dayOfWeek = static_cast<MacroConditionDate::Day>(day);
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
@@ -665,7 +632,7 @@ void MacroConditionDateEdit::ShowNextMatch()
return;
}
QString format(obs_module_text(
"AdvSceneSwitcher.condition.date.layout.nextMatchDate"));
"AdvSceneSwitcher.condition.date.entry.nextMatchDate"));
_nextMatchDate->setText(
format.arg(_entryData->GetNextMatchDateTime().toString()));
}
@@ -691,7 +658,8 @@ void MacroConditionDateEdit::UpdateEntryData()
}
_weekCondition->setCurrentIndex(
static_cast<int>(_entryData->_condition));
_days->SetSelectedDays(_entryData->_days);
_dayOfWeek->setCurrentIndex(
static_cast<int>(static_cast<int>(_entryData->_dayOfWeek)));
_ignoreWeekTime->setChecked(!_entryData->_ignoreTime);
_weekTime->setTime(_entryData->GetDateTime1().time());
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
@@ -710,12 +678,6 @@ void MacroConditionDateEdit::UpdateEntryData()
SetWidgetStatus();
}
void MacroConditionDateEdit::showEvent(QShowEvent *event)
{
const QSignalBlocker b(this);
UpdateEntryData();
}
void MacroConditionDateEdit::SetupSimpleView()
{
SetLayoutVisible(_simpleLayout, true);

View File

@@ -1,6 +1,5 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "day-of-week-selector.hpp"
#include "duration-control.hpp"
#include <QCheckBox>
@@ -31,6 +30,17 @@ public:
QDateTime GetDateTime2() const;
QDateTime GetNextMatchDateTime() const;
enum class Day {
ANY = 0,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY,
};
enum class Condition {
AT,
AFTER,
@@ -39,11 +49,7 @@ public:
PATTERN,
};
QSet<DayOfWeekSelector::Day> _days = {
DayOfWeekSelector::Day::Monday,
DayOfWeekSelector::Day::Tuesday,
DayOfWeekSelector::Day::Wednesday,
};
Day _dayOfWeek = Day::ANY;
bool _ignoreDate = false;
bool _ignoreTime = false;
bool _repeat = false;
@@ -88,7 +94,7 @@ public:
}
private slots:
void DaysChanged(const QSet<DayOfWeekSelector::Day> &);
void DayOfWeekChanged(int day);
void ConditionChanged(int cond);
void DateChanged(const QDate &date);
void TimeChanged(const QTime &time);
@@ -107,18 +113,8 @@ 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;
QComboBox *_dayOfWeek;
QCheckBox *_ignoreWeekTime;
QTimeEdit *_weekTime;
@@ -138,15 +134,22 @@ private:
QLabel *_currentDate;
QPushButton *_advancedSettingsTooggle;
QVBoxLayout *_simpleLayout;
QHBoxLayout *_simpleLayout;
QHBoxLayout *_advancedLayout;
QVBoxLayout *_repeatLayout;
QHBoxLayout *_repeatUpdateLayout;
QHBoxLayout *_patternLayout;
QTimer _timer;
std::shared_ptr<MacroConditionDate> _entryData;
private:
void SetupSimpleView();
void SetupAdvancedView();
void SetupPatternView();
void SetWidgetStatus();
void ShowFirstDateSelection(bool visible);
void ShowSecondDateSelection(bool visible);
QTimer _timer;
bool _loading = true;
};

View File

@@ -1,10 +1,12 @@
#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 <QFileInfo>
#include <regex>
namespace advss {
@@ -17,6 +19,29 @@ 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;
@@ -25,6 +50,14 @@ 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));
}
@@ -33,13 +66,31 @@ bool MacroConditionFile::MatchFileContent(QString &filedata)
return CompareIgnoringLineEnding(text, filedata);
}
bool MacroConditionFile::CheckFileContent()
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()
{
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());
@@ -52,30 +103,42 @@ bool MacroConditionFile::CheckFileContent()
bool MacroConditionFile::CheckChangeContent()
{
QString filedata;
std::string path = _file;
QFile file(QString::fromStdString(path));
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
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;
}
filedata = QTextStream(&file).readAll();
file.close();
SetTempVarValue("content", filedata.toStdString());
size_t newHash = strHash(filedata.toUtf8().constData());
const bool contentChanged = !_firstContentCheck &&
(newHash != _lastHash);
const bool contentChanged = 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.isValid() && (_lastMod != newLastMod);
const bool dateChanged = _lastMod != newLastMod;
_lastMod = newLastMod;
SetTempVarValue("date", newLastMod.toString(Qt::ISODate).toStdString());
return dateChanged;
@@ -94,6 +157,10 @@ void MacroConditionFile::SetupTempVars()
"AdvSceneSwitcher.tempVar.file.content"));
}
if (_fileType == FileType::REMOTE) {
return;
}
AddTempvar(
"basename",
obs_module_text("AdvSceneSwitcher.tempVar.file.basename"),
@@ -132,7 +199,11 @@ bool MacroConditionFile::CheckCondition()
bool ret = false;
switch (_condition) {
case Condition::MATCH:
ret = CheckFileContent();
if (_fileType == FileType::REMOTE) {
ret = CheckRemoteFileContent();
break;
}
ret = CheckLocalFileContent();
break;
case Condition::CONTENT_CHANGE:
ret = CheckChangeContent();
@@ -193,7 +264,10 @@ 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;
}
@@ -201,10 +275,18 @@ 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;
}
@@ -213,6 +295,13 @@ 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(
@@ -232,13 +321,21 @@ 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))
_regex(new RegexConfigWidget(parent)),
_checkModificationDate(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.fileTab.checkfileContentTime"))),
_checkFileContent(new QCheckBox(
obs_module_text("AdvSceneSwitcher.fileTab.checkfileContent")))
{
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,
@@ -248,20 +345,42 @@ 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)));
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}});
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{fileType}}", _fileTypes},
{"{{conditions}}", _conditions},
{"{{filePath}}", _filePath},
{"{{matchText}}", _matchText},
{"{{useRegex}}", _regex},
{"{{checkModificationDate}}", _checkModificationDate},
{"{{checkFileContent}}", _checkFileContent},
};
auto layout = new QVBoxLayout;
layout->addLayout(widgetLayout);
layout->addWidget(_matchText);
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);
setLayout(layout);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
@@ -274,15 +393,47 @@ 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();
@@ -316,6 +467,18 @@ 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) {
@@ -326,6 +489,20 @@ void MacroConditionFileEdit::SetWidgetVisibility()
MacroConditionFile::Condition::MATCH);
_regex->setVisible(_entryData->GetCondition() ==
MacroConditionFile::Condition::MATCH);
_checkModificationDate->setVisible(
_entryData->_useTime &&
_entryData->GetCondition() ==
MacroConditionFile::Condition::MATCH);
_checkFileContent->setVisible(
_entryData->_onlyMatchIfChanged &&
_entryData->GetCondition() ==
MacroConditionFile::Condition::MATCH);
// TODO: Remove remote file support in future version in favor of HTTP
// action.
// Hide the option for now, if it is not used already.
_fileTypes->setVisible(_entryData->_fileType ==
MacroConditionFile::FileType::REMOTE);
adjustSize();
updateGeometry();

View File

@@ -7,6 +7,9 @@
#include <QWidget>
#include <QComboBox>
#include <QDateTime>
#include <QLineEdit>
#include <QPushButton>
#include <QCheckBox>
namespace advss {
@@ -23,6 +26,11 @@ public:
return std::make_shared<MacroConditionFile>(m);
}
enum class FileType {
LOCAL,
REMOTE,
};
enum class Condition {
MATCH,
CONTENT_CHANGE,
@@ -38,9 +46,15 @@ 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 CheckFileContent();
bool CheckRemoteFileContent();
bool CheckLocalFileContent();
bool CheckChangeContent();
bool CheckChangeDate();
void SetupTempVars();
@@ -48,7 +62,6 @@ private:
Condition _condition = Condition::MATCH;
QDateTime _lastMod;
size_t _lastHash = 0;
bool _firstContentCheck = true;
std::string _lastFile;
std::string _basename;
std::string _basenameComplete;
@@ -79,22 +92,29 @@ 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 &);
private:
void SetWidgetVisibility();
protected:
QComboBox *_fileTypes;
QComboBox *_conditions;
FileSelection *_filePath;
VariableTextEdit *_matchText;
RegexConfigWidget *_regex;
QCheckBox *_checkModificationDate;
QCheckBox *_checkFileContent;
std::shared_ptr<MacroConditionFile> _entryData;
private:
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -55,17 +55,17 @@ void MacroConditionGameCapture::GetCalldataInfo(calldata_t *cd)
if (!calldata_get_string(cd, "title", &title)) {
blog(LOG_WARNING, "%s failed to get title", __func__);
}
_title = title ? title : "";
_title = title;
const char *className = "";
if (!calldata_get_string(cd, "class", &className)) {
blog(LOG_WARNING, "%s failed to get class", __func__);
}
_class = className ? className : "";
_class = className;
const char *executable = "";
if (!calldata_get_string(cd, "executable", &executable)) {
blog(LOG_WARNING, "%s failed to get executable", __func__);
}
_executable = executable ? executable : "";
_executable = executable;
}
void MacroConditionGameCapture::HookedSignalReceived(void *data, calldata_t *cd)

View File

@@ -1,121 +0,0 @@
#include "day-of-week-selector.hpp"
#include "obs-module-helper.hpp"
#include <QPushButton>
#include <QHBoxLayout>
namespace advss {
DayOfWeekSelector::DayOfWeekSelector(QWidget *parent) : QWidget(parent)
{
auto layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(4);
const QVector<QPair<Day, QString>> days = {
{Monday, obs_module_text("AdvSceneSwitcher.day.monday")},
{Tuesday, obs_module_text("AdvSceneSwitcher.day.tuesday")},
{Wednesday, obs_module_text("AdvSceneSwitcher.day.wednesday")},
{Thursday, obs_module_text("AdvSceneSwitcher.day.thursday")},
{Friday, obs_module_text("AdvSceneSwitcher.day.friday")},
{Saturday, obs_module_text("AdvSceneSwitcher.day.saturday")},
{Sunday, obs_module_text("AdvSceneSwitcher.day.sunday")}};
for (const auto &[day, name] : days) {
auto btn = new QPushButton(this);
btn->setText(name);
btn->setCheckable(true);
connect(btn, &QPushButton::toggled, this,
&DayOfWeekSelector::OnButtonToggled);
layout->addWidget(btn);
m_buttons.insert(day, btn);
}
setLayout(layout);
}
void DayOfWeekSelector::OnButtonToggled(bool)
{
emit SelectionChanged(SelectedDays());
}
QSet<DayOfWeekSelector::Day> DayOfWeekSelector::SelectedDays() const
{
QSet<Day> result;
for (auto it = m_buttons.begin(); it != m_buttons.end(); ++it) {
if (it.value()->isChecked()) {
result.insert(it.key());
}
}
return result;
}
void DayOfWeekSelector::SetSelectedDays(const QSet<Day> &days)
{
for (auto it = m_buttons.begin(); it != m_buttons.end(); ++it) {
it.value()->setChecked(days.contains(it.key()));
}
}
QString DayOfWeekSelector::ToString(const QSet<Day> &days)
{
const QVector<QPair<Day, QString>> dayNames = {
{Monday, obs_module_text("AdvSceneSwitcher.day.monday")},
{Tuesday, obs_module_text("AdvSceneSwitcher.day.tuesday")},
{Wednesday, obs_module_text("AdvSceneSwitcher.day.wednesday")},
{Thursday, obs_module_text("AdvSceneSwitcher.day.thursday")},
{Friday, obs_module_text("AdvSceneSwitcher.day.friday")},
{Saturday, obs_module_text("AdvSceneSwitcher.day.saturday")},
{Sunday, obs_module_text("AdvSceneSwitcher.day.sunday")}};
if (days.isEmpty()) {
return obs_module_text("AdvSceneSwitcher.day.none");
}
if (days.size() == 7) {
return obs_module_text("AdvSceneSwitcher.day.any");
}
QStringList parts;
for (const auto &[day, name] : dayNames) {
if (days.contains(day)) {
parts << name;
}
}
return parts.join(", ");
}
void SaveSelectedDays(obs_data_t *settings,
const QSet<DayOfWeekSelector::Day> &days)
{
uint64_t mask = 0;
for (DayOfWeekSelector::Day day : days) {
mask |= (1ULL << static_cast<int>(day));
}
obs_data_set_int(settings, "selectedDays",
static_cast<long long>(mask));
}
QSet<DayOfWeekSelector::Day> LoadSelectedDays(obs_data_t *settings)
{
QSet<DayOfWeekSelector::Day> result;
uint64_t mask = static_cast<uint64_t>(
obs_data_get_int(settings, "selectedDays"));
for (int i = DayOfWeekSelector::Monday; i <= DayOfWeekSelector::Sunday;
++i) {
if (mask & (1ULL << i)) {
result.insert(static_cast<DayOfWeekSelector::Day>(i));
}
}
return result;
}
} // namespace advss

View File

@@ -1,45 +0,0 @@
#pragma once
#include <QWidget>
#include <QSet>
#include <obs-data.h>
class QPushButton;
namespace advss {
class DayOfWeekSelector : public QWidget {
Q_OBJECT
public:
enum Day {
Monday = 1,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
};
Q_ENUM(Day)
explicit DayOfWeekSelector(QWidget *parent = nullptr);
QSet<Day> SelectedDays() const;
void SetSelectedDays(const QSet<Day> &days);
static QString ToString(const QSet<Day> &days);
signals:
void SelectionChanged(const QSet<Day> &days);
private:
void OnButtonToggled(bool checked);
QMap<Day, QPushButton *> m_buttons;
};
void SaveSelectedDays(obs_data_t *settings,
const QSet<DayOfWeekSelector::Day> &days);
QSet<DayOfWeekSelector::Day> LoadSelectedDays(obs_data_t *settings);
} // namespace advss

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