mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 12:45:28 -05:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f35ef558ee | ||
|
|
d7ff9088f8 | ||
|
|
7048925d6e | ||
|
|
fcb3ea50d3 | ||
|
|
87c45e2b32 | ||
|
|
eb5046a9d6 | ||
|
|
58c05f3f6e | ||
|
|
0b284da3de | ||
|
|
03fe7016e4 | ||
|
|
2ea89912a3 | ||
|
|
78d2efa083 | ||
|
|
149fce1c2b | ||
|
|
2dd9120265 | ||
|
|
3a9f315b67 | ||
|
|
6ba01dca39 | ||
|
|
ad1f1effeb | ||
|
|
0a53a8649f | ||
|
|
7ea721f9f1 | ||
|
|
718a899a98 | ||
|
|
4c493451f4 | ||
|
|
a8d483f5a7 | ||
|
|
dc786a5313 | ||
|
|
628a4d896c | ||
|
|
74a9681c8e | ||
|
|
faf52c38bb | ||
|
|
bad1a548fb | ||
|
|
f706416df5 | ||
|
|
c344c88acd | ||
|
|
2cd9e61717 |
@@ -6,18 +6,18 @@ You have the option to ...
|
||||
|
||||
Both methods require [CMake](https://cmake.org/download/).
|
||||
|
||||
The plugin can be compiled for OBS 27 and above, although using the latest version of OBS is recommended to support all features.
|
||||
The plugin can be compiled for OBS 31 and above, although using the latest version of OBS is recommended to support all features.
|
||||
|
||||
## Compiling in tree (recommended for development)
|
||||
This section assumes that you have a working [OBS Studio development environment](https://obsproject.com/wiki/Building-OBS-Studio).
|
||||
|
||||
Add the "SceneSwitcher" source directory to your obs-studio source directory under obs-studio/UI/frontend-plugins/:
|
||||
Add the "SceneSwitcher" source directory to your obs-studio source directory under obs-studio/plugins:
|
||||
```
|
||||
cd obs-studio/UI/frontend-plugins/
|
||||
cd obs-studio/plugins
|
||||
git clone --recursive https://github.com/WarmUpTill/SceneSwitcher.git
|
||||
```
|
||||
|
||||
Then modify the obs-studio/UI/frontend-plugins/CMakeLists.txt Example and add an entry for the scene switcher:
|
||||
Then modify the obs-studio/plugins/CMakeLists.txt and add an entry for the scene switcher:
|
||||
```
|
||||
add_subdirectory(SceneSwitcher)
|
||||
```
|
||||
|
||||
@@ -166,6 +166,7 @@ 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)
|
||||
|
||||
@@ -184,6 +185,8 @@ target_sources(
|
||||
lib/utils/canvas-helpers.hpp
|
||||
lib/utils/condition-logic.cpp
|
||||
lib/utils/condition-logic.hpp
|
||||
lib/utils/crash-handler.cpp
|
||||
lib/utils/crash-handler.hpp
|
||||
lib/utils/curl-helper.cpp
|
||||
lib/utils/curl-helper.hpp
|
||||
lib/utils/cursor-shape-changer.cpp
|
||||
|
||||
@@ -291,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.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.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.state.at="Am"
|
||||
AdvSceneSwitcher.condition.date.state.after="Nach"
|
||||
AdvSceneSwitcher.condition.date.state.before="Vor"
|
||||
@@ -309,12 +309,13 @@ AdvSceneSwitcher.condition.date.ignoreDate="Wenn diese Option nicht aktiviert is
|
||||
AdvSceneSwitcher.condition.date.ignoreTime="Wenn diese Option nicht aktiviert ist, wird die Zeitkomponente ignoriert"
|
||||
AdvSceneSwitcher.condition.date.showAdvancedSettings="Erweiterte Einstellungen anzeigen"
|
||||
AdvSceneSwitcher.condition.date.showSimpleSettings="Einfache Einstellungen anzeigen"
|
||||
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.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.sceneTransform="Szenenelement transformieren"
|
||||
AdvSceneSwitcher.condition.sceneTransform.getTransform="Transformation erhalten"
|
||||
AdvSceneSwitcher.condition.sceneTransform.condition.match="entspricht Transformation"
|
||||
@@ -429,7 +430,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.entry="Auf{{scenes}}{{actions}}{{sources}}"
|
||||
AdvSceneSwitcher.action.sceneVisibility.layout="Auf{{scenes}}{{actions}}{{sources}}"
|
||||
AdvSceneSwitcher.action.filter="Filter"
|
||||
AdvSceneSwitcher.action.filter.type.enable="Aktivieren"
|
||||
AdvSceneSwitcher.action.filter.type.disable="Deaktivieren"
|
||||
|
||||
@@ -160,6 +160,8 @@ 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."
|
||||
@@ -587,14 +589,6 @@ 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"
|
||||
@@ -605,12 +599,13 @@ 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.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.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.sceneTransform="Scene item transform"
|
||||
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
|
||||
AdvSceneSwitcher.condition.sceneTransform.getCurrentValue="Get current value"
|
||||
@@ -916,7 +911,9 @@ 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.entry="On{{scenes}}{{actions}}{{sources}}"
|
||||
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.filter="Filter"
|
||||
AdvSceneSwitcher.action.filter.type.enable="Enable"
|
||||
AdvSceneSwitcher.action.filter.type.disable="Disable"
|
||||
@@ -1192,6 +1189,8 @@ 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"
|
||||
@@ -1214,8 +1213,8 @@ AdvSceneSwitcher.action.variable.currentSegmentValue="Current value:"
|
||||
AdvSceneSwitcher.action.variable.layout.other="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}{{tempVarsHelp}}{{sceneItemIndex}}{{direction}}{{stringLength}}{{paddingCharSelection}}{{caseType}}{{jsonQuery}}{{jsonQueryHelp}}{{jsonIndex}}"
|
||||
AdvSceneSwitcher.action.variable.layout.pad="{{actions}}of{{variables}}to length{{stringLength}}by adding{{paddingCharSelection}}to the{{direction}}"
|
||||
AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}of{{variables}}to length{{stringLength}}by removing characters from the{{direction}}"
|
||||
AdvSceneSwitcher.action.variable.layout.substringIndex="Substring start:{{subStringStart}}Substring size:{{subStringSize}}"
|
||||
AdvSceneSwitcher.action.variable.layout.substringRegex="Assign value of{{regexMatchIdx}}match using regular expression:"
|
||||
AdvSceneSwitcher.action.variable.layout.substringIndex="Substring start:{{subStringStart}}Substring size:{{subStringSize}}{{subStringRegex}}"
|
||||
AdvSceneSwitcher.action.variable.layout.substringRegex="Assign value of{{regexMatchIdx}}match using regular expression:{{subStringRegex}}"
|
||||
AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}"
|
||||
AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}Use custom prompt{{inputPrompt}}"
|
||||
AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}Fill with placeholder{{inputPlaceholder}}"
|
||||
@@ -1390,6 +1389,8 @@ AdvSceneSwitcher.hotkey.macro.segment.remove="Remove selected macro segment"
|
||||
AdvSceneSwitcher.askBackup="Detected a new version of the Advanced Scene Switcher.\nShould a backup of the old settings be created?"
|
||||
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.close="Close"
|
||||
AdvSceneSwitcher.browse="Browse"
|
||||
|
||||
@@ -2149,6 +2150,8 @@ AdvSceneSwitcher.tempVar.window.window="Window title"
|
||||
AdvSceneSwitcher.tempVar.window.window.description="The window title of the matching window."
|
||||
AdvSceneSwitcher.tempVar.window.windowClass="Window class"
|
||||
AdvSceneSwitcher.tempVar.window.windowClass.description="The window class of the matching window."
|
||||
AdvSceneSwitcher.tempVar.window.windowText="Window text"
|
||||
AdvSceneSwitcher.tempVar.window.windowText.description="Text contained within the window (won't work on all window types)."
|
||||
|
||||
AdvSceneSwitcher.tempVar.timer.seconds="Seconds"
|
||||
AdvSceneSwitcher.tempVar.timer.minutes="Minutes"
|
||||
@@ -2464,6 +2467,16 @@ 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"
|
||||
|
||||
# This secion is copied from the OBS locale files
|
||||
|
||||
# OBS commonly shared locale
|
||||
|
||||
@@ -241,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.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.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.state.at="A las"
|
||||
AdvSceneSwitcher.condition.date.state.after="Después"
|
||||
AdvSceneSwitcher.condition.date.state.before="Antes"
|
||||
@@ -258,11 +258,12 @@ AdvSceneSwitcher.condition.date.ignoreDate="Si no se marca, se ignorará el comp
|
||||
AdvSceneSwitcher.condition.date.ignoreTime="Si no se marca, se ignorará el componente de tiempo"
|
||||
AdvSceneSwitcher.condition.date.showAdvancedSettings="Mostrar configuración avanzada"
|
||||
AdvSceneSwitcher.condition.date.showSimpleSettings="Mostrar configuración simple"
|
||||
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.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.sceneTransform="Transformar elemento de escena"
|
||||
AdvSceneSwitcher.condition.sceneTransform.getTransform="Obtener transformación"
|
||||
AdvSceneSwitcher.condition.sceneTransform.condition.match="coincide con la transformación"
|
||||
@@ -350,7 +351,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.entry="En{{scenes}}{{actions}}{{sources}}"
|
||||
AdvSceneSwitcher.action.sceneVisibility.layout="En{{scenes}}{{actions}}{{sources}}"
|
||||
AdvSceneSwitcher.action.filter="Filtro"
|
||||
AdvSceneSwitcher.action.filter.type.enable="Habilitar"
|
||||
AdvSceneSwitcher.action.filter.type.disable="Deshabilitar"
|
||||
|
||||
@@ -356,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.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.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.state.at="À"
|
||||
AdvSceneSwitcher.condition.date.state.after="Après"
|
||||
AdvSceneSwitcher.condition.date.state.before="Avant"
|
||||
@@ -374,11 +374,12 @@ AdvSceneSwitcher.condition.date.ignoreDate="Si non cochée, la composante date s
|
||||
AdvSceneSwitcher.condition.date.ignoreTime="Si non cochée, la composante heure sera ignorée"
|
||||
AdvSceneSwitcher.condition.date.showAdvancedSettings="Afficher les paramètres avancés"
|
||||
AdvSceneSwitcher.condition.date.showSimpleSettings="Afficher les paramètres simples"
|
||||
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.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.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"
|
||||
@@ -510,7 +511,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.entry="Sur{{scenes}}{{actions}}{{sources}}"
|
||||
AdvSceneSwitcher.action.sceneVisibility.layout="Sur{{scenes}}{{actions}}{{sources}}"
|
||||
AdvSceneSwitcher.action.filter="Filtre"
|
||||
AdvSceneSwitcher.action.filter.type.enable="Activer"
|
||||
AdvSceneSwitcher.action.filter.type.disable="Désactiver"
|
||||
@@ -701,8 +702,8 @@ AdvSceneSwitcher.action.variable.invalidSelection="Sélection invalide !"
|
||||
AdvSceneSwitcher.action.variable.actionNoVariableSupport="La récupération de valeurs de variables à partir d'actions %1 n'est pas prise en charge !"
|
||||
AdvSceneSwitcher.action.variable.conditionNoVariableSupport="La récupération de valeurs de variables à partir de conditions %1 n'est pas prise en charge !"
|
||||
AdvSceneSwitcher.action.variable.currentSegmentValue="Valeur actuelle :"
|
||||
AdvSceneSwitcher.action.variable.layout.substringIndex="Début de la sous-chaîne :{{subStringStart}}Taille de la sous-chaîne :{{subStringSize}}"
|
||||
AdvSceneSwitcher.action.variable.layout.substringRegex="Attribuer la valeur du match{{regexMatchIdx}}en utilisant une expression régulière :"
|
||||
AdvSceneSwitcher.action.variable.layout.substringIndex="Début de la sous-chaîne :{{subStringStart}}Taille de la sous-chaîne :{{subStringSize}}{{subStringRegex}}"
|
||||
AdvSceneSwitcher.action.variable.layout.substringRegex="Attribuer la valeur du match{{regexMatchIdx}}en utilisant une expression régulière:{{subStringRegex}}"
|
||||
AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}Utiliser un message personnalisé{{inputPrompt}}"
|
||||
AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}Remplir avec un indicateur de position{{inputPlaceholder}}"
|
||||
AdvSceneSwitcher.action.projector="Projecteur"
|
||||
|
||||
@@ -555,14 +555,14 @@ AdvSceneSwitcher.condition.replay.state.started="リプレイバッファを開
|
||||
AdvSceneSwitcher.condition.replay.state.saved="リプレイバッファを保存"
|
||||
; AdvSceneSwitcher.condition.replay.entry="{{state}}"
|
||||
AdvSceneSwitcher.condition.date="日付条件"
|
||||
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.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.state.at="At"
|
||||
AdvSceneSwitcher.condition.date.state.after="アフター"
|
||||
AdvSceneSwitcher.condition.date.state.before="前"
|
||||
@@ -573,12 +573,9 @@ AdvSceneSwitcher.condition.date.ignoreDate="チェックを外すと日付部分
|
||||
AdvSceneSwitcher.condition.date.ignoreTime="チェックを外すと時間要素は無視されます"
|
||||
AdvSceneSwitcher.condition.date.showAdvancedSettings="詳細設定を表示"
|
||||
AdvSceneSwitcher.condition.date.showSimpleSettings="簡単設定を表示"
|
||||
; 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.date.layout.repeat="{{repeat}}日付の一致ごとに{{duration}}繰り返します"
|
||||
AdvSceneSwitcher.condition.date.layout.pattern="現在の日付「{{currentDate}}」はパターン{{pattern}}と一致します"
|
||||
AdvSceneSwitcher.condition.date.layout.updateOnRepeat="{{updateOnRepeat}}繰り返しの場合、選択した日付を繰り返しの日付に更新します"
|
||||
; AdvSceneSwitcher.condition.sceneTransform="Scene item transform"
|
||||
; AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
|
||||
AdvSceneSwitcher.condition.sceneTransform.getCurrentValue="現在の値を取得"
|
||||
@@ -870,7 +867,6 @@ 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="無効化"
|
||||
@@ -1149,8 +1145,8 @@ AdvSceneSwitcher.action.variable.currentSegmentValue="現在値:"
|
||||
; AdvSceneSwitcher.action.variable.layout.other="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}{{tempVarsHelp}}{{sceneItemIndex}}{{direction}}{{stringLength}}{{paddingCharSelection}}"
|
||||
AdvSceneSwitcher.action.variable.layout.pad="{{actions}}の{{variables}}の長さを{{stringLength}}にするには、{{paddingCharSelection}}を{{direction}}に追加します。"
|
||||
AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}の{{variables}}の長さを{{stringLength}}にするには、{{direction}}から文字を削除します。"
|
||||
AdvSceneSwitcher.action.variable.layout.substringIndex="部分文字列の開始:{{subStringStart}}部分文字列のサイズ:{{subStringSize}}"
|
||||
AdvSceneSwitcher.action.variable.layout.substringRegex="正規表現を使用して{{regexMatchIdx}}一致の値を割り当てます:"
|
||||
AdvSceneSwitcher.action.variable.layout.substringIndex="部分文字列の開始:{{subStringStart}}部分文字列のサイズ:{{subStringSize}}{{subStringRegex}}"
|
||||
AdvSceneSwitcher.action.variable.layout.substringRegex="正規表現を使用して{{regexMatchIdx}}一致の値を割り当てます:{{subStringRegex}}"
|
||||
; AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}"
|
||||
AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}カスタム プロンプトを使用する{{inputPrompt}}"
|
||||
AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}プレースホルダーを入力{{inputPlaceholder}}"
|
||||
|
||||
@@ -502,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.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.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.state.at="Às"
|
||||
AdvSceneSwitcher.condition.date.state.after="Após"
|
||||
AdvSceneSwitcher.condition.date.state.before="Antes"
|
||||
@@ -520,12 +520,13 @@ AdvSceneSwitcher.condition.date.ignoreDate="Se desmarcado, o componente de data
|
||||
AdvSceneSwitcher.condition.date.ignoreTime="Se desmarcado, o componente de tempo será ignorado"
|
||||
AdvSceneSwitcher.condition.date.showAdvancedSettings="Mostrar configurações avançadas"
|
||||
AdvSceneSwitcher.condition.date.showSimpleSettings="Mostrar configurações simples"
|
||||
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.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.sceneTransform="Transformação do item de cena"
|
||||
AdvSceneSwitcher.condition.sceneTransform.getTransform="Obter transformação"
|
||||
AdvSceneSwitcher.condition.sceneTransform.getCurrentValue="Obter valor atual"
|
||||
@@ -781,7 +782,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.entry="Em{{scenes}}{{actions}}{{sources}}"
|
||||
AdvSceneSwitcher.action.sceneVisibility.layout="Em{{scenes}}{{actions}}{{sources}}"
|
||||
AdvSceneSwitcher.action.filter="Filtro"
|
||||
AdvSceneSwitcher.action.filter.type.enable="Habilitar"
|
||||
AdvSceneSwitcher.action.filter.type.disable="Desabilitar"
|
||||
@@ -1030,8 +1031,8 @@ AdvSceneSwitcher.action.variable.conditionNoVariableSupport="Obter valores de va
|
||||
AdvSceneSwitcher.action.variable.currentSegmentValue="Valor atual:"
|
||||
AdvSceneSwitcher.action.variable.layout.pad="{{actions}}de{{variables}}para comprimento{{stringLength}}adicionando{{paddingCharSelection}}ao{{direction}}"
|
||||
AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}de{{variables}}para comprimento{{stringLength}}removendo caracteres da{{direction}}"
|
||||
AdvSceneSwitcher.action.variable.layout.substringIndex="Início da substring:{{subStringStart}}Tamanho da substring:{{subStringSize}}"
|
||||
AdvSceneSwitcher.action.variable.layout.substringRegex="Atribuir valor do{{regexMatchIdx}}correspondência usando expressão regular:"
|
||||
AdvSceneSwitcher.action.variable.layout.substringIndex="Início da substring:{{subStringStart}}Tamanho da substring:{{subStringSize}}{{subStringRegex}}"
|
||||
AdvSceneSwitcher.action.variable.layout.substringRegex="Atribuir valor do{{regexMatchIdx}}correspondência usando expressão regular:{{subStringRegex}}"
|
||||
AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}"
|
||||
AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}Usar prompt personalizado{{inputPrompt}}"
|
||||
AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}Preencher com placeholder{{inputPlaceholder}}"
|
||||
|
||||
@@ -281,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.entry="Açık{{scenes}}{{actions}}{{sources}}"
|
||||
AdvSceneSwitcher.action.sceneVisibility.layout="Açık{{scenes}}{{actions}}{{sources}}"
|
||||
AdvSceneSwitcher.action.filter="Filtrele"
|
||||
AdvSceneSwitcher.action.filter.type.enable="Etkin"
|
||||
AdvSceneSwitcher.action.filter.type.disable="Etkisiz"
|
||||
|
||||
@@ -530,14 +530,14 @@ AdvSceneSwitcher.condition.replay.state.started="回放缓存已启动"
|
||||
AdvSceneSwitcher.condition.replay.state.saved="已保存回放缓存"
|
||||
AdvSceneSwitcher.condition.replay.entry="{{state}}"
|
||||
AdvSceneSwitcher.condition.date="日期"
|
||||
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.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.state.at="现在"
|
||||
AdvSceneSwitcher.condition.date.state.after="之后"
|
||||
AdvSceneSwitcher.condition.date.state.before="之前"
|
||||
@@ -548,12 +548,13 @@ AdvSceneSwitcher.condition.date.ignoreDate="如果未选中,日期组件将被
|
||||
AdvSceneSwitcher.condition.date.ignoreTime="如果未选中,时间组件将被忽略"
|
||||
AdvSceneSwitcher.condition.date.showAdvancedSettings="显示高级设置"
|
||||
AdvSceneSwitcher.condition.date.showSimpleSettings="显示简单设置"
|
||||
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.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.sceneTransform="场景项目变换"
|
||||
AdvSceneSwitcher.condition.sceneTransform.getTransform="获取变换"
|
||||
AdvSceneSwitcher.condition.sceneTransform.getCurrentValue="获取当前值"
|
||||
@@ -840,7 +841,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="隐藏"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.toggle="切换可见性"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.source="源"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="任何"
|
||||
AdvSceneSwitcher.action.sceneVisibility.entry="{{scenes}}{{actions}}{{sources}}"
|
||||
AdvSceneSwitcher.action.sceneVisibility.layout="{{scenes}}{{actions}}{{sources}}"
|
||||
AdvSceneSwitcher.action.filter="滤镜"
|
||||
AdvSceneSwitcher.action.filter.type.enable="开启"
|
||||
AdvSceneSwitcher.action.filter.type.disable="关闭"
|
||||
@@ -1100,8 +1101,8 @@ AdvSceneSwitcher.action.variable.conditionNoVariableSupport="不支持从 %1 条
|
||||
AdvSceneSwitcher.action.variable.currentSegmentValue="当前值:"
|
||||
AdvSceneSwitcher.action.variable.layout.pad="{{actions}}于{{variables}}的{{direction}}起,内容为{{paddingCharSelection}}将其变量值长度增加至{{stringLength}}."
|
||||
AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}于{{variables}}的{{direction}}起,将变量长度缩减至{{stringLength}}"
|
||||
AdvSceneSwitcher.action.variable.layout.substringIndex="截取字符串开始位置:{{subStringStart}} 截取字符串长度:{{subStringSize}}"
|
||||
AdvSceneSwitcher.action.variable.layout.substringRegex="使用正则表达式为 {{regexMatchIdx}} 匹配的值:"
|
||||
AdvSceneSwitcher.action.variable.layout.substringIndex="截取字符串开始位置:{{subStringStart}} 截取字符串长度:{{subStringSize}}{{subStringRegex}}"
|
||||
AdvSceneSwitcher.action.variable.layout.substringRegex="使用正则表达式为 {{regexMatchIdx}} 匹配的值:{{subStringRegex}}"
|
||||
AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}"
|
||||
AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}使用自定义提示{{inputPrompt}}"
|
||||
AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}用占位符填充{{inputPlaceholder}}"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "advanced-scene-switcher.hpp"
|
||||
#include "backup.hpp"
|
||||
#include "crash-handler.hpp"
|
||||
#include "log-helper.hpp"
|
||||
#include "macro-helpers.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
@@ -191,25 +192,21 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
|
||||
|
||||
switcher->m.lock();
|
||||
if (switcher->VersionChanged(data, g_GIT_SHA1)) {
|
||||
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();
|
||||
AskForBackup(data);
|
||||
}
|
||||
|
||||
switcher->LoadSettings(data);
|
||||
switcher->m.unlock();
|
||||
|
||||
if (!switcher->stop) {
|
||||
switcher->Start();
|
||||
if (switcher->stop) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShouldSkipPluginStartOnUncleanShutdown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switcher->Start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -219,6 +219,43 @@ void MacroActionVariable::GenerateRandomNumber(Variable *var)
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionVariable::PickRandomValue(Variable *var)
|
||||
{
|
||||
static std::random_device rd;
|
||||
static std::mt19937 gen(rd());
|
||||
static const uint8_t maxIter = 255;
|
||||
|
||||
if (_randomValues.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_randomValues.size() == 1) {
|
||||
const auto &value = _randomValues.at(0);
|
||||
var->SetValue(value);
|
||||
_lastRandomValue = value;
|
||||
}
|
||||
|
||||
std::uniform_int_distribution<int> dis(0, _randomValues.size() - 1);
|
||||
StringVariable value = _randomValues.at(dis(gen));
|
||||
|
||||
uint8_t iter = 0;
|
||||
for (; !_allowRepeatValues && iter < maxIter &&
|
||||
_lastRandomValue.has_value() &&
|
||||
std::string(value) == _lastRandomValue.value();
|
||||
iter++) {
|
||||
value = _randomValues.at(dis(gen));
|
||||
}
|
||||
|
||||
if (iter == maxIter) {
|
||||
blog(LOG_INFO,
|
||||
"giving up picking non-repeat random value after %d tries",
|
||||
maxIter);
|
||||
}
|
||||
|
||||
var->SetValue(value);
|
||||
_lastRandomValue = value;
|
||||
}
|
||||
|
||||
struct AskForInputParams {
|
||||
AskForInputParams(const QString &prompt_, const QString &placeholder_)
|
||||
: prompt(prompt_),
|
||||
@@ -458,6 +495,9 @@ bool MacroActionVariable::PerformAction()
|
||||
case Action::RANDOM_NUMBER:
|
||||
GenerateRandomNumber(var.get());
|
||||
return true;
|
||||
case Action::RANDOM_LIST_VALUE:
|
||||
PickRandomValue(var.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -498,6 +538,8 @@ 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");
|
||||
|
||||
@@ -556,6 +598,8 @@ 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");
|
||||
|
||||
@@ -724,6 +768,8 @@ 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,
|
||||
@@ -801,11 +847,10 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
_segmentValueStatus(new QLabel()),
|
||||
_segmentValue(new ResizingPlainTextEdit(this, 10, 1, 1)),
|
||||
_substringLayout(new QVBoxLayout()),
|
||||
_subStringIndexEntryLayout(new QHBoxLayout()),
|
||||
_subStringRegexEntryLayout(new QHBoxLayout()),
|
||||
_subStringControlsLayout(new QHBoxLayout()),
|
||||
_subStringStart(new VariableSpinBox(this)),
|
||||
_subStringSize(new VariableSpinBox(this)),
|
||||
_substringRegex(new RegexConfigWidget(parent)),
|
||||
_subStringRegex(new RegexConfigWidget(parent)),
|
||||
_regexPattern(new ResizingPlainTextEdit(this, 10, 1, 1)),
|
||||
_regexMatchIdx(new VariableSpinBox(this)),
|
||||
_findReplaceLayout(new QHBoxLayout()),
|
||||
@@ -837,7 +882,13 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.variable.generateInteger"),
|
||||
this)),
|
||||
_randomLayout(new QVBoxLayout()),
|
||||
_randomNumberLayout(new QVBoxLayout()),
|
||||
_randomValues(new StringListEdit(this)),
|
||||
_allowRepeatValues(new QCheckBox(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.variable.type.allowRepeat"),
|
||||
this)),
|
||||
_randomValueLayout(new QVBoxLayout()),
|
||||
_jsonQuery(new VariableLineEdit(this)),
|
||||
_jsonQueryHelp(new HelpIcon(
|
||||
obs_module_text(
|
||||
@@ -873,6 +924,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
_randomNumberStart->setMaximum(9999999999);
|
||||
_randomNumberEnd->setMinimum(-9999999999);
|
||||
_randomNumberEnd->setMaximum(9999999999);
|
||||
_randomValues->SetMaxStringSize(99999999);
|
||||
_jsonIndex->setMaximum(999);
|
||||
|
||||
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
|
||||
@@ -901,7 +953,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
_subStringSize,
|
||||
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
|
||||
this, SLOT(SubStringSizeChanged(const NumberVariable<int> &)));
|
||||
QWidget::connect(_substringRegex,
|
||||
QWidget::connect(_subStringRegex,
|
||||
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
|
||||
SLOT(SubStringRegexChanged(const RegexConfig &)));
|
||||
QWidget::connect(_regexPattern, SIGNAL(textChanged()), this,
|
||||
@@ -961,6 +1013,11 @@ 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(
|
||||
@@ -975,9 +1032,6 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
{"{{strValue}}", _strValue},
|
||||
{"{{numValue}}", _numValue},
|
||||
{"{{segmentIndex}}", _segmentIdx},
|
||||
{"{{subStringStart}}", _subStringStart},
|
||||
{"{{subStringSize}}", _subStringSize},
|
||||
{"{{regexMatchIdx}}", _regexMatchIdx},
|
||||
{"{{findRegex}}", _findRegex},
|
||||
{"{{findStr}}", _findStr},
|
||||
{"{{replaceStr}}", _replaceStr},
|
||||
@@ -1009,12 +1063,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.variable.layout.substringIndex"),
|
||||
_subStringIndexEntryLayout, widgetPlaceholders);
|
||||
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.variable.layout.substringRegex"),
|
||||
_subStringRegexEntryLayout, widgetPlaceholders);
|
||||
_subStringControlsLayout, widgetPlaceholders);
|
||||
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
@@ -1035,15 +1084,17 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.variable.layout.randomNumber"),
|
||||
randomLayout, widgetPlaceholders);
|
||||
_randomLayout->addLayout(randomLayout);
|
||||
_randomLayout->addWidget(_generateInteger);
|
||||
_randomNumberLayout->addLayout(randomLayout);
|
||||
_randomNumberLayout->addWidget(_generateInteger);
|
||||
|
||||
_randomValueLayout->addWidget(_randomValues);
|
||||
_randomValueLayout->addWidget(_allowRepeatValues);
|
||||
|
||||
auto regexConfigLayout = new QHBoxLayout;
|
||||
regexConfigLayout->addWidget(_substringRegex);
|
||||
regexConfigLayout->addWidget(_subStringRegex);
|
||||
regexConfigLayout->addStretch();
|
||||
|
||||
_substringLayout->addLayout(_subStringIndexEntryLayout);
|
||||
_substringLayout->addLayout(_subStringRegexEntryLayout);
|
||||
_substringLayout->addLayout(_subStringControlsLayout);
|
||||
_substringLayout->addWidget(_regexPattern);
|
||||
_substringLayout->addLayout(regexConfigLayout);
|
||||
|
||||
@@ -1056,7 +1107,8 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
layout->addWidget(_mathExpressionResult);
|
||||
layout->addLayout(_promptLayout);
|
||||
layout->addLayout(_placeholderLayout);
|
||||
layout->addLayout(_randomLayout);
|
||||
layout->addLayout(_randomNumberLayout);
|
||||
layout->addLayout(_randomValueLayout);
|
||||
setLayout(layout);
|
||||
|
||||
_entryData = entryData;
|
||||
@@ -1090,7 +1142,7 @@ void MacroActionVariableEdit::UpdateEntryData()
|
||||
: MacroSegmentSelection::Type::ACTION);
|
||||
_subStringStart->SetValue(_entryData->_subStringStart);
|
||||
_subStringSize->SetValue(_entryData->_subStringSize);
|
||||
_substringRegex->SetRegexConfig(_entryData->_subStringRegex);
|
||||
_subStringRegex->SetRegexConfig(_entryData->_subStringRegex);
|
||||
_findRegex->SetRegexConfig(_entryData->_findRegex);
|
||||
_regexPattern->setPlainText(
|
||||
QString::fromStdString(_entryData->_regexPattern));
|
||||
@@ -1115,6 +1167,8 @@ 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);
|
||||
|
||||
@@ -1457,6 +1511,18 @@ 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();
|
||||
@@ -1495,8 +1561,20 @@ void MacroActionVariableEdit::SetWidgetVisibility()
|
||||
{"{{jsonQuery}}", _jsonQuery},
|
||||
{"{{jsonQueryHelp}}", _jsonQueryHelp},
|
||||
{"{{jsonIndex}}", _jsonIndex},
|
||||
{"{{subStringRegex}}", _subStringRegex},
|
||||
{"{{subStringStart}}", _subStringStart},
|
||||
{"{{subStringSize}}", _subStringSize},
|
||||
{"{{regexMatchIdx}}", _regexMatchIdx},
|
||||
};
|
||||
|
||||
for (const auto &[_, widget] : widgetPlaceholders) {
|
||||
_entryLayout->removeWidget(widget);
|
||||
_subStringControlsLayout->removeWidget(widget);
|
||||
}
|
||||
|
||||
ClearLayout(_entryLayout);
|
||||
ClearLayout(_subStringControlsLayout);
|
||||
|
||||
const char *layoutString = "";
|
||||
if (_entryData->_action == MacroActionVariable::Action::PAD) {
|
||||
layoutString = obs_module_text(
|
||||
@@ -1510,11 +1588,6 @@ void MacroActionVariableEdit::SetWidgetVisibility()
|
||||
"AdvSceneSwitcher.action.variable.layout.other");
|
||||
}
|
||||
|
||||
for (const auto &[_, widget] : widgetPlaceholders) {
|
||||
_entryLayout->removeWidget(widget);
|
||||
}
|
||||
|
||||
ClearLayout(_entryLayout);
|
||||
PlaceWidgets(layoutString, _entryLayout, widgetPlaceholders);
|
||||
|
||||
if (_entryData->_action == MacroActionVariable::Action::SET_VALUE ||
|
||||
@@ -1561,15 +1634,25 @@ void MacroActionVariableEdit::SetWidgetVisibility()
|
||||
MacroActionVariable::Action::SET_ACTION_VALUE ||
|
||||
_entryData->_action ==
|
||||
MacroActionVariable::Action::SET_CONDITION_VALUE);
|
||||
|
||||
bool showRegex = _entryData->_subStringRegex.Enabled();
|
||||
layoutString =
|
||||
showRegex
|
||||
? "AdvSceneSwitcher.action.variable.layout.substringRegex"
|
||||
: "AdvSceneSwitcher.action.variable.layout.substringIndex";
|
||||
PlaceWidgets(obs_module_text(layoutString), _subStringControlsLayout,
|
||||
widgetPlaceholders);
|
||||
_subStringStart->setVisible(!showRegex);
|
||||
_subStringSize->setVisible(!showRegex);
|
||||
_regexMatchIdx->setVisible(showRegex);
|
||||
|
||||
SetLayoutVisible(_substringLayout,
|
||||
_entryData->_action ==
|
||||
MacroActionVariable::Action::SUBSTRING);
|
||||
if (_entryData->_action == MacroActionVariable::Action::SUBSTRING) {
|
||||
bool showRegex = _entryData->_subStringRegex.Enabled();
|
||||
SetLayoutVisible(_subStringIndexEntryLayout, !showRegex);
|
||||
SetLayoutVisible(_subStringRegexEntryLayout, showRegex);
|
||||
_regexPattern->setVisible(showRegex);
|
||||
}
|
||||
_regexPattern->setVisible(
|
||||
showRegex &&
|
||||
_entryData->_action == MacroActionVariable::Action::SUBSTRING);
|
||||
|
||||
SetLayoutVisible(_findReplaceLayout,
|
||||
_entryData->_action ==
|
||||
MacroActionVariable::Action::FIND_AND_REPLACE);
|
||||
@@ -1634,9 +1717,13 @@ void MacroActionVariableEdit::SetWidgetVisibility()
|
||||
MacroActionVariable::Action::PAD);
|
||||
_caseType->setVisible(_entryData->_action ==
|
||||
MacroActionVariable::Action::CHANGE_CASE);
|
||||
SetLayoutVisible(_randomLayout,
|
||||
SetLayoutVisible(_randomNumberLayout,
|
||||
_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 ==
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#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"
|
||||
@@ -56,6 +57,7 @@ public:
|
||||
QUERY_JSON,
|
||||
ARRAY_JSON,
|
||||
COPY_VAR,
|
||||
RANDOM_LIST_VALUE,
|
||||
};
|
||||
|
||||
Action _action = Action::SET_VALUE;
|
||||
@@ -107,6 +109,10 @@ 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;
|
||||
|
||||
@@ -119,6 +125,7 @@ private:
|
||||
void HandleCaseChange(Variable *);
|
||||
void SetToSceneItemName(Variable *);
|
||||
void GenerateRandomNumber(Variable *);
|
||||
void PickRandomValue(Variable *);
|
||||
|
||||
std::weak_ptr<MacroSegment> _macroSegment;
|
||||
int _segmentIdxLoadValue = -1;
|
||||
@@ -175,6 +182,8 @@ 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> &);
|
||||
|
||||
@@ -194,11 +203,10 @@ private:
|
||||
QLabel *_segmentValueStatus;
|
||||
ResizingPlainTextEdit *_segmentValue;
|
||||
QVBoxLayout *_substringLayout;
|
||||
QHBoxLayout *_subStringIndexEntryLayout;
|
||||
QHBoxLayout *_subStringRegexEntryLayout;
|
||||
QHBoxLayout *_subStringControlsLayout;
|
||||
VariableSpinBox *_subStringStart;
|
||||
VariableSpinBox *_subStringSize;
|
||||
RegexConfigWidget *_substringRegex;
|
||||
RegexConfigWidget *_subStringRegex;
|
||||
ResizingPlainTextEdit *_regexPattern;
|
||||
VariableSpinBox *_regexMatchIdx;
|
||||
QHBoxLayout *_findReplaceLayout;
|
||||
@@ -225,7 +233,10 @@ private:
|
||||
VariableDoubleSpinBox *_randomNumberStart;
|
||||
VariableDoubleSpinBox *_randomNumberEnd;
|
||||
QCheckBox *_generateInteger;
|
||||
QVBoxLayout *_randomLayout;
|
||||
QVBoxLayout *_randomNumberLayout;
|
||||
StringListEdit *_randomValues;
|
||||
QCheckBox *_allowRepeatValues;
|
||||
QVBoxLayout *_randomValueLayout;
|
||||
VariableLineEdit *_jsonQuery;
|
||||
QLabel *_jsonQueryHelp;
|
||||
VariableSpinBox *_jsonIndex;
|
||||
|
||||
@@ -242,6 +242,11 @@ bool RunMacroActions(Macro *macro)
|
||||
return macro && macro->PerformActions(true);
|
||||
}
|
||||
|
||||
bool RunMacroElseActions(Macro *macro)
|
||||
{
|
||||
return macro && macro->PerformActions(false);
|
||||
}
|
||||
|
||||
void ResetMacroConditionTimers(Macro *macro)
|
||||
{
|
||||
if (!macro) {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
|
||||
struct obs_data;
|
||||
@@ -69,6 +68,7 @@ EXPORT void AddMacroHelperThread(Macro *, std::thread &&);
|
||||
EXPORT bool CheckMacros();
|
||||
|
||||
EXPORT bool RunMacroActions(Macro *);
|
||||
bool RunMacroElseActions(Macro *);
|
||||
EXPORT bool RunMacros();
|
||||
void StopAllMacros();
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace advss {
|
||||
|
||||
static QObject *addPulse = nullptr;
|
||||
static QTimer onChangeHighlightTimer;
|
||||
static std::chrono::high_resolution_clock::time_point
|
||||
lastOnChangeHighlightCheckTime{};
|
||||
|
||||
static void disableAddButtonHighlight()
|
||||
{
|
||||
@@ -550,10 +552,14 @@ void AdvSceneSwitcher::HighlightOnChange() const
|
||||
return;
|
||||
}
|
||||
|
||||
if (macro->OnChangePreventedActionsRecently()) {
|
||||
if (macro->OnChangePreventedActionsSince(
|
||||
lastOnChangeHighlightCheckTime)) {
|
||||
HighlightWidget(ui->runMacroOnChange, Qt::yellow,
|
||||
Qt::transparent, true);
|
||||
}
|
||||
|
||||
lastOnChangeHighlightCheckTime =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_macroSettings_clicked()
|
||||
@@ -652,6 +658,8 @@ 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()));
|
||||
|
||||
@@ -5,18 +5,17 @@
|
||||
#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>);
|
||||
|
||||
@@ -62,12 +61,11 @@ MacroTreeItem::MacroTreeItem(MacroTree *tree, std::shared_ptr<Macro> macroItem,
|
||||
|
||||
_boxLayout = new QHBoxLayout();
|
||||
_boxLayout->setContentsMargins(0, 0, 0, 0);
|
||||
_boxLayout->addWidget(_running);
|
||||
if (isGroup) {
|
||||
_boxLayout->addWidget(_iconLabel);
|
||||
_boxLayout->addSpacing(2);
|
||||
_running->hide();
|
||||
}
|
||||
_boxLayout->addWidget(_running);
|
||||
_boxLayout->addWidget(_label);
|
||||
#ifdef __APPLE__
|
||||
/* Hack: Fixes a bug where scrollbars would be above the lock icon */
|
||||
@@ -77,29 +75,138 @@ MacroTreeItem::MacroTreeItem(MacroTree *tree, std::shared_ptr<Macro> macroItem,
|
||||
Update(true);
|
||||
setLayout(_boxLayout);
|
||||
|
||||
auto setRunning = [this](bool val) {
|
||||
_macro->SetPaused(!val);
|
||||
};
|
||||
connect(_running, &QAbstractButton::clicked, setRunning);
|
||||
connect(_running, SIGNAL(clicked(bool)), this,
|
||||
SLOT(RunningClicked(bool)));
|
||||
connect(MacroSignalManager::Instance(), SIGNAL(HighlightChanged(bool)),
|
||||
this, SLOT(EnableHighlight(bool)));
|
||||
connect(MacroSignalManager::Instance(),
|
||||
SIGNAL(Rename(const QString &, const QString &)), this,
|
||||
SLOT(MacroRenamed(const QString &, const QString &)));
|
||||
connect(&_timer, SIGNAL(timeout()), this, SLOT(HighlightIfExecuted()));
|
||||
connect(&_timer, SIGNAL(timeout()), this, SLOT(UpdatePaused()));
|
||||
connect(&_timer, SIGNAL(timeout()), this, SLOT(UpdateRunning()));
|
||||
|
||||
UpdateRunning();
|
||||
_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;
|
||||
}
|
||||
|
||||
void MacroTreeItem::UpdatePaused()
|
||||
void MacroTreeItem::RunningClicked(bool running)
|
||||
{
|
||||
const auto updateWidget = [this](Macro *macro) {
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ¯os = GetTopLevelMacros();
|
||||
|
||||
bool found = false;
|
||||
int idx = 0;
|
||||
for (const auto &m : macros) {
|
||||
if (m.get() == macro) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto widget = _tree->GetItemWidget(idx);
|
||||
if (widget) {
|
||||
widget->UpdateRunning();
|
||||
}
|
||||
};
|
||||
|
||||
if (!_macro->IsGroup()) {
|
||||
_macro->SetPaused(!running);
|
||||
if (!_macro->IsSubitem()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto group = _macro->Parent();
|
||||
updateWidget(group.get());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto macros = GetGroupMacroEntries(_macro.get());
|
||||
for (const auto ¯o : macros) {
|
||||
macro->SetPaused(!running);
|
||||
}
|
||||
|
||||
// Update backend values before updating UI to prevent flickering in
|
||||
// running state of the group
|
||||
for (const auto ¯o : macros) {
|
||||
updateWidget(macro.get());
|
||||
}
|
||||
UpdateRunning();
|
||||
}
|
||||
|
||||
void MacroTreeItem::UpdateRunning()
|
||||
{
|
||||
const QSignalBlocker blocker(_running);
|
||||
_running->setChecked(!_macro->Paused());
|
||||
|
||||
if (!_macro->IsGroup()) {
|
||||
_running->setChecked(!_macro->Paused());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto macros = GetGroupMacroEntries(_macro.get());
|
||||
bool allRunning = true;
|
||||
bool allPaused = true;
|
||||
for (const auto ¯o : macros) {
|
||||
if (macro->Paused()) {
|
||||
allRunning = false;
|
||||
} else {
|
||||
allPaused = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allRunning) {
|
||||
_running->setCheckState(Qt::Checked);
|
||||
return;
|
||||
}
|
||||
|
||||
if (allPaused) {
|
||||
_running->setCheckState(Qt::Unchecked);
|
||||
return;
|
||||
}
|
||||
|
||||
_running->setCheckState(Qt::PartiallyChecked);
|
||||
}
|
||||
|
||||
void MacroTreeItem::HighlightIfExecuted()
|
||||
@@ -108,10 +215,19 @@ 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->OnChangePreventedActionsSince(_lastHighlightCheckTime)) {
|
||||
HighlightWidget(this, Qt::yellow, QColor(0, 0, 0, 0), true);
|
||||
}
|
||||
|
||||
_lastHighlightCheckTime = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
@@ -188,7 +304,7 @@ void MacroTreeItem::Update(bool force)
|
||||
_expand->blockSignals(true);
|
||||
_expand->setChecked(_macro->IsCollapsed());
|
||||
_expand->blockSignals(false);
|
||||
connect(_expand, &QPushButton::toggled, this,
|
||||
connect(_expand, &QCheckBox::toggled, this,
|
||||
&MacroTreeItem::ExpandClicked);
|
||||
} else {
|
||||
_spacer = new QSpacerItem(3, 1);
|
||||
@@ -214,7 +330,6 @@ void MacroTreeModel::Reset(std::deque<std::shared_ptr<Macro>> &newItems)
|
||||
_macros = newItems;
|
||||
endResetModel();
|
||||
|
||||
UpdateGroupState(false);
|
||||
_mt->ResetWidgets();
|
||||
}
|
||||
|
||||
@@ -459,9 +574,6 @@ void MacroTreeModel::Remove(std::shared_ptr<Macro> item)
|
||||
|
||||
_mt->selectionModel()->clear();
|
||||
|
||||
if (isGroup) {
|
||||
UpdateGroupState(true);
|
||||
}
|
||||
assert(IsInValidState());
|
||||
}
|
||||
|
||||
@@ -550,7 +662,6 @@ MacroTreeModel::MacroTreeModel(MacroTree *st_,
|
||||
_mt(st_),
|
||||
_macros(macros)
|
||||
{
|
||||
UpdateGroupState(false);
|
||||
}
|
||||
|
||||
int MacroTreeModel::rowCount(const QModelIndex &parent) const
|
||||
@@ -704,7 +815,6 @@ void MacroTreeModel::GroupSelectedItems(QModelIndexList &indices)
|
||||
offset++;
|
||||
}
|
||||
|
||||
_hasGroups = true;
|
||||
_mt->selectionModel()->clear();
|
||||
|
||||
Reset(_macros);
|
||||
@@ -762,24 +872,6 @@ void MacroTreeModel::CollapseGroup(std::shared_ptr<Macro> item)
|
||||
assert(IsInValidState());
|
||||
}
|
||||
|
||||
void MacroTreeModel::UpdateGroupState(bool update)
|
||||
{
|
||||
bool nowHasGroups = false;
|
||||
for (auto &item : _macros) {
|
||||
if (item->IsGroup()) {
|
||||
nowHasGroups = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nowHasGroups != _hasGroups) {
|
||||
_hasGroups = nowHasGroups;
|
||||
if (update) {
|
||||
_mt->UpdateWidgets(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MacroTree::Reset(std::deque<std::shared_ptr<Macro>> ¯os,
|
||||
bool highlight)
|
||||
{
|
||||
@@ -842,7 +934,6 @@ MacroTree::MacroTree(QWidget *parent_) : QListView(parent_)
|
||||
void MacroTree::ResetWidgets()
|
||||
{
|
||||
MacroTreeModel *mtm = GetModel();
|
||||
mtm->UpdateGroupState(false);
|
||||
int modelIdx = 0;
|
||||
for (int i = 0; i < (int)mtm->_macros.size(); i++) {
|
||||
QModelIndex index = mtm->createIndex(modelIdx, 0, nullptr);
|
||||
|
||||
@@ -34,10 +34,14 @@ 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);
|
||||
void UpdatePaused();
|
||||
void RunningClicked(bool);
|
||||
void UpdateRunning();
|
||||
void HighlightIfExecuted();
|
||||
void MacroRenamed(const QString &, const QString &);
|
||||
|
||||
@@ -106,7 +110,6 @@ private:
|
||||
void UngroupSelectedGroups(QModelIndexList &indices);
|
||||
void ExpandGroup(std::shared_ptr<Macro> item);
|
||||
void CollapseGroup(std::shared_ptr<Macro> item);
|
||||
void UpdateGroupState(bool update);
|
||||
int GetItemMacroIndex(const std::shared_ptr<Macro> &item) const;
|
||||
int GetItemModelIndex(const std::shared_ptr<Macro> &item) const;
|
||||
bool IsLastItem(std::shared_ptr<Macro> item) const;
|
||||
@@ -114,7 +117,6 @@ private:
|
||||
|
||||
MacroTree *_mt;
|
||||
std::deque<std::shared_ptr<Macro>> &_macros;
|
||||
bool _hasGroups = false;
|
||||
|
||||
friend class MacroTree;
|
||||
friend class MacroTreeItem;
|
||||
|
||||
111
lib/macro/macro-websocket-trigger.cpp
Normal file
111
lib/macro/macro-websocket-trigger.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#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
|
||||
@@ -242,8 +242,12 @@ bool Macro::CheckConditions(bool ignorePause)
|
||||
vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched);
|
||||
|
||||
_conditionSateChanged = _lastMatched != _matched;
|
||||
if (!_conditionSateChanged && _performActionsOnChange) {
|
||||
_onPreventedActionExecution = true;
|
||||
const bool hasActionsToExecute = _matched ? (_actions.size() > 0)
|
||||
: (_elseActions.size() > 0);
|
||||
if (!_conditionSateChanged && _performActionsOnChange &&
|
||||
hasActionsToExecute) {
|
||||
_lastOnChangeActionsPreventedTime =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
_lastMatched = _matched;
|
||||
@@ -302,6 +306,16 @@ bool Macro::WasExecutedSince(const TimePoint &time) const
|
||||
return _lastExecutionTime > time;
|
||||
}
|
||||
|
||||
bool Macro::OnChangePreventedActionsSince(const TimePoint &time) const
|
||||
{
|
||||
return _lastOnChangeActionsPreventedTime > time;
|
||||
}
|
||||
|
||||
Macro::TimePoint Macro::GetLastExecutionTime() const
|
||||
{
|
||||
return _lastExecutionTime;
|
||||
}
|
||||
|
||||
bool Macro::ConditionsShouldBeChecked() const
|
||||
{
|
||||
if (!_useCustomConditionCheckInterval) {
|
||||
@@ -986,18 +1000,8 @@ 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();
|
||||
}
|
||||
|
||||
@@ -137,7 +137,8 @@ public:
|
||||
const QList<int> &GetElseActionSplitterPosition() const;
|
||||
bool HasValidSplitterPositions() const;
|
||||
bool WasExecutedSince(const TimePoint &) const;
|
||||
bool OnChangePreventedActionsRecently();
|
||||
bool OnChangePreventedActionsSince(const TimePoint &) const;
|
||||
TimePoint GetLastExecutionTime() const;
|
||||
void ResetUIHelpers();
|
||||
|
||||
// Hotkeys
|
||||
@@ -168,6 +169,7 @@ private:
|
||||
TimePoint _lastCheckTime{};
|
||||
TimePoint _lastUnpauseTime{};
|
||||
TimePoint _lastExecutionTime{};
|
||||
TimePoint _lastOnChangeActionsPreventedTime{};
|
||||
std::vector<std::thread> _helperThreads;
|
||||
|
||||
std::deque<std::shared_ptr<MacroCondition>> _conditions;
|
||||
@@ -204,9 +206,6 @@ private:
|
||||
|
||||
MacroInputVariables _inputVariables;
|
||||
|
||||
// UI helpers
|
||||
bool _onPreventedActionExecution = false;
|
||||
|
||||
QList<int> _actionConditionSplitterPosition;
|
||||
QList<int> _elseActionSplitterPosition;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "auto-update-tooltip-label.hpp"
|
||||
|
||||
#include <QToolTip>
|
||||
|
||||
namespace advss {
|
||||
|
||||
AutoUpdateHelpIcon::AutoUpdateHelpIcon(
|
||||
@@ -16,6 +18,7 @@ AutoUpdateHelpIcon::AutoUpdateHelpIcon(
|
||||
|
||||
void AutoUpdateHelpIcon::enterEvent(QEnterEvent *event)
|
||||
{
|
||||
UpdateTooltip();
|
||||
_timer->start(_updateIntervalMs);
|
||||
QLabel::enterEvent(event);
|
||||
}
|
||||
@@ -28,7 +31,12 @@ void AutoUpdateHelpIcon::leaveEvent(QEvent *event)
|
||||
|
||||
void AutoUpdateHelpIcon::UpdateTooltip()
|
||||
{
|
||||
setToolTip(_callback());
|
||||
if (!underMouse()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString text = _callback();
|
||||
QToolTip::showText(QCursor::pos(), text, this);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -8,13 +8,18 @@
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <obs-module.h>
|
||||
#include <QFileDialog>
|
||||
#include <QTextStream>
|
||||
#include <util/config-file.h>
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QMainWindow>
|
||||
#include <QTextStream>
|
||||
#include <QTimer>
|
||||
|
||||
#include <thread>
|
||||
|
||||
namespace advss {
|
||||
|
||||
void AskForBackup(const QString &json)
|
||||
static void showBackupDialogs(const QString &json)
|
||||
{
|
||||
const bool backupWasConfirmed = DisplayMessage(
|
||||
obs_module_text("AdvSceneSwitcher.askBackup"), true, false);
|
||||
@@ -42,6 +47,40 @@ void AskForBackup(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.
|
||||
//
|
||||
// On macOS, an additional QTimer::singleShot wrapper is required for
|
||||
// this to work correctly.
|
||||
|
||||
auto json = obs_data_get_json(settings);
|
||||
static QString jsonQString = json ? json : "";
|
||||
|
||||
static const auto askForBackupWrapper = [](void *) {
|
||||
#ifdef __APPLE__
|
||||
QTimer::singleShot(0,
|
||||
static_cast<QMainWindow *>(
|
||||
obs_frontend_get_main_window()),
|
||||
[]() {
|
||||
#endif
|
||||
showBackupDialogs(jsonQString);
|
||||
#ifdef __APPLE__
|
||||
});
|
||||
#endif
|
||||
};
|
||||
|
||||
std::thread t([]() {
|
||||
obs_queue_task(OBS_TASK_UI, askForBackupWrapper, nullptr,
|
||||
false);
|
||||
});
|
||||
t.detach();
|
||||
}
|
||||
|
||||
void BackupSettingsOfCurrentVersion()
|
||||
{
|
||||
auto sceneCollectionName = obs_frontend_get_current_scene_collection();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <obs-data.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
void AskForBackup(const QString &json);
|
||||
void AskForBackup(obs_data_t *settings);
|
||||
void BackupSettingsOfCurrentVersion();
|
||||
|
||||
} // namespace advss
|
||||
|
||||
153
lib/utils/crash-handler.cpp
Normal file
153
lib/utils/crash-handler.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
#include "crash-handler.hpp"
|
||||
#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 <QDir>
|
||||
#include <QFile>
|
||||
#include <QMainWindow>
|
||||
#include <QTimer>
|
||||
|
||||
#include <thread>
|
||||
|
||||
namespace advss {
|
||||
|
||||
static constexpr std::string_view sentinel = ".running";
|
||||
|
||||
#ifndef NDEBUG
|
||||
static constexpr bool handleUncleanShutdown = false;
|
||||
#else
|
||||
static constexpr bool handleUncleanShutdown = true;
|
||||
#endif
|
||||
|
||||
static bool wasCleanShutdown = false;
|
||||
|
||||
static void setup();
|
||||
static bool setupDone = []() {
|
||||
AddPluginInitStep(setup);
|
||||
return true;
|
||||
}();
|
||||
|
||||
static void handleShutdown(enum obs_frontend_event event, void *)
|
||||
{
|
||||
if (event != OBS_FRONTEND_EVENT_EXIT) {
|
||||
return;
|
||||
}
|
||||
|
||||
char *sentinelFile = obs_module_config_path(sentinel.data());
|
||||
if (!sentinelFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFile file(sentinelFile);
|
||||
if (!file.exists()) {
|
||||
bfree(sentinelFile);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.remove()) {
|
||||
blog(LOG_WARNING, "failed to remove sentinel file");
|
||||
}
|
||||
|
||||
bfree(sentinelFile);
|
||||
}
|
||||
|
||||
static void setup()
|
||||
{
|
||||
char *sentinelFile = obs_module_config_path(sentinel.data());
|
||||
if (!sentinelFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString dirPath = QFileInfo(sentinelFile).absolutePath();
|
||||
QDir dir(dirPath);
|
||||
if (!dir.exists()) {
|
||||
if (!dir.mkpath(dirPath)) {
|
||||
blog(LOG_WARNING,
|
||||
"failed to create directory for sentinel file");
|
||||
bfree(sentinelFile);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QFile file(sentinelFile);
|
||||
|
||||
wasCleanShutdown = file.exists();
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
blog(LOG_WARNING, "failed to create sentinel file");
|
||||
bfree(sentinelFile);
|
||||
return;
|
||||
}
|
||||
|
||||
file.write("running");
|
||||
file.close();
|
||||
bfree(sentinelFile);
|
||||
|
||||
obs_frontend_add_event_callback(handleShutdown, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
static bool wasUncleanShutdown()
|
||||
{
|
||||
static bool alreadyHandled = false;
|
||||
|
||||
if (!handleUncleanShutdown || !wasCleanShutdown || alreadyHandled) {
|
||||
alreadyHandled = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
alreadyHandled = true;
|
||||
blog(LOG_WARNING, "unclean shutdown detected");
|
||||
return true;
|
||||
}
|
||||
|
||||
static void askForStartupSkip()
|
||||
{
|
||||
bool skipStart = DisplayMessage(
|
||||
obs_module_text("AdvSceneSwitcher.crashDetected"), true, false);
|
||||
if (!skipStart) {
|
||||
StartPlugin();
|
||||
}
|
||||
}
|
||||
|
||||
bool ShouldSkipPluginStartOnUncleanShutdown()
|
||||
{
|
||||
if (!wasUncleanShutdown()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This function is called while the plugin settings are being loaded.
|
||||
// Blocking at this stage can cause issues such as OBS failing to start
|
||||
// or crashing.
|
||||
// Therefore, we ask the user whether they want to start the plugin
|
||||
// asynchronously.
|
||||
//
|
||||
// On macOS, an additional QTimer::singleShot wrapper is required for
|
||||
// this to work correctly.
|
||||
static const auto showDialogWrapper = [](void *) {
|
||||
#ifdef __APPLE__
|
||||
QTimer::singleShot(0,
|
||||
static_cast<QMainWindow *>(
|
||||
obs_frontend_get_main_window()),
|
||||
[]() {
|
||||
#endif
|
||||
askForStartupSkip();
|
||||
#ifdef __APPLE__
|
||||
});
|
||||
#endif
|
||||
};
|
||||
|
||||
std::thread t([]() {
|
||||
obs_queue_task(OBS_TASK_UI, showDialogWrapper, nullptr, false);
|
||||
});
|
||||
t.detach();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
7
lib/utils/crash-handler.hpp
Normal file
7
lib/utils/crash-handler.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace advss {
|
||||
|
||||
bool ShouldSkipPluginStartOnUncleanShutdown();
|
||||
|
||||
} // namespace advss
|
||||
@@ -4,9 +4,11 @@
|
||||
#include "ui-helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <QAction>
|
||||
#include <QMenu>
|
||||
#include <QLayout>
|
||||
#include <QMenu>
|
||||
#include <QTimer>
|
||||
|
||||
Q_DECLARE_METATYPE(advss::Item *);
|
||||
|
||||
@@ -330,6 +332,13 @@ 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()) {
|
||||
|
||||
@@ -52,6 +52,7 @@ private slots:
|
||||
void NameChanged(const QString &);
|
||||
|
||||
protected:
|
||||
virtual void showEvent(QShowEvent *) override;
|
||||
void SetNameWarning(const QString);
|
||||
|
||||
QLineEdit *_name;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
#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 {
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ void AddPluginInitStep(std::function<void()> step)
|
||||
|
||||
void AddPluginPostLoadStep(std::function<void()> step)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(initMutex);
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
getPluginPostLoadSteps().emplace_back(step);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,13 +65,16 @@ ResourceTable::ResourceTable(QWidget *parent, const QString &help,
|
||||
[openSettings]() { openSettings(); });
|
||||
|
||||
auto settingsShortcut = new QShortcut(QKeySequence("F2"), this);
|
||||
settingsShortcut->setContext(Qt::WidgetWithChildrenShortcut);
|
||||
QWidget::connect(settingsShortcut, &QShortcut::activated, this,
|
||||
openSettings);
|
||||
auto removeShortcut = new QShortcut(QKeySequence("Del"), this);
|
||||
removeShortcut->setContext(Qt::WidgetWithChildrenShortcut);
|
||||
QWidget::connect(removeShortcut, &QShortcut::activated, this,
|
||||
[this]() { Remove(); });
|
||||
|
||||
auto newShortcut = new QShortcut(QKeySequence("Ctrl+N"), this);
|
||||
newShortcut->setContext(Qt::WidgetWithChildrenShortcut);
|
||||
QWidget::connect(newShortcut, &QShortcut::activated, this,
|
||||
[this]() { Add(); });
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "source-helpers.hpp"
|
||||
#include "canvas-helpers.hpp"
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
@@ -28,15 +29,43 @@ std::string GetWeakSourceName(obs_weak_source_t *weak_source)
|
||||
|
||||
OBSWeakSource GetWeakSourceByName(const char *name)
|
||||
{
|
||||
OBSWeakSource weak;
|
||||
obs_source_t *source = obs_get_source_by_name(name);
|
||||
OBSSourceAutoRelease source = obs_get_source_by_name(name);
|
||||
if (source) {
|
||||
weak = obs_source_get_weak_source(source);
|
||||
obs_weak_source_release(weak);
|
||||
obs_source_release(source);
|
||||
return OBSGetWeakRef(source);
|
||||
}
|
||||
|
||||
return weak;
|
||||
#if LIBOBS_API_VER > MAKE_SEMANTIC_VERSION(31, 1, 0)
|
||||
struct SearchData {
|
||||
const char *name;
|
||||
OBSWeakSource &scene;
|
||||
};
|
||||
|
||||
static const auto enumCanvasScenes = [](void *dataPtr,
|
||||
obs_source_t *source) -> bool {
|
||||
auto data = static_cast<SearchData *>(dataPtr);
|
||||
if (strcmp(data->name, obs_source_get_name(source)) == 0) {
|
||||
data->scene = OBSGetWeakRef(source);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
static const auto enumCanvases = [](void *dataPtr,
|
||||
obs_canvas_t *canvas) -> bool {
|
||||
obs_canvas_enum_scenes(canvas, enumCanvasScenes, dataPtr);
|
||||
|
||||
auto data = static_cast<SearchData *>(dataPtr);
|
||||
return data->scene == nullptr;
|
||||
};
|
||||
|
||||
OBSWeakSource weakScene;
|
||||
SearchData searchData{name, weakScene};
|
||||
obs_enum_canvases(enumCanvases, &searchData);
|
||||
return weakScene;
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
OBSWeakSource GetWeakSourceByQString(const QString &name)
|
||||
|
||||
@@ -58,6 +58,9 @@ StringListEdit::StringListEdit(
|
||||
_filterCallback(filter),
|
||||
_preprocessCallback(preprocess)
|
||||
{
|
||||
if (_addString.isEmpty()) {
|
||||
_addString = obs_module_text("AdvSceneSwitcher.windowTitle");
|
||||
}
|
||||
}
|
||||
|
||||
void StringListEdit::SetStringList(const StringList &list)
|
||||
|
||||
@@ -10,6 +10,8 @@ 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,
|
||||
|
||||
@@ -261,6 +261,14 @@ std::optional<std::string> GetTextInWindow(const std::string &window)
|
||||
return {};
|
||||
}
|
||||
|
||||
DWORD pid = 0;
|
||||
DWORD thid = 0;
|
||||
thid = GetWindowThreadProcessId(hwnd, &pid);
|
||||
// Calling CoCreateInstance() on the OBS windows might cause a deadlock
|
||||
if (GetCurrentProcessId() == pid) {
|
||||
return {};
|
||||
}
|
||||
|
||||
IUIAutomation *automation = nullptr;
|
||||
auto hr = CoCreateInstance(__uuidof(CUIAutomation), nullptr,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
|
||||
@@ -150,6 +150,8 @@ target_sources(
|
||||
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
|
||||
@@ -166,6 +168,8 @@ 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
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "scene-switch-helpers.hpp"
|
||||
#include "source-helpers.hpp"
|
||||
#include "transition-helpers.hpp"
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
@@ -68,14 +69,6 @@ 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;
|
||||
@@ -101,13 +94,13 @@ static int getExpectedTransitionDuration(OBSWeakSource &scene,
|
||||
auto overrideTransition = getOverrideTransition(scene);
|
||||
if (overrideTransition) {
|
||||
transition = overrideTransition;
|
||||
if (!isUsingFixedLengthTransition(transition)) {
|
||||
if (!IsFixedLengthTransition(transition)) {
|
||||
return getTransitionOverrideDuration(scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isUsingFixedLengthTransition(transition)) {
|
||||
if (IsFixedLengthTransition(transition)) {
|
||||
return -1; // no API is available to access the fixed duration
|
||||
}
|
||||
if (duration != 0) {
|
||||
@@ -503,7 +496,7 @@ shouldShowDurationInTransitionLayout(MacroActionSwitchScene::SceneType type,
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isUsingFixedLengthTransition(transition.GetTransition())) {
|
||||
if (IsFixedLengthTransition(transition.GetTransition())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#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 {
|
||||
|
||||
@@ -21,9 +25,168 @@ 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);
|
||||
@@ -32,16 +195,43 @@ static void setSceneItemVisibility(obs_sceneitem_t *item,
|
||||
obs_sceneitem_set_visible(item, false);
|
||||
break;
|
||||
case MacroActionSceneVisibility::Action::TOGGLE:
|
||||
obs_sceneitem_set_visible(item, !obs_sceneitem_visible(item));
|
||||
obs_sceneitem_set_visible(item, !itemIsVisible);
|
||||
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, _action);
|
||||
setSceneItemVisibility(item, _updateTransition,
|
||||
_transition.GetTransition(),
|
||||
_updateDuration, _duration, _action);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -66,6 +256,10 @@ 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;
|
||||
}
|
||||
@@ -82,6 +276,10 @@ 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"));
|
||||
|
||||
@@ -141,10 +339,17 @@ 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 &)),
|
||||
@@ -154,18 +359,49 @@ 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 layout = new QHBoxLayout;
|
||||
auto sceneItemLayout = new QHBoxLayout;
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sceneVisibility.entry"),
|
||||
layout,
|
||||
"AdvSceneSwitcher.action.sceneVisibility.layout"),
|
||||
sceneItemLayout,
|
||||
{{"{{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;
|
||||
}
|
||||
|
||||
@@ -177,7 +413,11 @@ void MacroActionSceneVisibilityEdit::UpdateEntryData()
|
||||
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_sources->SetSceneItem((_entryData->_source));
|
||||
_sources->SetSceneItem(_entryData->_source);
|
||||
_updateTransition->setChecked(_entryData->_updateTransition);
|
||||
_transitions->SetTransition(_entryData->_transition);
|
||||
_updateDuration->setChecked(_entryData->_updateDuration);
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
}
|
||||
|
||||
void MacroActionSceneVisibilityEdit::SceneChanged(const SceneSelection &s)
|
||||
@@ -197,6 +437,34 @@ 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();
|
||||
@@ -204,4 +472,20 @@ 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
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#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 {
|
||||
|
||||
@@ -20,6 +22,10 @@ public:
|
||||
|
||||
SceneSelection _scene;
|
||||
SceneItemSelection _source;
|
||||
bool _updateTransition = false;
|
||||
TransitionSelection _transition;
|
||||
bool _updateDuration = false;
|
||||
Duration _duration;
|
||||
|
||||
enum class Action {
|
||||
SHOW,
|
||||
@@ -53,17 +59,27 @@ 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 &);
|
||||
|
||||
protected:
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
SceneSelectionWidget *_scenes;
|
||||
SceneItemSelectionWidget *_sources;
|
||||
QCheckBox *_updateTransition;
|
||||
TransitionSelectionWidget *_transitions;
|
||||
QCheckBox *_updateDuration;
|
||||
DurationSelection *_duration;
|
||||
QHBoxLayout *_durationLayout;
|
||||
QComboBox *_actions;
|
||||
std::shared_ptr<MacroActionSceneVisibility> _entryData;
|
||||
|
||||
private:
|
||||
std::shared_ptr<MacroActionSceneVisibility> _entryData;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "macro-action-transition.hpp"
|
||||
#include "layout-helpers.hpp"
|
||||
#include "transition-helpers.hpp"
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
@@ -75,26 +76,6 @@ static void obs_sceneitem_set_transition_duration(obs_sceneitem_t *item,
|
||||
}
|
||||
#endif
|
||||
|
||||
static void setSceneItemTransition(const OBSSceneItem &item,
|
||||
const OBSSourceAutoRelease &transition,
|
||||
bool show)
|
||||
{
|
||||
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)
|
||||
{
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
|
||||
@@ -103,7 +84,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(
|
||||
|
||||
@@ -38,32 +38,13 @@ 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 (_dayOfWeek != Day::ANY &&
|
||||
cur.date().dayOfWeek() != static_cast<int>(_dayOfWeek)) {
|
||||
if (!_days.contains(static_cast<DayOfWeekSelector::Day>(
|
||||
cur.date().dayOfWeek()))) {
|
||||
return false;
|
||||
}
|
||||
if (_ignoreTime) {
|
||||
@@ -201,7 +182,7 @@ bool MacroConditionDate::CheckCondition()
|
||||
bool MacroConditionDate::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_int(obj, "dayOfWeek", static_cast<int>(_dayOfWeek));
|
||||
SaveSelectedDays(obj, _days);
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
const auto &dateToSave = _updateOnRepeat ? _dateTime : _origDateTime;
|
||||
const auto &dateToSave2 = _updateOnRepeat ? _dateTime2 : _origDateTime2;
|
||||
@@ -216,13 +197,14 @@ 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);
|
||||
_dayOfWeek = static_cast<Day>(obs_data_get_int(obj, "dayOfWeek"));
|
||||
_days = LoadSelectedDays(obj);
|
||||
_condition = static_cast<MacroConditionDate::Condition>(
|
||||
obs_data_get_int(obj, "condition"));
|
||||
_dateTime = QDateTime::fromString(
|
||||
@@ -245,17 +227,64 @@ 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) {
|
||||
auto it = dayOfWeekNames.find(_dayOfWeek);
|
||||
if (it == dayOfWeekNames.end()) {
|
||||
return "";
|
||||
}
|
||||
std::string dayName = obs_module_text(it->second.c_str());
|
||||
const auto dayName =
|
||||
DayOfWeekSelector::ToString(_days).toStdString();
|
||||
if (_ignoreTime) {
|
||||
return dayName;
|
||||
}
|
||||
@@ -347,13 +376,6 @@ 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) {
|
||||
@@ -372,7 +394,7 @@ MacroConditionDateEdit::MacroConditionDateEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionDate> entryData)
|
||||
: QWidget(parent),
|
||||
_weekCondition(new QComboBox()),
|
||||
_dayOfWeek(new QComboBox()),
|
||||
_days(new DayOfWeekSelector(this)),
|
||||
_ignoreWeekTime(new QCheckBox()),
|
||||
_weekTime(new QTimeEdit()),
|
||||
_condition(new QComboBox()),
|
||||
@@ -392,7 +414,7 @@ MacroConditionDateEdit::MacroConditionDateEdit(
|
||||
_currentDate(new QLabel()),
|
||||
_advancedSettingsTooggle(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.showAdvancedSettings"))),
|
||||
_simpleLayout(new QHBoxLayout()),
|
||||
_simpleLayout(new QVBoxLayout()),
|
||||
_advancedLayout(new QHBoxLayout()),
|
||||
_repeatLayout(new QVBoxLayout()),
|
||||
_repeatUpdateLayout(new QHBoxLayout()),
|
||||
@@ -418,8 +440,8 @@ MacroConditionDateEdit::MacroConditionDateEdit(
|
||||
|
||||
QWidget::connect(_weekCondition, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_dayOfWeek, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(DayOfWeekChanged(int)));
|
||||
QWidget::connect(_days, &DayOfWeekSelector::SelectionChanged, this,
|
||||
&MacroConditionDateEdit::DaysChanged);
|
||||
QWidget::connect(_ignoreWeekTime, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(IgnoreTimeChanged(int)));
|
||||
QWidget::connect(_weekTime, SIGNAL(timeChanged(const QTime &)), this,
|
||||
@@ -449,13 +471,12 @@ 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}}", _dayOfWeek},
|
||||
{"{{dayOfWeek}}", _days},
|
||||
{"{{ignoreWeekTime}}", _ignoreWeekTime},
|
||||
{"{{weekTime}}", _weekTime},
|
||||
{"{{condition}}", _condition},
|
||||
@@ -472,28 +493,39 @@ MacroConditionDateEdit::MacroConditionDateEdit(
|
||||
{"{{pattern}}", _pattern},
|
||||
{"{{currentDate}}", _currentDate},
|
||||
};
|
||||
|
||||
auto dayLayout = new QHBoxLayout;
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.entry.simple"),
|
||||
_simpleLayout, widgetPlaceholders);
|
||||
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);
|
||||
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.entry.advanced"),
|
||||
"AdvSceneSwitcher.condition.date.layout.advanced"),
|
||||
_advancedLayout, widgetPlaceholders);
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.entry.updateOnRepeat"),
|
||||
"AdvSceneSwitcher.condition.date.layout.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.entry.pattern"),
|
||||
"AdvSceneSwitcher.condition.date.layout.repeat"),
|
||||
repeatLayout, widgetPlaceholders);
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.layout.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);
|
||||
@@ -515,10 +547,11 @@ MacroConditionDateEdit::MacroConditionDateEdit(
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::DayOfWeekChanged(int day)
|
||||
void MacroConditionDateEdit::DaysChanged(
|
||||
const QSet<DayOfWeekSelector::Day> &days)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_dayOfWeek = static_cast<MacroConditionDate::Day>(day);
|
||||
_entryData->_days = days;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
@@ -632,7 +665,7 @@ void MacroConditionDateEdit::ShowNextMatch()
|
||||
return;
|
||||
}
|
||||
QString format(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.entry.nextMatchDate"));
|
||||
"AdvSceneSwitcher.condition.date.layout.nextMatchDate"));
|
||||
_nextMatchDate->setText(
|
||||
format.arg(_entryData->GetNextMatchDateTime().toString()));
|
||||
}
|
||||
@@ -658,8 +691,7 @@ void MacroConditionDateEdit::UpdateEntryData()
|
||||
}
|
||||
_weekCondition->setCurrentIndex(
|
||||
static_cast<int>(_entryData->_condition));
|
||||
_dayOfWeek->setCurrentIndex(
|
||||
static_cast<int>(static_cast<int>(_entryData->_dayOfWeek)));
|
||||
_days->SetSelectedDays(_entryData->_days);
|
||||
_ignoreWeekTime->setChecked(!_entryData->_ignoreTime);
|
||||
_weekTime->setTime(_entryData->GetDateTime1().time());
|
||||
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "day-of-week-selector.hpp"
|
||||
#include "duration-control.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
@@ -30,17 +31,6 @@ public:
|
||||
QDateTime GetDateTime2() const;
|
||||
QDateTime GetNextMatchDateTime() const;
|
||||
|
||||
enum class Day {
|
||||
ANY = 0,
|
||||
MONDAY,
|
||||
TUESDAY,
|
||||
WEDNESDAY,
|
||||
THURSDAY,
|
||||
FRIDAY,
|
||||
SATURDAY,
|
||||
SUNDAY,
|
||||
};
|
||||
|
||||
enum class Condition {
|
||||
AT,
|
||||
AFTER,
|
||||
@@ -49,7 +39,11 @@ public:
|
||||
PATTERN,
|
||||
};
|
||||
|
||||
Day _dayOfWeek = Day::ANY;
|
||||
QSet<DayOfWeekSelector::Day> _days = {
|
||||
DayOfWeekSelector::Day::Monday,
|
||||
DayOfWeekSelector::Day::Tuesday,
|
||||
DayOfWeekSelector::Day::Wednesday,
|
||||
};
|
||||
bool _ignoreDate = false;
|
||||
bool _ignoreTime = false;
|
||||
bool _repeat = false;
|
||||
@@ -94,7 +88,7 @@ public:
|
||||
}
|
||||
|
||||
private slots:
|
||||
void DayOfWeekChanged(int day);
|
||||
void DaysChanged(const QSet<DayOfWeekSelector::Day> &);
|
||||
void ConditionChanged(int cond);
|
||||
void DateChanged(const QDate &date);
|
||||
void TimeChanged(const QTime &time);
|
||||
@@ -114,7 +108,7 @@ signals:
|
||||
|
||||
protected:
|
||||
QComboBox *_weekCondition;
|
||||
QComboBox *_dayOfWeek;
|
||||
DayOfWeekSelector *_days;
|
||||
QCheckBox *_ignoreWeekTime;
|
||||
QTimeEdit *_weekTime;
|
||||
|
||||
@@ -134,7 +128,7 @@ protected:
|
||||
QLabel *_currentDate;
|
||||
|
||||
QPushButton *_advancedSettingsTooggle;
|
||||
QHBoxLayout *_simpleLayout;
|
||||
QVBoxLayout *_simpleLayout;
|
||||
QHBoxLayout *_advancedLayout;
|
||||
QVBoxLayout *_repeatLayout;
|
||||
QHBoxLayout *_repeatUpdateLayout;
|
||||
|
||||
@@ -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;
|
||||
_class = className ? 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)
|
||||
|
||||
@@ -29,6 +29,22 @@ static bool windowContainsText(const std::string &window,
|
||||
return text == matchText;
|
||||
}
|
||||
|
||||
void MacroConditionWindow::SetCheckText(bool value)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
_checkText = value;
|
||||
#else
|
||||
(void)value;
|
||||
_checkText = false;
|
||||
#endif
|
||||
SetupTempVars();
|
||||
}
|
||||
|
||||
bool MacroConditionWindow::GetCheckText()
|
||||
{
|
||||
return _checkText;
|
||||
}
|
||||
|
||||
bool MacroConditionWindow::WindowMatchesRequirements(
|
||||
const std::string &window) const
|
||||
{
|
||||
@@ -94,12 +110,18 @@ void MacroConditionWindow::SetVariableValueBasedOnMatch(
|
||||
#ifdef _WIN32
|
||||
SetTempVarValue("windowClass",
|
||||
GetWindowClassByWindowTitle(matchWindow));
|
||||
if (_checkText) {
|
||||
const auto text = GetTextInWindow(matchWindow);
|
||||
if (text) {
|
||||
SetTempVarValue("windowText", *text);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!IsReferencedInVars()) {
|
||||
return;
|
||||
}
|
||||
if (_checkText) {
|
||||
auto text = GetTextInWindow(matchWindow);
|
||||
const auto text = GetTextInWindow(matchWindow);
|
||||
SetVariableValue(text.value_or(""));
|
||||
} else {
|
||||
SetVariableValue(ForegroundWindowTitle());
|
||||
@@ -186,6 +208,16 @@ void MacroConditionWindow::SetupTempVars()
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.window.windowClass"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.window.windowClass.description"));
|
||||
|
||||
if (!_checkText) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddTempvar(
|
||||
"windowText",
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.window.windowText"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.window.windowText.description"));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -349,7 +381,7 @@ void MacroConditionWindowEdit::CheckTitleChanged(int state)
|
||||
void MacroConditionWindowEdit::CheckTextChanged(int state)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_checkText = state;
|
||||
_entryData->SetCheckText(state);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
@@ -409,8 +441,8 @@ void MacroConditionWindowEdit::SetWidgetVisibility()
|
||||
_entryData->_focus || _entryData->_windowFocusChanged);
|
||||
_windowSelection->setVisible(_entryData->_checkTitle);
|
||||
_windowRegex->setVisible(_entryData->_checkTitle);
|
||||
_textRegex->setVisible(_entryData->_checkText);
|
||||
_text->setVisible(_entryData->_checkText);
|
||||
_textRegex->setVisible(_entryData->GetCheckText());
|
||||
_text->setVisible(_entryData->GetCheckText());
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
@@ -429,7 +461,7 @@ void MacroConditionWindowEdit::UpdateEntryData()
|
||||
_maximized->setChecked(_entryData->_maximized);
|
||||
_focused->setChecked(_entryData->_focus);
|
||||
_windowFocusChanged->setChecked(_entryData->_windowFocusChanged);
|
||||
_checkText->setChecked(_entryData->_checkText);
|
||||
_checkText->setChecked(_entryData->GetCheckText());
|
||||
_text->setPlainText(_entryData->_text);
|
||||
_textRegex->SetRegexConfig(_entryData->_textRegex);
|
||||
SetWidgetVisibility();
|
||||
|
||||
@@ -22,6 +22,9 @@ public:
|
||||
return std::make_shared<MacroConditionWindow>(m);
|
||||
}
|
||||
|
||||
void SetCheckText(bool value);
|
||||
bool GetCheckText();
|
||||
|
||||
StringVariable _window;
|
||||
RegexConfig _windowRegex;
|
||||
bool _checkTitle = true;
|
||||
@@ -30,8 +33,6 @@ public:
|
||||
bool _focus = true;
|
||||
bool _windowFocusChanged = false;
|
||||
|
||||
// For now only supported on Windows
|
||||
bool _checkText = false;
|
||||
StringVariable _text;
|
||||
RegexConfig _textRegex = RegexConfig::PartialMatchRegexConfig();
|
||||
|
||||
@@ -42,6 +43,9 @@ private:
|
||||
void SetVariableValueBasedOnMatch(const std::string &matchWindow);
|
||||
void SetupTempVars();
|
||||
|
||||
// For now only supported on Windows
|
||||
bool _checkText = false;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
121
plugins/base/utils/day-of-week-selector.cpp
Normal file
121
plugins/base/utils/day-of-week-selector.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#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
|
||||
45
plugins/base/utils/day-of-week-selector.hpp
Normal file
45
plugins/base/utils/day-of-week-selector.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#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
|
||||
@@ -10,7 +10,7 @@ namespace advss {
|
||||
enum class HotkeyType;
|
||||
|
||||
bool CanSimulateKeyPresses();
|
||||
void PressKeys(const std::vector<HotkeyType> keys, int duration);
|
||||
void PressKeys(const std::vector<HotkeyType> &keys, int duration);
|
||||
|
||||
class Hotkey {
|
||||
public:
|
||||
|
||||
@@ -188,7 +188,7 @@ static const std::unordered_map<HotkeyType, long> keyTable = {
|
||||
{HotkeyType::Key_NumpadEnter, XK_KP_Enter},
|
||||
};
|
||||
|
||||
void PressKeys(const std::vector<HotkeyType> keys, int duration)
|
||||
void PressKeys(const std::vector<HotkeyType> &keys, int duration)
|
||||
{
|
||||
if (!canSimulateKeyPresses) {
|
||||
return;
|
||||
|
||||
@@ -9,7 +9,7 @@ bool CanSimulateKeyPresses()
|
||||
return canSimulateKeyPresses;
|
||||
}
|
||||
|
||||
void PressKeys(const std::vector<HotkeyType> keys, int duration)
|
||||
void PressKeys(const std::vector<HotkeyType> &, int)
|
||||
{
|
||||
// Not supported on MacOS
|
||||
return;
|
||||
|
||||
@@ -335,6 +335,12 @@ void SetSourceSetting(obs_source_t *source, const SourceSetting &setting,
|
||||
case OBS_DATA_OBJECT: {
|
||||
OBSDataAutoRelease json =
|
||||
obs_data_create_from_json(value.c_str());
|
||||
if (!json) {
|
||||
blog(LOG_WARNING,
|
||||
"not setting invalid data object settings value! (%s)",
|
||||
value.c_str());
|
||||
break;
|
||||
}
|
||||
obs_data_set_obj(data, id.c_str(), json);
|
||||
break;
|
||||
}
|
||||
|
||||
32
plugins/base/utils/transition-helpers.cpp
Normal file
32
plugins/base/utils/transition-helpers.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "transition-helpers.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
bool IsFixedLengthTransition(const OBSWeakSource &transition)
|
||||
{
|
||||
OBSSourceAutoRelease source = obs_weak_source_get_source(transition);
|
||||
return obs_transition_fixed(source);
|
||||
}
|
||||
|
||||
obs_source_t *SetSceneItemTransition(const OBSSceneItem &item,
|
||||
const OBSSourceAutoRelease &transition,
|
||||
bool show)
|
||||
{
|
||||
OBSDataAutoRelease settings = obs_source_get_settings(transition);
|
||||
if (!transition || !settings) {
|
||||
// Set transition to "None"
|
||||
obs_sceneitem_set_transition(item, show, nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 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);
|
||||
return transitionSource.Get();
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
11
plugins/base/utils/transition-helpers.hpp
Normal file
11
plugins/base/utils/transition-helpers.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include <obs.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
bool IsFixedLengthTransition(const OBSWeakSource &transition);
|
||||
obs_source_t *SetSceneItemTransition(const OBSSceneItem &item,
|
||||
const OBSSourceAutoRelease &transition,
|
||||
bool show);
|
||||
|
||||
} // namespace advss
|
||||
@@ -138,7 +138,7 @@ static const std::unordered_map<HotkeyType, long> keyTable = {
|
||||
{HotkeyType::Key_NumpadEnter, VK_RETURN},
|
||||
};
|
||||
|
||||
void PressKeys(const std::vector<HotkeyType> keys, int duration)
|
||||
void PressKeys(const std::vector<HotkeyType> &keys, int duration)
|
||||
{
|
||||
const int repeatInterval = 100;
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ struct URLInfo {
|
||||
std::string path;
|
||||
};
|
||||
|
||||
static URLInfo getURLInfo(const std::string &input)
|
||||
static URLInfo getURLInfo(const std::string &input, bool keepParams)
|
||||
{
|
||||
if (input.empty()) {
|
||||
return {};
|
||||
@@ -84,21 +84,35 @@ static URLInfo getURLInfo(const std::string &input)
|
||||
const QUrl url(urlInput);
|
||||
auto host =
|
||||
url.scheme().toStdString() + "://" + url.host().toStdString();
|
||||
int port = url.port();
|
||||
|
||||
const int port = url.port();
|
||||
if (port != -1) {
|
||||
host += ":" + std::to_string(port);
|
||||
}
|
||||
|
||||
auto path = url.path().toStdString();
|
||||
if (path.empty()) {
|
||||
path = "/";
|
||||
}
|
||||
|
||||
const auto query = url.query().toStdString();
|
||||
if (!query.empty()) {
|
||||
if (keepParams) {
|
||||
path += "?" + query;
|
||||
} else {
|
||||
blog(LOG_WARNING,
|
||||
"ignoring query parameters \"%s\" in URL field. "
|
||||
"Using parameter field values instead.",
|
||||
query.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return {host, path};
|
||||
}
|
||||
|
||||
bool MacroActionHttp::PerformAction()
|
||||
{
|
||||
const auto [host, path] = getURLInfo(_url);
|
||||
const auto [host, path] = getURLInfo(_url, !_setParams);
|
||||
|
||||
httplib::Client cli(host);
|
||||
setTimeout(cli, _timeout);
|
||||
|
||||
@@ -513,7 +513,7 @@ void ScriptHandler::SetVariableValue(void *, calldata_t *data)
|
||||
RETURN_SUCCESS();
|
||||
}
|
||||
|
||||
void ScriptHandler::GetRunningStatus(void *ctx, calldata_t *data)
|
||||
void ScriptHandler::GetRunningStatus(void *, calldata_t *data)
|
||||
{
|
||||
calldata_set_bool(data, "is_running", PluginIsRunning());
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ bool MacroConditionStreamdeck::MessageMatches(const StreamDeckMessage &message)
|
||||
{
|
||||
const bool keyStateMatches = !_pattern.checkKeyState ||
|
||||
((message.keyDown && _pattern.keyDown) ||
|
||||
(!message.keyDown && !message.keyDown));
|
||||
(!message.keyDown && !_pattern.keyDown));
|
||||
const bool positionMatches = !_pattern.checkPosition ||
|
||||
(message.row == _pattern.row &&
|
||||
message.column == _pattern.column);
|
||||
|
||||
@@ -58,14 +58,16 @@ std::string TwitchChannel::GetUserID(const TwitchToken &token) const
|
||||
}
|
||||
OBSDataArrayAutoRelease array = obs_data_get_array(res.data, "data");
|
||||
size_t count = obs_data_array_count(array);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
OBSDataAutoRelease arrayObj = obs_data_array_item(array, i);
|
||||
std::string id = obs_data_get_string(arrayObj, "id");
|
||||
userIDCache[_name] = id;
|
||||
return id;
|
||||
|
||||
if (count == 0) {
|
||||
userIDCache[_name] = "invalid";
|
||||
return "invalid";
|
||||
}
|
||||
userIDCache[_name] = "invalid";
|
||||
return "invalid";
|
||||
|
||||
OBSDataAutoRelease arrayObj = obs_data_array_item(array, 0);
|
||||
const std::string id = obs_data_get_string(arrayObj, "id");
|
||||
userIDCache[_name] = id;
|
||||
return id;
|
||||
}
|
||||
|
||||
static QDate parseRFC3339Date(const std::string &rfc3339Date)
|
||||
|
||||
@@ -89,12 +89,24 @@ void EventSub::ConnectThread()
|
||||
|
||||
void EventSub::WaitAndReconnect()
|
||||
{
|
||||
if (_reconnecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
_reconnecting = true;
|
||||
|
||||
auto thread = std::thread([this]() {
|
||||
std::unique_lock<std::mutex> lock(_waitMtx);
|
||||
blog(LOG_INFO,
|
||||
"Twitch EventSub trying to reconnect to in %d seconds.",
|
||||
reconnectDelay);
|
||||
_cv.wait_for(lock, std::chrono::seconds(reconnectDelay));
|
||||
_reconnecting = false;
|
||||
|
||||
if (_disconnect) {
|
||||
return;
|
||||
}
|
||||
|
||||
Connect();
|
||||
});
|
||||
thread.detach();
|
||||
@@ -102,6 +114,10 @@ void EventSub::WaitAndReconnect()
|
||||
|
||||
void EventSub::Connect()
|
||||
{
|
||||
if (_reconnecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(_connectMtx);
|
||||
if (_connected) {
|
||||
vblog(LOG_INFO, "Twitch EventSub connect already in progress");
|
||||
@@ -448,7 +464,7 @@ void EventSub::StartServerMigrationClient(const std::string &url)
|
||||
auto client = std::make_unique<EventSubWSClient>();
|
||||
SetupClient(*client);
|
||||
|
||||
client->set_open_handler([this](connection_hdl hdl) {
|
||||
client->set_open_handler([this](connection_hdl) {
|
||||
vblog(LOG_INFO, "Twitch EventSub migration client opened");
|
||||
});
|
||||
|
||||
@@ -465,8 +481,8 @@ void EventSub::StartServerMigrationClient(const std::string &url)
|
||||
const auto &data = msg->payload;
|
||||
|
||||
if (type == "session_welcome") {
|
||||
vblog(LOG_INFO,
|
||||
"Twitch EventSub migration successful - switching to new connection");
|
||||
blog(LOG_INFO,
|
||||
"Twitch EventSub migration successful - switching to new connection");
|
||||
OBSDataAutoRelease session =
|
||||
obs_data_get_obj(data, "session");
|
||||
_migrationSessionID =
|
||||
@@ -565,7 +581,7 @@ void EventSub::OnClose(connection_hdl hdl)
|
||||
blog(LOG_INFO, "Twitch EventSub connection closed: %s / %s (%d)",
|
||||
msg.c_str(), reason.c_str(), code);
|
||||
|
||||
if (_migrating) {
|
||||
if (!_migrating) {
|
||||
ClearActiveSubscriptions();
|
||||
}
|
||||
_connected = false;
|
||||
|
||||
@@ -110,6 +110,7 @@ private:
|
||||
std::condition_variable _cv;
|
||||
std::atomic_bool _connected{false};
|
||||
std::atomic_bool _disconnect{false};
|
||||
std::atomic_bool _reconnecting{false};
|
||||
|
||||
std::string _url;
|
||||
std::string _sessionID;
|
||||
|
||||
@@ -829,7 +829,7 @@ void TokenGrabberThread::run()
|
||||
if (_serverThread.joinable()) {
|
||||
_serverThread.join();
|
||||
}
|
||||
_stopWaiting = {false};
|
||||
_stopWaiting = false;
|
||||
|
||||
// Generate URI to request token
|
||||
auto state = generateStateString();
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
#include "ui-helpers.hpp"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QMainWindow>
|
||||
#include <QTabWidget>
|
||||
#include <QTimer>
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
namespace advss {
|
||||
|
||||
@@ -38,8 +42,29 @@ static bool setup()
|
||||
}
|
||||
};
|
||||
|
||||
// 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 update their Twitch
|
||||
// connections asynchronously.
|
||||
//
|
||||
// On macOS, an additional QTimer::singleShot wrapper is required for
|
||||
// this to work correctly.
|
||||
AddLoadStep([](obs_data_t *) {
|
||||
AddPostLoadStep([]() { showInvalidWarnings(); });
|
||||
AddPostLoadStep([]() {
|
||||
#ifdef __APPLE__
|
||||
QTimer::singleShot(
|
||||
0,
|
||||
static_cast<QMainWindow *>(
|
||||
obs_frontend_get_main_window()),
|
||||
[]() {
|
||||
#endif
|
||||
showInvalidWarnings();
|
||||
|
||||
#ifdef __APPLE__
|
||||
});
|
||||
#endif
|
||||
});
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user