Compare commits

...

8 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
27 changed files with 727 additions and 239 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"

View File

@@ -589,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"
@@ -607,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"
@@ -2474,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"

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"

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="現在の値を取得"

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"

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="获取当前值"

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

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

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

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

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

View File

@@ -1,7 +1,10 @@
#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 {
const std::string MacroActionSceneVisibility::id = "scene_visibility";
@@ -24,43 +27,137 @@ const static std::map<MacroActionSceneVisibility::Action, std::string>
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;
bool restoreTransition;
OBSSource originalTransition;
bool restoreDuration;
OBSSource originalTransition = nullptr;
uint32_t originalDuration;
obs_source_t *transition = nullptr;
signal_handler_t *sh = nullptr;
~TransitionRestoreContext()
{
if (!sh) {
return;
}
signal_handler_disconnect(
sh, "transition_stop",
&TransitionRestoreContext::ResetSceneItemTransition,
this);
}
static void ResetSceneItemTransition(void *param, calldata_t *)
{
auto *ctx = static_cast<TransitionRestoreContext *>(param);
SetSceneItemTransition(ctx->item, ctx->originalTransition,
!ctx->wasVisible);
obs_sceneitem_set_transition_duration(
ctx->item, !ctx->wasVisible, ctx->originalDuration);
delete ctx;
}
};
} // 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,
@@ -76,7 +173,7 @@ static void setSceneItemVisibility(obs_sceneitem_t *item,
const uint32_t currentTransitionDuration =
obs_sceneitem_get_transition_duration(item, !itemIsVisible);
obs_source_t *privateTransitionSource = nullptr;
OBSSource privateTransitionSource = nullptr;
if (setTransition) {
privateTransitionSource = SetSceneItemTransition(
item, transition, !itemIsVisible);
@@ -102,6 +199,10 @@ static void setSceneItemVisibility(obs_sceneitem_t *item,
break;
}
if (!setTransition && !setDuration) {
return;
}
if (!privateTransitionSource) {
if (setTransition) {
SetSceneItemTransition(item, currentTransition,
@@ -120,17 +221,8 @@ static void setSceneItemVisibility(obs_sceneitem_t *item,
return;
}
auto ctx = new TransitionRestoreContext{item,
itemIsVisible,
setTransition,
currentTransition,
setDuration,
currentTransitionDuration,
sh};
signal_handler_connect(
sh, "transition_stop",
&TransitionRestoreContext::ResetSceneItemTransition, ctx);
attachRestoreContext(item, privateTransitionSource, itemIsVisible,
currentTransition, currentTransitionDuration);
}
bool MacroActionSceneVisibility::PerformAction()

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

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

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

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