Compare commits

...

20 Commits

Author SHA1 Message Date
WarmUpTill
f35ef558ee Fix crash when restoring scene item transition in parallel
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2026-02-19 20:28:10 +01:00
WarmUpTill
d7ff9088f8 Enable selecting multiple days in simple view of date condition 2026-02-19 20:11:12 +01:00
WarmUpTill
7048925d6e Add DayOfWeekSelector 2026-02-19 20:11:12 +01:00
WarmUpTill
fcb3ea50d3 Add QTimer::singleShot() wrappers to display startup dialogs on macOS 2026-02-19 20:11:12 +01:00
WarmUpTill
87c45e2b32 Focus name edit for new items 2026-02-19 20:11:12 +01:00
WarmUpTill
eb5046a9d6 Add websocket callbacks to run macros and set variables
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2026-02-17 22:07:54 +01:00
WarmUpTill
58c05f3f6e Fix race condition in plugin post load steps 2026-02-17 22:07:54 +01:00
WarmUpTill
0b284da3de Fix stream deck condition not matching key "down" state properly 2026-02-17 22:07:54 +01:00
WarmUpTill
03fe7016e4 Fix crash in game capture condition when receiving null data
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2026-02-07 22:38:26 +01:00
WarmUpTill
2ea89912a3 Fix transition duration selection not being editable
Some checks failed
debian-build / build (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
Check locale / ubuntu64 (push) Has been cancelled
2026-02-07 21:32:58 +01:00
WarmUpTill
78d2efa083 Skip invalid json object settings value 2026-02-07 21:32:58 +01:00
WarmUpTill
149fce1c2b Show tooltip updates while the tooltip is visible 2026-02-07 21:32:58 +01:00
WarmUpTill
2dd9120265 Add tooltip for last execution time of macro in macro list 2026-02-07 21:32:58 +01:00
WarmUpTill
3a9f315b67 Highlight macros blocked by the "on change" setting in the macro list 2026-02-07 21:32:58 +01:00
WarmUpTill
6ba01dca39 Fix Twitch event sub reconnect handling
If the connection was aborted by the local machine, the active
subsciptions were not cleared causing the new connection not attempt to
register any new subsciptions.

An event sub connection without any active subsciptions will be dropped
by Twitch after a certain amount of time.

Additionally the reconnection logic was triggering to frequently causing
unecessary load.
2026-02-07 21:32:58 +01:00
WarmUpTill
ad1f1effeb Add option to pick random value 2026-02-07 21:32:58 +01:00
WarmUpTill
0a53a8649f Add initializer_list support and set default add dialog window title 2026-02-07 21:32:57 +01:00
WarmUpTill
7ea721f9f1 Add option to set transition when changing scene item visibility 2026-01-27 19:28:45 +01:00
WarmUpTill
718a899a98 Move functions 2026-01-27 19:28:45 +01:00
WarmUpTill
4c493451f4 Keep URL parameters if parameters input is not used 2026-01-18 19:44:19 +01:00
48 changed files with 1199 additions and 286 deletions

View File

@@ -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)

View File

@@ -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"

View File

@@ -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"
@@ -2468,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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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="無効化"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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="关闭"

View File

@@ -192,17 +192,7 @@ 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);

View File

@@ -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,
@@ -836,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(
@@ -872,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 &)),
@@ -960,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(
@@ -1026,8 +1084,11 @@ 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);
@@ -1046,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;
@@ -1105,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);
@@ -1447,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();
@@ -1641,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 ==

View File

@@ -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> &);
@@ -224,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;

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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()));

View File

@@ -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>);
@@ -90,6 +89,35 @@ MacroTreeItem::MacroTreeItem(MacroTree *tree, std::shared_ptr<Macro> macroItem,
_timer.start(1500);
}
bool MacroTreeItem::event(QEvent *event)
{
if (event->type() != QEvent::ToolTip) {
return QFrame::event(event);
}
if (_macro->IsGroup()) {
return true;
}
QString text;
if (!_macro->WasExecutedSince({})) {
text = obs_module_text(
"AdvSceneSwitcher.macroTab.macroNotYetExecutedTooltip");
} else {
const QString formatStr = obs_module_text(
"AdvSceneSwitcher.macroTab.macroLastExecutedTooltip");
const auto secondsSinceLastRun =
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::high_resolution_clock::now() -
_macro->GetLastExecutionTime());
text = formatStr.arg(secondsSinceLastRun.count());
}
auto helpEvent = static_cast<QHelpEvent *>(event);
QToolTip::showText(helpEvent->globalPos(), text, this);
return true;
}
void MacroTreeItem::EnableHighlight(bool enable)
{
_highlight = enable;
@@ -187,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();
}
@@ -267,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);

View File

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

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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();

View File

@@ -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

View File

@@ -9,6 +9,8 @@
#include <QDir>
#include <QFile>
#include <QMainWindow>
#include <QTimer>
#include <thread>
@@ -104,10 +106,13 @@ static bool wasUncleanShutdown()
return true;
}
static bool askForStartupSkip()
static void askForStartupSkip()
{
return DisplayMessage(obs_module_text("AdvSceneSwitcher.crashDetected"),
true, false);
bool skipStart = DisplayMessage(
obs_module_text("AdvSceneSwitcher.crashDetected"), true, false);
if (!skipStart) {
StartPlugin();
}
}
bool ShouldSkipPluginStartOnUncleanShutdown()
@@ -116,16 +121,29 @@ bool ShouldSkipPluginStartOnUncleanShutdown()
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,
[](void *) {
const bool skipStart = askForStartupSkip();
if (!skipStart) {
StartPlugin();
}
},
nullptr, false);
obs_queue_task(OBS_TASK_UI, showDialogWrapper, nullptr, false);
});
t.detach();

View File

@@ -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()) {

View File

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

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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)

View File

@@ -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,

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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(

View File

@@ -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));

View File

@@ -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;

View File

@@ -55,17 +55,17 @@ void MacroConditionGameCapture::GetCalldataInfo(calldata_t *cd)
if (!calldata_get_string(cd, "title", &title)) {
blog(LOG_WARNING, "%s failed to get title", __func__);
}
_title = title;
_title = title ? title : "";
const char *className = "";
if (!calldata_get_string(cd, "class", &className)) {
blog(LOG_WARNING, "%s failed to get class", __func__);
}
_class = className;
_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)

View 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

View 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

View File

@@ -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;
}

View 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

View 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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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");
@@ -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;

View File

@@ -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;

View File

@@ -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;