Compare commits

...

30 Commits

Author SHA1 Message Date
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
WarmUpTill
a8d483f5a7 Enable un-/pausing all macros in a group 2026-01-13 21:45:04 +01:00
WarmUpTill
dc786a5313 Cleanup 'find and replace' layout in variable action 2026-01-13 21:44:17 +01:00
WarmUpTill
628a4d896c Add option to stop plugin in case unclean shutdown is detected 2026-01-05 10:58:55 +01:00
WarmUpTill
74a9681c8e Fix deadlock when opening settings window 2026-01-05 10:58:55 +01:00
WarmUpTill
faf52c38bb Add temp var for window text 2026-01-05 10:58:55 +01:00
WarmUpTill
bad1a548fb Fix resource table shortcuts conflicting with OBS shortcuts
This could happen when adding the variable tab as a dock.
The problem is visible in the OBS log:
QAction::event: Ambiguous shortcut overload: Del
2025-12-27 20:38:53 +01:00
WarmUpTill
f706416df5 Fix warnings
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
2025-12-21 14:33:08 +01:00
WarmUpTill
c344c88acd Add support for scenes of secondary canvases in source selection 2025-12-21 14:33:08 +01:00
WarmUpTill
2cd9e61717 Adapt paths to new OBS directory structure 2025-12-21 14:33:08 +01:00
WarmUpTill
6314de8f37 Add option to always show feature tabs
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
2025-12-14 12:54:17 +01:00
WarmUpTill
6bfcabc4af Add option to get channel info 2025-12-14 12:54:17 +01:00
WarmUpTill
69711d973a Fix memory leak 2025-12-14 12:54:17 +01:00
WarmUpTill
225913b44d Fix warnings 2025-12-14 10:42:51 +01:00
WarmUpTill
7c6d657fdd Add temp var for last recording save path 2025-12-14 10:42:51 +01:00
WarmUpTill
12e38274f5 Fix freeze on shutdown / scene collection change 2025-12-14 10:31:45 +01:00
WarmUpTill
5d49e8825b Add group support to MacroSelection 2025-12-14 10:30:58 +01:00
WarmUpTill
21c34356ed Add macro group helper functions 2025-12-14 10:30:58 +01:00
WarmUpTill
266e470509 Remove unused function 2025-12-14 10:30:58 +01:00
75 changed files with 1238 additions and 344 deletions

View File

@@ -6,18 +6,18 @@ You have the option to ...
Both methods require [CMake](https://cmake.org/download/). Both methods require [CMake](https://cmake.org/download/).
The plugin can be compiled for OBS 27 and above, although using the latest version of OBS is recommended to support all features. The plugin can be compiled for OBS 31 and above, although using the latest version of OBS is recommended to support all features.
## Compiling in tree (recommended for development) ## Compiling in tree (recommended for development)
This section assumes that you have a working [OBS Studio development environment](https://obsproject.com/wiki/Building-OBS-Studio). This section assumes that you have a working [OBS Studio development environment](https://obsproject.com/wiki/Building-OBS-Studio).
Add the "SceneSwitcher" source directory to your obs-studio source directory under obs-studio/UI/frontend-plugins/: Add the "SceneSwitcher" source directory to your obs-studio source directory under obs-studio/plugins:
``` ```
cd obs-studio/UI/frontend-plugins/ cd obs-studio/plugins
git clone --recursive https://github.com/WarmUpTill/SceneSwitcher.git git clone --recursive https://github.com/WarmUpTill/SceneSwitcher.git
``` ```
Then modify the obs-studio/UI/frontend-plugins/CMakeLists.txt Example and add an entry for the scene switcher: Then modify the obs-studio/plugins/CMakeLists.txt and add an entry for the scene switcher:
``` ```
add_subdirectory(SceneSwitcher) add_subdirectory(SceneSwitcher)
``` ```

View File

@@ -184,6 +184,8 @@ target_sources(
lib/utils/canvas-helpers.hpp lib/utils/canvas-helpers.hpp
lib/utils/condition-logic.cpp lib/utils/condition-logic.cpp
lib/utils/condition-logic.hpp lib/utils/condition-logic.hpp
lib/utils/crash-handler.cpp
lib/utils/crash-handler.hpp
lib/utils/curl-helper.cpp lib/utils/curl-helper.cpp
lib/utils/curl-helper.hpp lib/utils/curl-helper.hpp
lib/utils/cursor-shape-changer.cpp lib/utils/cursor-shape-changer.cpp

View File

@@ -429,7 +429,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="Verstecken"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="Umschalten" AdvSceneSwitcher.action.sceneVisibility.type.toggle="Umschalten"
AdvSceneSwitcher.action.sceneVisibility.type.source="Quelle" AdvSceneSwitcher.action.sceneVisibility.type.source="Quelle"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Beliebig" 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="Filter"
AdvSceneSwitcher.action.filter.type.enable="Aktivieren" AdvSceneSwitcher.action.filter.type.enable="Aktivieren"
AdvSceneSwitcher.action.filter.type.disable="Deaktivieren" AdvSceneSwitcher.action.filter.type.disable="Deaktivieren"

View File

@@ -44,6 +44,8 @@ AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage="<html>
AdvSceneSwitcher.generalTab.generalBehavior.warnCorruptedInstallMessage="The plugin installation seems to be corrupted and might crash!\nPlease make sure the plugin was installed correctly!" AdvSceneSwitcher.generalTab.generalBehavior.warnCorruptedInstallMessage="The plugin installation seems to be corrupted and might crash!\nPlease make sure the plugin was installed correctly!"
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="Hide tabs which can be represented via macros" AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="Hide tabs which can be represented via macros"
AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowMacroSearch="Always show macro search" AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowMacroSearch="Always show macro search"
AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowFeatureTabs="Always show all feature tabs"
AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowFeatureTabs.tooltip="Show tabs that are normally hidden until first used in a macro (e.g. Action Queues, Variables, Twitch, Websockets)."
AdvSceneSwitcher.generalTab.matchBehavior="Match behavior" AdvSceneSwitcher.generalTab.matchBehavior="Match behavior"
AdvSceneSwitcher.generalTab.priority="Priority" AdvSceneSwitcher.generalTab.priority="Priority"
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)" AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
@@ -158,6 +160,8 @@ AdvSceneSwitcher.macroTab.title="Macro"
AdvSceneSwitcher.macroTab.macros="Macros" 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.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.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.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.editActionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are met.\n\nSelect an existing or add a new macro on the left.\nThen click the plus button below to add a new action."
AdvSceneSwitcher.macroTab.editElseActionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are *not* met.\n\nSelect an existing or add a new macro on the left.\nThen click the plus button below to add a new action." AdvSceneSwitcher.macroTab.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."
@@ -914,7 +918,9 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="Hide"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="Toggle" AdvSceneSwitcher.action.sceneVisibility.type.toggle="Toggle"
AdvSceneSwitcher.action.sceneVisibility.type.source="Source" AdvSceneSwitcher.action.sceneVisibility.type.source="Source"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Any" 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="Filter"
AdvSceneSwitcher.action.filter.type.enable="Enable" AdvSceneSwitcher.action.filter.type.enable="Enable"
AdvSceneSwitcher.action.filter.type.disable="Disable" AdvSceneSwitcher.action.filter.type.disable="Disable"
@@ -1190,6 +1196,8 @@ AdvSceneSwitcher.action.variable.type.swapValues="Swap variable values"
AdvSceneSwitcher.action.variable.type.trim="Trim whitespace" AdvSceneSwitcher.action.variable.type.trim="Trim whitespace"
AdvSceneSwitcher.action.variable.type.changeCase="Change case" AdvSceneSwitcher.action.variable.type.changeCase="Change case"
AdvSceneSwitcher.action.variable.type.randomNumber="Generate random number" 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.lowerCase="lowercase"
AdvSceneSwitcher.action.variable.case.type.upperCase="UPPERCASE" AdvSceneSwitcher.action.variable.case.type.upperCase="UPPERCASE"
AdvSceneSwitcher.action.variable.case.type.capitalized="Capitalized" AdvSceneSwitcher.action.variable.case.type.capitalized="Capitalized"
@@ -1212,8 +1220,8 @@ AdvSceneSwitcher.action.variable.currentSegmentValue="Current value:"
AdvSceneSwitcher.action.variable.layout.other="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}{{tempVarsHelp}}{{sceneItemIndex}}{{direction}}{{stringLength}}{{paddingCharSelection}}{{caseType}}{{jsonQuery}}{{jsonQueryHelp}}{{jsonIndex}}" AdvSceneSwitcher.action.variable.layout.other="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}{{tempVarsHelp}}{{sceneItemIndex}}{{direction}}{{stringLength}}{{paddingCharSelection}}{{caseType}}{{jsonQuery}}{{jsonQueryHelp}}{{jsonIndex}}"
AdvSceneSwitcher.action.variable.layout.pad="{{actions}}of{{variables}}to length{{stringLength}}by adding{{paddingCharSelection}}to the{{direction}}" AdvSceneSwitcher.action.variable.layout.pad="{{actions}}of{{variables}}to length{{stringLength}}by adding{{paddingCharSelection}}to the{{direction}}"
AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}of{{variables}}to length{{stringLength}}by removing characters from the{{direction}}" AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}of{{variables}}to length{{stringLength}}by removing characters from the{{direction}}"
AdvSceneSwitcher.action.variable.layout.substringIndex="Substring start:{{subStringStart}}Substring size:{{subStringSize}}" AdvSceneSwitcher.action.variable.layout.substringIndex="Substring start:{{subStringStart}}Substring size:{{subStringSize}}{{subStringRegex}}"
AdvSceneSwitcher.action.variable.layout.substringRegex="Assign value of{{regexMatchIdx}}match using regular expression:" AdvSceneSwitcher.action.variable.layout.substringRegex="Assign value of{{regexMatchIdx}}match using regular expression:{{subStringRegex}}"
AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}" AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}"
AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}Use custom prompt{{inputPrompt}}" AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}Use custom prompt{{inputPrompt}}"
AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}Fill with placeholder{{inputPlaceholder}}" AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}Fill with placeholder{{inputPlaceholder}}"
@@ -1256,12 +1264,14 @@ AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="Disable chat's emote
AdvSceneSwitcher.action.twitch.type.chat.sendMessage="Send chat message" AdvSceneSwitcher.action.twitch.type.chat.sendMessage="Send chat message"
AdvSceneSwitcher.action.twitch.type.user.getInfo="Get user information" AdvSceneSwitcher.action.twitch.type.user.getInfo="Get user information"
AdvSceneSwitcher.action.twitch.type.reward.getInfo="Get channel points reward information" AdvSceneSwitcher.action.twitch.type.reward.getInfo="Get channel points reward information"
AdvSceneSwitcher.action.twitch.type.channel.getInfo="Get channel information"
AdvSceneSwitcher.action.twitch.reward.toggleControl="Toggle reward name / variable selection control" AdvSceneSwitcher.action.twitch.reward.toggleControl="Toggle reward name / variable selection control"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!" AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!"
AdvSceneSwitcher.action.twitch.entry.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}" AdvSceneSwitcher.action.twitch.layout.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}"
AdvSceneSwitcher.action.twitch.entry.chat="Using account{{account}}{{actions}}on{{channel}}" AdvSceneSwitcher.action.twitch.layout.chat="Using account{{account}}{{actions}}on{{channel}}"
AdvSceneSwitcher.action.twitch.entry.user.getInfo="Using account{{account}}{{actions}}for{{userInfoQueryType}}{{userLogin}}{{userId}}" AdvSceneSwitcher.action.twitch.layout.channel.getInfo="Using account{{account}}{{actions}}of{{channel}}"
AdvSceneSwitcher.action.twitch.entry.reward.getInfo="Using account{{account}}{{actions}}for channel{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}" AdvSceneSwitcher.action.twitch.layout.user.getInfo="Using account{{account}}{{actions}}for{{userInfoQueryType}}{{userLogin}}{{userId}}"
AdvSceneSwitcher.action.twitch.layout.reward.getInfo="Using account{{account}}{{actions}}for channel{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
AdvSceneSwitcher.action.twitch.title.title="Enter title" AdvSceneSwitcher.action.twitch.title.title="Enter title"
AdvSceneSwitcher.action.twitch.marker.description="Describe marker" AdvSceneSwitcher.action.twitch.marker.description="Describe marker"
AdvSceneSwitcher.action.twitch.clip.hasDelay="Add a slight delay before capturing the clip" AdvSceneSwitcher.action.twitch.clip.hasDelay="Add a slight delay before capturing the clip"
@@ -1386,6 +1396,8 @@ AdvSceneSwitcher.hotkey.macro.segment.remove="Remove selected macro segment"
AdvSceneSwitcher.askBackup="Detected a new version of the Advanced Scene Switcher.\nShould a backup of the old settings be created?" AdvSceneSwitcher.askBackup="Detected a new version of the Advanced Scene Switcher.\nShould a backup of the old settings be created?"
AdvSceneSwitcher.askForMacro="Select macro{{macroSelection}}" AdvSceneSwitcher.askForMacro="Select macro{{macroSelection}}"
AdvSceneSwitcher.crashDetected="OBS did not shut down cleanly (for example, due to a crash or freeze).\n\nThe Advanced Scene Switcher plugin would normally start automatically.\nWould you like to keep it stopped for now?"
AdvSceneSwitcher.close="Close" AdvSceneSwitcher.close="Close"
AdvSceneSwitcher.browse="Browse" AdvSceneSwitcher.browse="Browse"
@@ -2145,6 +2157,8 @@ AdvSceneSwitcher.tempVar.window.window="Window title"
AdvSceneSwitcher.tempVar.window.window.description="The window title of the matching window." AdvSceneSwitcher.tempVar.window.window.description="The window title of the matching window."
AdvSceneSwitcher.tempVar.window.windowClass="Window class" AdvSceneSwitcher.tempVar.window.windowClass="Window class"
AdvSceneSwitcher.tempVar.window.windowClass.description="The window class of the matching window." AdvSceneSwitcher.tempVar.window.windowClass.description="The window class of the matching window."
AdvSceneSwitcher.tempVar.window.windowText="Window text"
AdvSceneSwitcher.tempVar.window.windowText.description="Text contained within the window (won't work on all window types)."
AdvSceneSwitcher.tempVar.timer.seconds="Seconds" AdvSceneSwitcher.tempVar.timer.seconds="Seconds"
AdvSceneSwitcher.tempVar.timer.minutes="Minutes" AdvSceneSwitcher.tempVar.timer.minutes="Minutes"
@@ -2186,6 +2200,8 @@ AdvSceneSwitcher.tempVar.run.process.none.description="When not waiting for the
AdvSceneSwitcher.tempVar.recording.durationSeconds="Recording duration" AdvSceneSwitcher.tempVar.recording.durationSeconds="Recording duration"
AdvSceneSwitcher.tempVar.recording.durationSeconds.description="Recording duration in seconds.\nThis value does not change while the recording is paused and will be reset to zero if the recording is stopped." AdvSceneSwitcher.tempVar.recording.durationSeconds.description="Recording duration in seconds.\nThis value does not change while the recording is paused and will be reset to zero if the recording is stopped."
AdvSceneSwitcher.tempVar.recording.lastSavePath="Last save path"
AdvSceneSwitcher.tempVar.recording.lastSavePath.description="The file path of the last saved recording."
AdvSceneSwitcher.tempVar.video.patternCount="Pattern count" AdvSceneSwitcher.tempVar.video.patternCount="Pattern count"
AdvSceneSwitcher.tempVar.video.patternCount.description="The number of times the given pattern has been found in a given video input frame." AdvSceneSwitcher.tempVar.video.patternCount.description="The number of times the given pattern has been found in a given video input frame."

View File

@@ -350,7 +350,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.show="Mostrar"
AdvSceneSwitcher.action.sceneVisibility.type.hide="Ocultar" AdvSceneSwitcher.action.sceneVisibility.type.hide="Ocultar"
AdvSceneSwitcher.action.sceneVisibility.type.source="Fuente" AdvSceneSwitcher.action.sceneVisibility.type.source="Fuente"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Cualquiera" 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="Filtro"
AdvSceneSwitcher.action.filter.type.enable="Habilitar" AdvSceneSwitcher.action.filter.type.enable="Habilitar"
AdvSceneSwitcher.action.filter.type.disable="Deshabilitar" AdvSceneSwitcher.action.filter.type.disable="Deshabilitar"

View File

@@ -510,7 +510,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="Masquer"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="Basculer" AdvSceneSwitcher.action.sceneVisibility.type.toggle="Basculer"
AdvSceneSwitcher.action.sceneVisibility.type.source="Source" AdvSceneSwitcher.action.sceneVisibility.type.source="Source"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="N'importe" 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="Filtre"
AdvSceneSwitcher.action.filter.type.enable="Activer" AdvSceneSwitcher.action.filter.type.enable="Activer"
AdvSceneSwitcher.action.filter.type.disable="Désactiver" AdvSceneSwitcher.action.filter.type.disable="Désactiver"
@@ -701,8 +701,8 @@ AdvSceneSwitcher.action.variable.invalidSelection="Sélection invalide !"
AdvSceneSwitcher.action.variable.actionNoVariableSupport="La récupération de valeurs de variables à partir d'actions %1 n'est pas prise en charge !" AdvSceneSwitcher.action.variable.actionNoVariableSupport="La récupération de valeurs de variables à partir d'actions %1 n'est pas prise en charge !"
AdvSceneSwitcher.action.variable.conditionNoVariableSupport="La récupération de valeurs de variables à partir de conditions %1 n'est pas prise en charge !" AdvSceneSwitcher.action.variable.conditionNoVariableSupport="La récupération de valeurs de variables à partir de conditions %1 n'est pas prise en charge !"
AdvSceneSwitcher.action.variable.currentSegmentValue="Valeur actuelle :" AdvSceneSwitcher.action.variable.currentSegmentValue="Valeur actuelle :"
AdvSceneSwitcher.action.variable.layout.substringIndex="Début de la sous-chaîne :{{subStringStart}}Taille de la sous-chaîne :{{subStringSize}}" AdvSceneSwitcher.action.variable.layout.substringIndex="Début de la sous-chaîne :{{subStringStart}}Taille de la sous-chaîne :{{subStringSize}}{{subStringRegex}}"
AdvSceneSwitcher.action.variable.layout.substringRegex="Attribuer la valeur du match{{regexMatchIdx}}en utilisant une expression régulière :" AdvSceneSwitcher.action.variable.layout.substringRegex="Attribuer la valeur du match{{regexMatchIdx}}en utilisant une expression régulière:{{subStringRegex}}"
AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}Utiliser un message personnalisé{{inputPrompt}}" AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}Utiliser un message personnalisé{{inputPrompt}}"
AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}Remplir avec un indicateur de position{{inputPlaceholder}}" AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}Remplir avec un indicateur de position{{inputPlaceholder}}"
AdvSceneSwitcher.action.projector="Projecteur" AdvSceneSwitcher.action.projector="Projecteur"

View File

@@ -870,7 +870,6 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="非表示"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="切り替え" AdvSceneSwitcher.action.sceneVisibility.type.toggle="切り替え"
; AdvSceneSwitcher.action.sceneVisibility.type.source="Source" ; AdvSceneSwitcher.action.sceneVisibility.type.source="Source"
; AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Any" ; AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Any"
; AdvSceneSwitcher.action.sceneVisibility.entry="On{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="フィルタ" AdvSceneSwitcher.action.filter="フィルタ"
AdvSceneSwitcher.action.filter.type.enable="有効にする" AdvSceneSwitcher.action.filter.type.enable="有効にする"
AdvSceneSwitcher.action.filter.type.disable="無効化" AdvSceneSwitcher.action.filter.type.disable="無効化"
@@ -1149,8 +1148,8 @@ AdvSceneSwitcher.action.variable.currentSegmentValue="現在値:"
; AdvSceneSwitcher.action.variable.layout.other="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}{{tempVarsHelp}}{{sceneItemIndex}}{{direction}}{{stringLength}}{{paddingCharSelection}}" ; AdvSceneSwitcher.action.variable.layout.other="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}{{tempVarsHelp}}{{sceneItemIndex}}{{direction}}{{stringLength}}{{paddingCharSelection}}"
AdvSceneSwitcher.action.variable.layout.pad="{{actions}}の{{variables}}の長さを{{stringLength}}にするには、{{paddingCharSelection}}を{{direction}}に追加します。" AdvSceneSwitcher.action.variable.layout.pad="{{actions}}の{{variables}}の長さを{{stringLength}}にするには、{{paddingCharSelection}}を{{direction}}に追加します。"
AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}の{{variables}}の長さを{{stringLength}}にするには、{{direction}}から文字を削除します。" AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}の{{variables}}の長さを{{stringLength}}にするには、{{direction}}から文字を削除します。"
AdvSceneSwitcher.action.variable.layout.substringIndex="部分文字列の開始:{{subStringStart}}部分文字列のサイズ:{{subStringSize}}" AdvSceneSwitcher.action.variable.layout.substringIndex="部分文字列の開始:{{subStringStart}}部分文字列のサイズ:{{subStringSize}}{{subStringRegex}}"
AdvSceneSwitcher.action.variable.layout.substringRegex="正規表現を使用して{{regexMatchIdx}}一致の値を割り当てます:" AdvSceneSwitcher.action.variable.layout.substringRegex="正規表現を使用して{{regexMatchIdx}}一致の値を割り当てます:{{subStringRegex}}"
; AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}" ; AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}"
AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}カスタム プロンプトを使用する{{inputPrompt}}" AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}カスタム プロンプトを使用する{{inputPrompt}}"
AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}プレースホルダーを入力{{inputPlaceholder}}" AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}プレースホルダーを入力{{inputPlaceholder}}"
@@ -1193,10 +1192,10 @@ AdvSceneSwitcher.action.twitch.type.user.getInfo="ユーザー情報を取得"
AdvSceneSwitcher.action.twitch.type.reward.getInfo="チャンネルポイントリワード情報を取得" AdvSceneSwitcher.action.twitch.type.reward.getInfo="チャンネルポイントリワード情報を取得"
AdvSceneSwitcher.action.twitch.reward.toggleControl="リワード名/変数選択コントロールの切り替え" AdvSceneSwitcher.action.twitch.reward.toggleControl="リワード名/変数選択コントロールの切り替え"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Twitchアカウントを選択しないとカテゴリを選択できません" AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Twitchアカウントを選択しないとカテゴリを選択できません"
; AdvSceneSwitcher.action.twitch.entry.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}" ; AdvSceneSwitcher.action.twitch.layout.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}"
AdvSceneSwitcher.action.twitch.entry.chat="{{channel}}でアカウント{{account}}{{actions}}を使用しています" AdvSceneSwitcher.action.twitch.layout.chat="{{channel}}でアカウント{{account}}{{actions}}を使用しています"
AdvSceneSwitcher.action.twitch.entry.user.getInfo="{{userInfoQueryType}}{{userLogin}}{{userId}}でアカウント{{account}}{{actions}}を使用" AdvSceneSwitcher.action.twitch.layout.user.getInfo="{{userInfoQueryType}}{{userLogin}}{{userId}}でアカウント{{account}}{{actions}}を使用"
AdvSceneSwitcher.action.twitch.entry.reward.getInfo="チャンネル{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}でアカウント{{account}}{{actions}}を使用" AdvSceneSwitcher.action.twitch.layout.reward.getInfo="チャンネル{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}でアカウント{{account}}{{actions}}を使用"
AdvSceneSwitcher.action.twitch.title.title="タイトルを入力" AdvSceneSwitcher.action.twitch.title.title="タイトルを入力"
AdvSceneSwitcher.action.twitch.marker.description="マーカーの説明" AdvSceneSwitcher.action.twitch.marker.description="マーカーの説明"
AdvSceneSwitcher.action.twitch.clip.hasDelay="クリップをキャプチャする前にわずかな遅延を追加します" AdvSceneSwitcher.action.twitch.clip.hasDelay="クリップをキャプチャする前にわずかな遅延を追加します"

View File

@@ -781,7 +781,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="Ocultar"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="Alternar" AdvSceneSwitcher.action.sceneVisibility.type.toggle="Alternar"
AdvSceneSwitcher.action.sceneVisibility.type.source="Fonte" AdvSceneSwitcher.action.sceneVisibility.type.source="Fonte"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Qualquer" 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="Filtro"
AdvSceneSwitcher.action.filter.type.enable="Habilitar" AdvSceneSwitcher.action.filter.type.enable="Habilitar"
AdvSceneSwitcher.action.filter.type.disable="Desabilitar" AdvSceneSwitcher.action.filter.type.disable="Desabilitar"
@@ -1030,8 +1030,8 @@ AdvSceneSwitcher.action.variable.conditionNoVariableSupport="Obter valores de va
AdvSceneSwitcher.action.variable.currentSegmentValue="Valor atual:" AdvSceneSwitcher.action.variable.currentSegmentValue="Valor atual:"
AdvSceneSwitcher.action.variable.layout.pad="{{actions}}de{{variables}}para comprimento{{stringLength}}adicionando{{paddingCharSelection}}ao{{direction}}" AdvSceneSwitcher.action.variable.layout.pad="{{actions}}de{{variables}}para comprimento{{stringLength}}adicionando{{paddingCharSelection}}ao{{direction}}"
AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}de{{variables}}para comprimento{{stringLength}}removendo caracteres da{{direction}}" AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}de{{variables}}para comprimento{{stringLength}}removendo caracteres da{{direction}}"
AdvSceneSwitcher.action.variable.layout.substringIndex="Início da substring:{{subStringStart}}Tamanho da substring:{{subStringSize}}" AdvSceneSwitcher.action.variable.layout.substringIndex="Início da substring:{{subStringStart}}Tamanho da substring:{{subStringSize}}{{subStringRegex}}"
AdvSceneSwitcher.action.variable.layout.substringRegex="Atribuir valor do{{regexMatchIdx}}correspondência usando expressão regular:" AdvSceneSwitcher.action.variable.layout.substringRegex="Atribuir valor do{{regexMatchIdx}}correspondência usando expressão regular:{{subStringRegex}}"
AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}" AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}"
AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}Usar prompt personalizado{{inputPrompt}}" AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}Usar prompt personalizado{{inputPrompt}}"
AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}Preencher com placeholder{{inputPlaceholder}}" AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}Preencher com placeholder{{inputPlaceholder}}"
@@ -1065,8 +1065,8 @@ AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable="Habilitar modo apenas
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="Desabilitar modo apenas emotes no chat" AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="Desabilitar modo apenas emotes no chat"
AdvSceneSwitcher.action.twitch.type.chat.sendMessage="Enviar mensagem de chat" AdvSceneSwitcher.action.twitch.type.chat.sendMessage="Enviar mensagem de chat"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Não é possível selecionar categoria sem selecionar uma conta Twitch primeiro!" AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Não é possível selecionar categoria sem selecionar uma conta Twitch primeiro!"
AdvSceneSwitcher.action.twitch.entry.default="Em{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}" AdvSceneSwitcher.action.twitch.layout.default="Em{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}"
AdvSceneSwitcher.action.twitch.entry.chat="Usando conta{{account}}{{actions}}em{{channel}}" AdvSceneSwitcher.action.twitch.layout.chat="Usando conta{{account}}{{actions}}em{{channel}}"
AdvSceneSwitcher.action.twitch.title.title="Digite o título" AdvSceneSwitcher.action.twitch.title.title="Digite o título"
AdvSceneSwitcher.action.twitch.marker.description="Descreva o marcador" AdvSceneSwitcher.action.twitch.marker.description="Descreva o marcador"
AdvSceneSwitcher.action.twitch.clip.hasDelay="Adicionar um leve atraso antes de capturar o clipe" AdvSceneSwitcher.action.twitch.clip.hasDelay="Adicionar um leve atraso antes de capturar o clipe"

View File

@@ -281,7 +281,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.show="Göster"
AdvSceneSwitcher.action.sceneVisibility.type.hide="Gizle" AdvSceneSwitcher.action.sceneVisibility.type.hide="Gizle"
AdvSceneSwitcher.action.sceneVisibility.type.source="Kayıt" AdvSceneSwitcher.action.sceneVisibility.type.source="Kayıt"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Herhangi" 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="Filtrele"
AdvSceneSwitcher.action.filter.type.enable="Etkin" AdvSceneSwitcher.action.filter.type.enable="Etkin"
AdvSceneSwitcher.action.filter.type.disable="Etkisiz" AdvSceneSwitcher.action.filter.type.disable="Etkisiz"

View File

@@ -840,7 +840,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="隐藏"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="切换可见性" AdvSceneSwitcher.action.sceneVisibility.type.toggle="切换可见性"
AdvSceneSwitcher.action.sceneVisibility.type.source="源" AdvSceneSwitcher.action.sceneVisibility.type.source="源"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="任何" 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="滤镜"
AdvSceneSwitcher.action.filter.type.enable="开启" AdvSceneSwitcher.action.filter.type.enable="开启"
AdvSceneSwitcher.action.filter.type.disable="关闭" AdvSceneSwitcher.action.filter.type.disable="关闭"
@@ -1100,8 +1100,8 @@ AdvSceneSwitcher.action.variable.conditionNoVariableSupport="不支持从 %1 条
AdvSceneSwitcher.action.variable.currentSegmentValue="当前值:" AdvSceneSwitcher.action.variable.currentSegmentValue="当前值:"
AdvSceneSwitcher.action.variable.layout.pad="{{actions}}于{{variables}}的{{direction}}起,内容为{{paddingCharSelection}}将其变量值长度增加至{{stringLength}}." AdvSceneSwitcher.action.variable.layout.pad="{{actions}}于{{variables}}的{{direction}}起,内容为{{paddingCharSelection}}将其变量值长度增加至{{stringLength}}."
AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}于{{variables}}的{{direction}}起,将变量长度缩减至{{stringLength}}" AdvSceneSwitcher.action.variable.layout.truncate="{{actions}}于{{variables}}的{{direction}}起,将变量长度缩减至{{stringLength}}"
AdvSceneSwitcher.action.variable.layout.substringIndex="截取字符串开始位置:{{subStringStart}} 截取字符串长度:{{subStringSize}}" AdvSceneSwitcher.action.variable.layout.substringIndex="截取字符串开始位置:{{subStringStart}} 截取字符串长度:{{subStringSize}}{{subStringRegex}}"
AdvSceneSwitcher.action.variable.layout.substringRegex="使用正则表达式为 {{regexMatchIdx}} 匹配的值:" AdvSceneSwitcher.action.variable.layout.substringRegex="使用正则表达式为 {{regexMatchIdx}} 匹配的值:{{subStringRegex}}"
AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}" AdvSceneSwitcher.action.variable.layout.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}"
AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}使用自定义提示{{inputPrompt}}" AdvSceneSwitcher.action.variable.layout.userInput.customPrompt="{{useCustomPrompt}}使用自定义提示{{inputPrompt}}"
AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}用占位符填充{{inputPlaceholder}}" AdvSceneSwitcher.action.variable.layout.userInput.placeholder="{{useInputPlaceholder}}用占位符填充{{inputPlaceholder}}"
@@ -1139,10 +1139,10 @@ AdvSceneSwitcher.action.twitch.type.user.getInfo="获取用户信息"
AdvSceneSwitcher.action.twitch.type.reward.getInfo="获取频道积分奖励信息" AdvSceneSwitcher.action.twitch.type.reward.getInfo="获取频道积分奖励信息"
AdvSceneSwitcher.action.twitch.reward.toggleControl="切换奖励名称/变量选择控制" AdvSceneSwitcher.action.twitch.reward.toggleControl="切换奖励名称/变量选择控制"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="在未选择 Twitch 帐户的情况下无法选择类别!" AdvSceneSwitcher.action.twitch.categorySelectionDisabled="在未选择 Twitch 帐户的情况下无法选择类别!"
AdvSceneSwitcher.action.twitch.entry.default="{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}" AdvSceneSwitcher.action.twitch.layout.default="{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}"
AdvSceneSwitcher.action.twitch.entry.chat="使用账户{{account}}{{actions}}{{channel}}" AdvSceneSwitcher.action.twitch.layout.chat="使用账户{{account}}{{actions}}{{channel}}"
AdvSceneSwitcher.action.twitch.entry.user.getInfo="使用账户{{account}}{{actions}}{{userInfoQueryType}}{{userLogin}}{{userId}}" AdvSceneSwitcher.action.twitch.layout.user.getInfo="使用账户{{account}}{{actions}}{{userInfoQueryType}}{{userLogin}}{{userId}}"
AdvSceneSwitcher.action.twitch.entry.reward.getInfo="使用账户{{account}}{{actions}} 频道{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}" AdvSceneSwitcher.action.twitch.layout.reward.getInfo="使用账户{{account}}{{actions}} 频道{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
AdvSceneSwitcher.action.twitch.title.title="输入标题" AdvSceneSwitcher.action.twitch.title.title="输入标题"
AdvSceneSwitcher.action.twitch.marker.description="标记描述" AdvSceneSwitcher.action.twitch.marker.description="标记描述"
AdvSceneSwitcher.action.twitch.clip.hasDelay="在捕捉视频片段前稍加延迟" AdvSceneSwitcher.action.twitch.clip.hasDelay="在捕捉视频片段前稍加延迟"

View File

@@ -68,7 +68,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>962</width> <width>962</width>
<height>1132</height> <height>1160</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_19"> <layout class="QVBoxLayout" name="verticalLayout_19">
@@ -268,9 +268,19 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="uiHintsDisable"> <widget class="QCheckBox" name="alwaysShowMacroSearch">
<property name="text"> <property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints</string> <string>AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowMacroSearch</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="alwaysShowFeatureTabs">
<property name="toolTip">
<string>AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowFeatureTabs.tooltip</string>
</property>
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowFeatureTabs</string>
</property> </property>
</widget> </widget>
</item> </item>
@@ -282,9 +292,9 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="alwaysShowMacroSearch"> <widget class="QCheckBox" name="uiHintsDisable">
<property name="text"> <property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowMacroSearch</string> <string>AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@@ -1,5 +1,6 @@
#include "advanced-scene-switcher.hpp" #include "advanced-scene-switcher.hpp"
#include "backup.hpp" #include "backup.hpp"
#include "crash-handler.hpp"
#include "log-helper.hpp" #include "log-helper.hpp"
#include "macro-helpers.hpp" #include "macro-helpers.hpp"
#include "obs-module-helper.hpp" #include "obs-module-helper.hpp"
@@ -207,9 +208,15 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
switcher->LoadSettings(data); switcher->LoadSettings(data);
switcher->m.unlock(); switcher->m.unlock();
if (!switcher->stop) { if (switcher->stop) {
switcher->Start(); return;
} }
if (ShouldSkipPluginStartOnUncleanShutdown()) {
return;
}
switcher->Start();
} }
} }

View File

@@ -1029,6 +1029,8 @@ void AdvSceneSwitcher::SetupGeneralTab()
setTabOrder(ui->importSettings, ui->cooldownTime); setTabOrder(ui->importSettings, ui->cooldownTime);
setTabOrder(ui->cooldownTime, ui->noMatchDontSwitch); setTabOrder(ui->cooldownTime, ui->noMatchDontSwitch);
SetupShowAllTabsCheckBox(ui->alwaysShowFeatureTabs, ui->tabWidget);
MinimizeSizeOfColumn(ui->statusLayout, 0); MinimizeSizeOfColumn(ui->statusLayout, 0);
setWindowTitle(windowTitle() + " - " + g_GIT_TAG); setWindowTitle(windowTitle() + " - " + g_GIT_TAG);
} }

View File

@@ -28,8 +28,7 @@ static inline void populateActionSelection(QComboBox *list)
} }
MacroActionEdit::MacroActionEdit(QWidget *parent, MacroActionEdit::MacroActionEdit(QWidget *parent,
std::shared_ptr<MacroAction> *entryData, std::shared_ptr<MacroAction> *entryData)
const std::string &id)
: MacroSegmentEdit(parent), : MacroSegmentEdit(parent),
_actionSelection(new FilterComboBox()), _actionSelection(new FilterComboBox()),
_enable(new SwitchButton()), _enable(new SwitchButton()),

View File

@@ -13,10 +13,8 @@ class MacroActionEdit : public MacroSegmentEdit {
Q_OBJECT Q_OBJECT
public: public:
MacroActionEdit( MacroActionEdit(QWidget *parent = nullptr,
QWidget *parent = nullptr, std::shared_ptr<MacroAction> * = nullptr);
std::shared_ptr<MacroAction> * = nullptr,
const std::string &id = MacroAction::GetDefaultID().data());
void SetupWidgets(bool basicSetup = false); void SetupWidgets(bool basicSetup = false);
void SetEntryData(std::shared_ptr<MacroAction> *); void SetEntryData(std::shared_ptr<MacroAction> *);

View File

@@ -93,33 +93,46 @@ bool MacroActionMacro::PerformAction()
return true; return true;
} }
switch (_action) { const auto performActionForMacro = [this](Macro *macro) {
case Action::PAUSE: switch (_action) {
macro->SetPaused(); case Action::PAUSE:
break; macro->SetPaused();
case Action::UNPAUSE: break;
macro->SetPaused(false); case Action::UNPAUSE:
break; macro->SetPaused(false);
case Action::TOGGLE_PAUSE: break;
macro->SetPaused(!macro->Paused()); case Action::TOGGLE_PAUSE:
break; macro->SetPaused(!macro->Paused());
case Action::RESET_COUNTER: break;
macro->ResetRunCount(); case Action::RESET_COUNTER:
break; macro->ResetRunCount();
case Action::RUN_ACTIONS: break;
RunActions(macro.get()); case Action::RUN_ACTIONS:
break; RunActions(macro);
case Action::STOP: break;
macro->Stop(); case Action::STOP:
break; macro->Stop();
case Action::DISABLE_ACTION: break;
case Action::ENABLE_ACTION: case Action::DISABLE_ACTION:
case Action::TOGGLE_ACTION: case Action::ENABLE_ACTION:
AdjustActionState(macro.get()); case Action::TOGGLE_ACTION:
break; AdjustActionState(macro);
default: break;
break; default:
break;
}
};
if (!IsGroupMacro(macro.get())) {
performActionForMacro(macro.get());
return true;
} }
auto macros = GetGroupMacroEntries(macro.get());
for (const auto &macro : macros) {
performActionForMacro(macro.get());
}
return true; return true;
} }
@@ -404,6 +417,7 @@ MacroActionMacroEdit::MacroActionMacroEdit(
populateActionTypes(_actionTypes); populateActionTypes(_actionTypes);
_conditionMacros->HideSelectedMacro(); _conditionMacros->HideSelectedMacro();
_conditionMacros->HideGroups();
QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)), QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(MacroChanged(const QString &))); this, SLOT(MacroChanged(const QString &)));

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 { struct AskForInputParams {
AskForInputParams(const QString &prompt_, const QString &placeholder_) AskForInputParams(const QString &prompt_, const QString &placeholder_)
: prompt(prompt_), : prompt(prompt_),
@@ -458,6 +495,9 @@ bool MacroActionVariable::PerformAction()
case Action::RANDOM_NUMBER: case Action::RANDOM_NUMBER:
GenerateRandomNumber(var.get()); GenerateRandomNumber(var.get());
return true; return true;
case Action::RANDOM_LIST_VALUE:
PickRandomValue(var.get());
return true;
} }
return true; return true;
@@ -498,6 +538,8 @@ bool MacroActionVariable::Save(obs_data_t *obj) const
_randomNumberStart.Save(obj, "randomNumberStart"); _randomNumberStart.Save(obj, "randomNumberStart");
_randomNumberEnd.Save(obj, "randomNumberEnd"); _randomNumberEnd.Save(obj, "randomNumberEnd");
obs_data_set_bool(obj, "generateInteger", _generateInteger); obs_data_set_bool(obj, "generateInteger", _generateInteger);
_randomValues.Save(obj, "randomValues", "value");
obs_data_set_bool(obj, "allowRepeatValues", _allowRepeatValues);
_jsonQuery.Save(obj, "jsonQuery"); _jsonQuery.Save(obj, "jsonQuery");
_jsonIndex.Save(obj, "jsonIndex"); _jsonIndex.Save(obj, "jsonIndex");
@@ -556,6 +598,8 @@ bool MacroActionVariable::Load(obs_data_t *obj)
_randomNumberStart.Load(obj, "randomNumberStart"); _randomNumberStart.Load(obj, "randomNumberStart");
_randomNumberEnd.Load(obj, "randomNumberEnd"); _randomNumberEnd.Load(obj, "randomNumberEnd");
_generateInteger = obs_data_get_bool(obj, "generateInteger"); _generateInteger = obs_data_get_bool(obj, "generateInteger");
_randomValues.Load(obj, "randomValues", "value");
_allowRepeatValues = obs_data_get_bool(obj, "allowRepeatValues");
_jsonQuery.Load(obj, "jsonQuery"); _jsonQuery.Load(obj, "jsonQuery");
_jsonIndex.Load(obj, "jsonIndex"); _jsonIndex.Load(obj, "jsonIndex");
@@ -724,6 +768,8 @@ static inline void populateActionSelection(QComboBox *list)
"AdvSceneSwitcher.action.variable.type.roundToInt"}, "AdvSceneSwitcher.action.variable.type.roundToInt"},
{MacroActionVariable::Action::RANDOM_NUMBER, {MacroActionVariable::Action::RANDOM_NUMBER,
"AdvSceneSwitcher.action.variable.type.randomNumber"}, "AdvSceneSwitcher.action.variable.type.randomNumber"},
{MacroActionVariable::Action::RANDOM_LIST_VALUE,
"AdvSceneSwitcher.action.variable.type.randomListValue"},
{true, ""}, // Separator {true, ""}, // Separator
{MacroActionVariable::Action::USER_INPUT, {MacroActionVariable::Action::USER_INPUT,
@@ -801,11 +847,10 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_segmentValueStatus(new QLabel()), _segmentValueStatus(new QLabel()),
_segmentValue(new ResizingPlainTextEdit(this, 10, 1, 1)), _segmentValue(new ResizingPlainTextEdit(this, 10, 1, 1)),
_substringLayout(new QVBoxLayout()), _substringLayout(new QVBoxLayout()),
_subStringIndexEntryLayout(new QHBoxLayout()), _subStringControlsLayout(new QHBoxLayout()),
_subStringRegexEntryLayout(new QHBoxLayout()),
_subStringStart(new VariableSpinBox(this)), _subStringStart(new VariableSpinBox(this)),
_subStringSize(new VariableSpinBox(this)), _subStringSize(new VariableSpinBox(this)),
_substringRegex(new RegexConfigWidget(parent)), _subStringRegex(new RegexConfigWidget(parent)),
_regexPattern(new ResizingPlainTextEdit(this, 10, 1, 1)), _regexPattern(new ResizingPlainTextEdit(this, 10, 1, 1)),
_regexMatchIdx(new VariableSpinBox(this)), _regexMatchIdx(new VariableSpinBox(this)),
_findReplaceLayout(new QHBoxLayout()), _findReplaceLayout(new QHBoxLayout()),
@@ -837,7 +882,13 @@ MacroActionVariableEdit::MacroActionVariableEdit(
obs_module_text( obs_module_text(
"AdvSceneSwitcher.action.variable.generateInteger"), "AdvSceneSwitcher.action.variable.generateInteger"),
this)), 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)), _jsonQuery(new VariableLineEdit(this)),
_jsonQueryHelp(new HelpIcon( _jsonQueryHelp(new HelpIcon(
obs_module_text( obs_module_text(
@@ -873,6 +924,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_randomNumberStart->setMaximum(9999999999); _randomNumberStart->setMaximum(9999999999);
_randomNumberEnd->setMinimum(-9999999999); _randomNumberEnd->setMinimum(-9999999999);
_randomNumberEnd->setMaximum(9999999999); _randomNumberEnd->setMaximum(9999999999);
_randomValues->SetMaxStringSize(99999999);
_jsonIndex->setMaximum(999); _jsonIndex->setMaximum(999);
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)), QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
@@ -901,7 +953,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_subStringSize, _subStringSize,
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)), SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this, SLOT(SubStringSizeChanged(const NumberVariable<int> &))); this, SLOT(SubStringSizeChanged(const NumberVariable<int> &)));
QWidget::connect(_substringRegex, QWidget::connect(_subStringRegex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this, SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(SubStringRegexChanged(const RegexConfig &))); SLOT(SubStringRegexChanged(const RegexConfig &)));
QWidget::connect(_regexPattern, SIGNAL(textChanged()), this, QWidget::connect(_regexPattern, SIGNAL(textChanged()), this,
@@ -961,6 +1013,11 @@ MacroActionVariableEdit::MacroActionVariableEdit(
SLOT(RandomNumberEndChanged(const NumberVariable<double> &))); SLOT(RandomNumberEndChanged(const NumberVariable<double> &)));
QWidget::connect(_generateInteger, SIGNAL(stateChanged(int)), this, QWidget::connect(_generateInteger, SIGNAL(stateChanged(int)), this,
SLOT(GenerateIntegerChanged(int))); 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, QWidget::connect(_jsonQuery, SIGNAL(editingFinished()), this,
SLOT(JsonQueryChanged())); SLOT(JsonQueryChanged()));
QWidget::connect( QWidget::connect(
@@ -975,9 +1032,6 @@ MacroActionVariableEdit::MacroActionVariableEdit(
{"{{strValue}}", _strValue}, {"{{strValue}}", _strValue},
{"{{numValue}}", _numValue}, {"{{numValue}}", _numValue},
{"{{segmentIndex}}", _segmentIdx}, {"{{segmentIndex}}", _segmentIdx},
{"{{subStringStart}}", _subStringStart},
{"{{subStringSize}}", _subStringSize},
{"{{regexMatchIdx}}", _regexMatchIdx},
{"{{findRegex}}", _findRegex}, {"{{findRegex}}", _findRegex},
{"{{findStr}}", _findStr}, {"{{findStr}}", _findStr},
{"{{replaceStr}}", _replaceStr}, {"{{replaceStr}}", _replaceStr},
@@ -1009,12 +1063,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
PlaceWidgets( PlaceWidgets(
obs_module_text( obs_module_text(
"AdvSceneSwitcher.action.variable.layout.substringIndex"), "AdvSceneSwitcher.action.variable.layout.substringIndex"),
_subStringIndexEntryLayout, widgetPlaceholders); _subStringControlsLayout, widgetPlaceholders);
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.variable.layout.substringRegex"),
_subStringRegexEntryLayout, widgetPlaceholders);
PlaceWidgets( PlaceWidgets(
obs_module_text( obs_module_text(
@@ -1035,15 +1084,17 @@ MacroActionVariableEdit::MacroActionVariableEdit(
obs_module_text( obs_module_text(
"AdvSceneSwitcher.action.variable.layout.randomNumber"), "AdvSceneSwitcher.action.variable.layout.randomNumber"),
randomLayout, widgetPlaceholders); randomLayout, widgetPlaceholders);
_randomLayout->addLayout(randomLayout); _randomNumberLayout->addLayout(randomLayout);
_randomLayout->addWidget(_generateInteger); _randomNumberLayout->addWidget(_generateInteger);
_randomValueLayout->addWidget(_randomValues);
_randomValueLayout->addWidget(_allowRepeatValues);
auto regexConfigLayout = new QHBoxLayout; auto regexConfigLayout = new QHBoxLayout;
regexConfigLayout->addWidget(_substringRegex); regexConfigLayout->addWidget(_subStringRegex);
regexConfigLayout->addStretch(); regexConfigLayout->addStretch();
_substringLayout->addLayout(_subStringIndexEntryLayout); _substringLayout->addLayout(_subStringControlsLayout);
_substringLayout->addLayout(_subStringRegexEntryLayout);
_substringLayout->addWidget(_regexPattern); _substringLayout->addWidget(_regexPattern);
_substringLayout->addLayout(regexConfigLayout); _substringLayout->addLayout(regexConfigLayout);
@@ -1056,7 +1107,8 @@ MacroActionVariableEdit::MacroActionVariableEdit(
layout->addWidget(_mathExpressionResult); layout->addWidget(_mathExpressionResult);
layout->addLayout(_promptLayout); layout->addLayout(_promptLayout);
layout->addLayout(_placeholderLayout); layout->addLayout(_placeholderLayout);
layout->addLayout(_randomLayout); layout->addLayout(_randomNumberLayout);
layout->addLayout(_randomValueLayout);
setLayout(layout); setLayout(layout);
_entryData = entryData; _entryData = entryData;
@@ -1090,7 +1142,7 @@ void MacroActionVariableEdit::UpdateEntryData()
: MacroSegmentSelection::Type::ACTION); : MacroSegmentSelection::Type::ACTION);
_subStringStart->SetValue(_entryData->_subStringStart); _subStringStart->SetValue(_entryData->_subStringStart);
_subStringSize->SetValue(_entryData->_subStringSize); _subStringSize->SetValue(_entryData->_subStringSize);
_substringRegex->SetRegexConfig(_entryData->_subStringRegex); _subStringRegex->SetRegexConfig(_entryData->_subStringRegex);
_findRegex->SetRegexConfig(_entryData->_findRegex); _findRegex->SetRegexConfig(_entryData->_findRegex);
_regexPattern->setPlainText( _regexPattern->setPlainText(
QString::fromStdString(_entryData->_regexPattern)); QString::fromStdString(_entryData->_regexPattern));
@@ -1115,6 +1167,8 @@ void MacroActionVariableEdit::UpdateEntryData()
_randomNumberStart->SetValue(_entryData->_randomNumberStart); _randomNumberStart->SetValue(_entryData->_randomNumberStart);
_randomNumberEnd->SetValue(_entryData->_randomNumberEnd); _randomNumberEnd->SetValue(_entryData->_randomNumberEnd);
_generateInteger->setChecked(_entryData->_generateInteger); _generateInteger->setChecked(_entryData->_generateInteger);
_allowRepeatValues->setChecked(_entryData->_allowRepeatValues);
_randomValues->SetStringList(_entryData->_randomValues);
_jsonQuery->setText(_entryData->_jsonQuery); _jsonQuery->setText(_entryData->_jsonQuery);
_jsonIndex->SetValue(_entryData->_jsonIndex); _jsonIndex->SetValue(_entryData->_jsonIndex);
@@ -1457,6 +1511,18 @@ void MacroActionVariableEdit::GenerateIntegerChanged(int value)
_entryData->_generateInteger = 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() void MacroActionVariableEdit::JsonQueryChanged()
{ {
GUARD_LOADING_AND_LOCK(); GUARD_LOADING_AND_LOCK();
@@ -1495,8 +1561,20 @@ void MacroActionVariableEdit::SetWidgetVisibility()
{"{{jsonQuery}}", _jsonQuery}, {"{{jsonQuery}}", _jsonQuery},
{"{{jsonQueryHelp}}", _jsonQueryHelp}, {"{{jsonQueryHelp}}", _jsonQueryHelp},
{"{{jsonIndex}}", _jsonIndex}, {"{{jsonIndex}}", _jsonIndex},
{"{{subStringRegex}}", _subStringRegex},
{"{{subStringStart}}", _subStringStart},
{"{{subStringSize}}", _subStringSize},
{"{{regexMatchIdx}}", _regexMatchIdx},
}; };
for (const auto &[_, widget] : widgetPlaceholders) {
_entryLayout->removeWidget(widget);
_subStringControlsLayout->removeWidget(widget);
}
ClearLayout(_entryLayout);
ClearLayout(_subStringControlsLayout);
const char *layoutString = ""; const char *layoutString = "";
if (_entryData->_action == MacroActionVariable::Action::PAD) { if (_entryData->_action == MacroActionVariable::Action::PAD) {
layoutString = obs_module_text( layoutString = obs_module_text(
@@ -1510,11 +1588,6 @@ void MacroActionVariableEdit::SetWidgetVisibility()
"AdvSceneSwitcher.action.variable.layout.other"); "AdvSceneSwitcher.action.variable.layout.other");
} }
for (const auto &[_, widget] : widgetPlaceholders) {
_entryLayout->removeWidget(widget);
}
ClearLayout(_entryLayout);
PlaceWidgets(layoutString, _entryLayout, widgetPlaceholders); PlaceWidgets(layoutString, _entryLayout, widgetPlaceholders);
if (_entryData->_action == MacroActionVariable::Action::SET_VALUE || if (_entryData->_action == MacroActionVariable::Action::SET_VALUE ||
@@ -1561,15 +1634,25 @@ void MacroActionVariableEdit::SetWidgetVisibility()
MacroActionVariable::Action::SET_ACTION_VALUE || MacroActionVariable::Action::SET_ACTION_VALUE ||
_entryData->_action == _entryData->_action ==
MacroActionVariable::Action::SET_CONDITION_VALUE); MacroActionVariable::Action::SET_CONDITION_VALUE);
bool showRegex = _entryData->_subStringRegex.Enabled();
layoutString =
showRegex
? "AdvSceneSwitcher.action.variable.layout.substringRegex"
: "AdvSceneSwitcher.action.variable.layout.substringIndex";
PlaceWidgets(obs_module_text(layoutString), _subStringControlsLayout,
widgetPlaceholders);
_subStringStart->setVisible(!showRegex);
_subStringSize->setVisible(!showRegex);
_regexMatchIdx->setVisible(showRegex);
SetLayoutVisible(_substringLayout, SetLayoutVisible(_substringLayout,
_entryData->_action == _entryData->_action ==
MacroActionVariable::Action::SUBSTRING); MacroActionVariable::Action::SUBSTRING);
if (_entryData->_action == MacroActionVariable::Action::SUBSTRING) { _regexPattern->setVisible(
bool showRegex = _entryData->_subStringRegex.Enabled(); showRegex &&
SetLayoutVisible(_subStringIndexEntryLayout, !showRegex); _entryData->_action == MacroActionVariable::Action::SUBSTRING);
SetLayoutVisible(_subStringRegexEntryLayout, showRegex);
_regexPattern->setVisible(showRegex);
}
SetLayoutVisible(_findReplaceLayout, SetLayoutVisible(_findReplaceLayout,
_entryData->_action == _entryData->_action ==
MacroActionVariable::Action::FIND_AND_REPLACE); MacroActionVariable::Action::FIND_AND_REPLACE);
@@ -1634,9 +1717,13 @@ void MacroActionVariableEdit::SetWidgetVisibility()
MacroActionVariable::Action::PAD); MacroActionVariable::Action::PAD);
_caseType->setVisible(_entryData->_action == _caseType->setVisible(_entryData->_action ==
MacroActionVariable::Action::CHANGE_CASE); MacroActionVariable::Action::CHANGE_CASE);
SetLayoutVisible(_randomLayout, SetLayoutVisible(_randomNumberLayout,
_entryData->_action == _entryData->_action ==
MacroActionVariable::Action::RANDOM_NUMBER); MacroActionVariable::Action::RANDOM_NUMBER);
SetLayoutVisible(
_randomValueLayout,
_entryData->_action ==
MacroActionVariable::Action::RANDOM_LIST_VALUE);
_jsonQuery->setVisible(_entryData->_action == _jsonQuery->setVisible(_entryData->_action ==
MacroActionVariable::Action::QUERY_JSON); MacroActionVariable::Action::QUERY_JSON);
_jsonQueryHelp->setVisible(_entryData->_action == _jsonQueryHelp->setVisible(_entryData->_action ==

View File

@@ -6,6 +6,7 @@
#include "resizing-text-edit.hpp" #include "resizing-text-edit.hpp"
#include "scene-selection.hpp" #include "scene-selection.hpp"
#include "single-char-selection.hpp" #include "single-char-selection.hpp"
#include "string-list.hpp"
#include "variable-line-edit.hpp" #include "variable-line-edit.hpp"
#include "variable-text-edit.hpp" #include "variable-text-edit.hpp"
#include "variable-spinbox.hpp" #include "variable-spinbox.hpp"
@@ -56,6 +57,7 @@ public:
QUERY_JSON, QUERY_JSON,
ARRAY_JSON, ARRAY_JSON,
COPY_VAR, COPY_VAR,
RANDOM_LIST_VALUE,
}; };
Action _action = Action::SET_VALUE; Action _action = Action::SET_VALUE;
@@ -107,6 +109,10 @@ public:
DoubleVariable _randomNumberEnd = 100; DoubleVariable _randomNumberEnd = 100;
bool _generateInteger = true; bool _generateInteger = true;
StringList _randomValues = {"value1", "value2", "value3"};
bool _allowRepeatValues = true;
std::optional<std::string> _lastRandomValue;
StringVariable _jsonQuery = "$.some.nested.value"; StringVariable _jsonQuery = "$.some.nested.value";
IntVariable _jsonIndex = 0; IntVariable _jsonIndex = 0;
@@ -119,6 +125,7 @@ private:
void HandleCaseChange(Variable *); void HandleCaseChange(Variable *);
void SetToSceneItemName(Variable *); void SetToSceneItemName(Variable *);
void GenerateRandomNumber(Variable *); void GenerateRandomNumber(Variable *);
void PickRandomValue(Variable *);
std::weak_ptr<MacroSegment> _macroSegment; std::weak_ptr<MacroSegment> _macroSegment;
int _segmentIdxLoadValue = -1; int _segmentIdxLoadValue = -1;
@@ -175,6 +182,8 @@ private slots:
void RandomNumberStartChanged(const NumberVariable<double> &); void RandomNumberStartChanged(const NumberVariable<double> &);
void RandomNumberEndChanged(const NumberVariable<double> &); void RandomNumberEndChanged(const NumberVariable<double> &);
void GenerateIntegerChanged(int); void GenerateIntegerChanged(int);
void RandomValueListChanged(const StringList &);
void AllowRepeatValuesChanged(int);
void JsonQueryChanged(); void JsonQueryChanged();
void JsonIndexChanged(const NumberVariable<int> &); void JsonIndexChanged(const NumberVariable<int> &);
@@ -194,11 +203,10 @@ private:
QLabel *_segmentValueStatus; QLabel *_segmentValueStatus;
ResizingPlainTextEdit *_segmentValue; ResizingPlainTextEdit *_segmentValue;
QVBoxLayout *_substringLayout; QVBoxLayout *_substringLayout;
QHBoxLayout *_subStringIndexEntryLayout; QHBoxLayout *_subStringControlsLayout;
QHBoxLayout *_subStringRegexEntryLayout;
VariableSpinBox *_subStringStart; VariableSpinBox *_subStringStart;
VariableSpinBox *_subStringSize; VariableSpinBox *_subStringSize;
RegexConfigWidget *_substringRegex; RegexConfigWidget *_subStringRegex;
ResizingPlainTextEdit *_regexPattern; ResizingPlainTextEdit *_regexPattern;
VariableSpinBox *_regexMatchIdx; VariableSpinBox *_regexMatchIdx;
QHBoxLayout *_findReplaceLayout; QHBoxLayout *_findReplaceLayout;
@@ -225,7 +233,10 @@ private:
VariableDoubleSpinBox *_randomNumberStart; VariableDoubleSpinBox *_randomNumberStart;
VariableDoubleSpinBox *_randomNumberEnd; VariableDoubleSpinBox *_randomNumberEnd;
QCheckBox *_generateInteger; QCheckBox *_generateInteger;
QVBoxLayout *_randomLayout; QVBoxLayout *_randomNumberLayout;
StringListEdit *_randomValues;
QCheckBox *_allowRepeatValues;
QVBoxLayout *_randomValueLayout;
VariableLineEdit *_jsonQuery; VariableLineEdit *_jsonQuery;
QLabel *_jsonQueryHelp; QLabel *_jsonQueryHelp;
VariableSpinBox *_jsonIndex; VariableSpinBox *_jsonIndex;

View File

@@ -100,7 +100,7 @@ void DurationModifierEdit::Collapse(bool collapse)
MacroConditionEdit::MacroConditionEdit( MacroConditionEdit::MacroConditionEdit(
QWidget *parent, std::shared_ptr<MacroCondition> *entryData, QWidget *parent, std::shared_ptr<MacroCondition> *entryData,
const std::string &id, bool isRootCondition) bool isRootCondition)
: MacroSegmentEdit(parent), : MacroSegmentEdit(parent),
_logicSelection(new QComboBox()), _logicSelection(new QComboBox()),
_conditionSelection(new FilterComboBox()), _conditionSelection(new FilterComboBox()),

View File

@@ -34,11 +34,9 @@ class MacroConditionEdit : public MacroSegmentEdit {
Q_OBJECT Q_OBJECT
public: public:
MacroConditionEdit( MacroConditionEdit(QWidget *parent = nullptr,
QWidget *parent = nullptr, std::shared_ptr<MacroCondition> * = nullptr,
std::shared_ptr<MacroCondition> * = nullptr, bool root = true);
const std::string &id = MacroCondition::GetDefaultID().data(),
bool root = true);
bool IsRootNode() const; bool IsRootNode() const;
void SetRootNode(bool); void SetRootNode(bool);
void SetupWidgets(bool basicSetup = false); void SetupWidgets(bool basicSetup = false);

View File

@@ -315,6 +315,7 @@ MacroConditionMacroEdit::MacroConditionMacroEdit(
_actionIndex(new MacroSegmentSelection( _actionIndex(new MacroSegmentSelection(
this, MacroSegmentSelection::Type::ACTION)) this, MacroSegmentSelection::Type::ACTION))
{ {
_macros->HideGroups();
_count->setMaximum(10000000); _count->setMaximum(10000000);
populateTypeSelection(_types); populateTypeSelection(_types);
populateCounterConditionSelection(_counterConditions); populateCounterConditionSelection(_counterConditions);

View File

@@ -418,8 +418,7 @@ void MacroEdit::PopulateMacroActions(Macro &m, uint32_t afterIdx)
ui->actionsList->SetVisibilityCheckEnable(false); ui->actionsList->SetVisibilityCheckEnable(false);
for (; afterIdx < actions.size(); afterIdx++) { for (; afterIdx < actions.size(); afterIdx++) {
auto newEntry = new MacroActionEdit(this, &actions[afterIdx], auto newEntry = new MacroActionEdit(this, &actions[afterIdx]);
actions[afterIdx]->GetId());
ui->actionsList->Add(newEntry); ui->actionsList->Add(newEntry);
} }
@@ -444,8 +443,7 @@ void MacroEdit::PopulateMacroElseActions(Macro &m, uint32_t afterIdx)
ui->elseActionsList->SetVisibilityCheckEnable(false); ui->elseActionsList->SetVisibilityCheckEnable(false);
for (; afterIdx < actions.size(); afterIdx++) { for (; afterIdx < actions.size(); afterIdx++) {
auto newEntry = new MacroActionEdit(this, &actions[afterIdx], auto newEntry = new MacroActionEdit(this, &actions[afterIdx]);
actions[afterIdx]->GetId());
ui->elseActionsList->Add(newEntry); ui->elseActionsList->Add(newEntry);
} }
@@ -472,8 +470,7 @@ void MacroEdit::PopulateMacroConditions(Macro &m, uint32_t afterIdx)
for (; afterIdx < conditions.size(); afterIdx++) { for (; afterIdx < conditions.size(); afterIdx++) {
auto newEntry = new MacroConditionEdit( auto newEntry = new MacroConditionEdit(
this, &conditions[afterIdx], this, &conditions[afterIdx], root);
conditions[afterIdx]->GetId(), root);
ui->conditionsList->Add(newEntry); ui->conditionsList->Add(newEntry);
root = false; root = false;
} }
@@ -1083,8 +1080,7 @@ void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id,
RunAndClearPostLoadSteps(); RunAndClearPostLoadSteps();
macro->UpdateActionIndices(); macro->UpdateActionIndices();
ui->actionsList->Insert( ui->actionsList->Insert(
idx, idx, new MacroActionEdit(this, &macro->Actions()[idx]));
new MacroActionEdit(this, &macro->Actions()[idx], id));
SetActionData(*macro); SetActionData(*macro);
} }
HighlightAction(idx); HighlightAction(idx);
@@ -1386,8 +1382,8 @@ void MacroEdit::AddMacroElseAction(Macro *macro, int idx, const std::string &id,
RunAndClearPostLoadSteps(); RunAndClearPostLoadSteps();
macro->UpdateElseActionIndices(); macro->UpdateElseActionIndices();
ui->elseActionsList->Insert( ui->elseActionsList->Insert(
idx, new MacroActionEdit( idx,
this, &macro->ElseActions()[idx], id)); new MacroActionEdit(this, &macro->ElseActions()[idx]));
SetElseActionData(*macro); SetElseActionData(*macro);
} }
HighlightElseAction(idx); HighlightElseAction(idx);
@@ -1589,7 +1585,7 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
ui->conditionsList->Insert( ui->conditionsList->Insert(
idx, idx,
new MacroConditionEdit(this, &macro->Conditions()[idx], new MacroConditionEdit(this, &macro->Conditions()[idx],
id, idx == 0)); idx == 0));
SetConditionData(*macro); SetConditionData(*macro);
} }
HighlightCondition(idx); HighlightCondition(idx);

View File

@@ -117,9 +117,32 @@ GetMacroConditions(Macro *macro)
return macro->Conditions(); return macro->Conditions();
} }
std::string_view GetSceneSwitchActionId() bool IsGroupMacro(Macro *macro)
{ {
return MacroAction::GetDefaultID(); return macro && macro->IsGroup();
}
std::vector<std::shared_ptr<Macro>> GetGroupMacroEntries(Macro *macro)
{
if (!macro || !macro->IsGroup()) {
return {};
}
std::vector<std::shared_ptr<Macro>> entries;
entries.reserve(macro->GroupSize());
const auto &macros = GetTopLevelMacros();
for (auto it = macros.begin(); it < macros.end(); it++) {
if ((*it)->Name() != macro->Name()) {
continue;
}
for (uint32_t i = 1; i <= macro->GroupSize(); i++) {
entries.emplace_back(*std::next(it, i));
}
break;
}
return entries;
} }
std::condition_variable &GetMacroWaitCV() std::condition_variable &GetMacroWaitCV()

View File

@@ -20,6 +20,8 @@ class Macro;
class MacroAction; class MacroAction;
class MacroCondition; class MacroCondition;
static const int macro_func = 10;
EXPORT std::deque<std::shared_ptr<Macro>> &GetTopLevelMacros(); EXPORT std::deque<std::shared_ptr<Macro>> &GetTopLevelMacros();
std::deque<std::shared_ptr<Macro>> &GetTemporaryMacros(); std::deque<std::shared_ptr<Macro>> &GetTemporaryMacros();
EXPORT std::deque<std::shared_ptr<Macro>> GetAllMacros(); EXPORT std::deque<std::shared_ptr<Macro>> GetAllMacros();
@@ -35,9 +37,8 @@ GetMacroElseActions(Macro *);
EXPORT std::optional<std::deque<std::shared_ptr<MacroCondition>>> EXPORT std::optional<std::deque<std::shared_ptr<MacroCondition>>>
GetMacroConditions(Macro *); GetMacroConditions(Macro *);
std::string_view GetSceneSwitchActionId(); EXPORT bool IsGroupMacro(Macro *);
EXPORT std::vector<std::shared_ptr<Macro>> GetGroupMacroEntries(Macro *);
constexpr auto macro_func = 10;
EXPORT std::condition_variable &GetMacroWaitCV(); EXPORT std::condition_variable &GetMacroWaitCV();
EXPORT std::condition_variable &GetMacroTransitionCV(); EXPORT std::condition_variable &GetMacroTransitionCV();

View File

@@ -244,22 +244,9 @@ bool MacroMatchesSearchFilter(Macro *macro)
} }
if (macro->IsGroup()) { if (macro->IsGroup()) {
std::vector<std::shared_ptr<Macro>> subItems; const auto groupEntries = GetGroupMacroEntries(macro);
subItems.reserve(macro->GroupSize()); for (const auto &entry : groupEntries) {
if (MacroMatchesSearchFilter(entry.get())) {
const auto &macros = GetTopLevelMacros();
for (auto it = macros.begin(); it < macros.end(); it++) {
if ((*it)->Name() != macro->Name()) {
continue;
}
for (uint32_t i = 1; i <= macro->GroupSize(); i++) {
subItems.emplace_back(*std::next(it, i));
}
break;
}
for (const auto &item : subItems) {
if (MacroMatchesSearchFilter(item.get())) {
return true; return true;
} }
} }

View File

@@ -15,9 +15,6 @@ MacroSelection::MacroSelection(QWidget *parent)
obs_module_text("AdvSceneSwitcher.selectMacro")) obs_module_text("AdvSceneSwitcher.selectMacro"))
{ {
for (const auto &m : GetTopLevelMacros()) { for (const auto &m : GetTopLevelMacros()) {
if (m->IsGroup()) {
continue;
}
addItem(QString::fromStdString(m->Name())); addItem(QString::fromStdString(m->Name()));
} }
@@ -66,6 +63,22 @@ void MacroSelection::HideSelectedMacro()
qobject_cast<QListView *>(view())->setRowHidden(idx, true); qobject_cast<QListView *>(view())->setRowHidden(idx, true);
} }
void MacroSelection::HideGroups()
{
for (const auto &macro : GetTopLevelMacros()) {
if (!macro->IsGroup()) {
continue;
}
int idx = findText(QString::fromStdString(macro->Name()));
if (idx == -1) {
continue;
}
qobject_cast<QListView *>(view())->setRowHidden(idx, true);
}
}
void MacroSelection::ShowAllMacros() void MacroSelection::ShowAllMacros()
{ {
auto v = qobject_cast<QListView *>(view()); auto v = qobject_cast<QListView *>(view());

View File

@@ -15,6 +15,7 @@ public:
MacroSelection(QWidget *parent); MacroSelection(QWidget *parent);
void SetCurrentMacro(const MacroRef &); void SetCurrentMacro(const MacroRef &);
void HideSelectedMacro(); // Macro currently being edited void HideSelectedMacro(); // Macro currently being edited
void HideGroups();
void ShowAllMacros(); void ShowAllMacros();
private slots: private slots:

View File

@@ -25,6 +25,8 @@ namespace advss {
static QObject *addPulse = nullptr; static QObject *addPulse = nullptr;
static QTimer onChangeHighlightTimer; static QTimer onChangeHighlightTimer;
static std::chrono::high_resolution_clock::time_point
lastOnChangeHighlightCheckTime{};
static void disableAddButtonHighlight() static void disableAddButtonHighlight()
{ {
@@ -147,20 +149,7 @@ void AdvSceneSwitcher::on_macroAdd_clicked()
static void addGroupSubitems(std::vector<std::shared_ptr<Macro>> &macros, static void addGroupSubitems(std::vector<std::shared_ptr<Macro>> &macros,
const std::shared_ptr<Macro> &group) const std::shared_ptr<Macro> &group)
{ {
std::vector<std::shared_ptr<Macro>> subitems; auto subitems = GetGroupMacroEntries(group.get());
subitems.reserve(group->GroupSize());
// Find all subitems
auto allMacros = GetTopLevelMacros();
for (auto it = allMacros.begin(); it < allMacros.end(); it++) {
if ((*it)->Name() != group->Name()) {
continue;
}
for (uint32_t i = 1; i <= group->GroupSize(); i++) {
subitems.emplace_back(*std::next(it, i));
}
break;
}
// Remove subitems which were already selected to avoid duplicates // Remove subitems which were already selected to avoid duplicates
for (const auto &subitem : subitems) { for (const auto &subitem : subitems) {
@@ -563,10 +552,14 @@ void AdvSceneSwitcher::HighlightOnChange() const
return; return;
} }
if (macro->OnChangePreventedActionsRecently()) { if (macro->OnChangePreventedActionsSince(
lastOnChangeHighlightCheckTime)) {
HighlightWidget(ui->runMacroOnChange, Qt::yellow, HighlightWidget(ui->runMacroOnChange, Qt::yellow,
Qt::transparent, true); Qt::transparent, true);
} }
lastOnChangeHighlightCheckTime =
std::chrono::high_resolution_clock::now();
} }
void AdvSceneSwitcher::on_macroSettings_clicked() void AdvSceneSwitcher::on_macroSettings_clicked()
@@ -665,6 +658,8 @@ void AdvSceneSwitcher::SetupMacroTab()
ui->macroPriorityWarning->setVisible( ui->macroPriorityWarning->setVisible(
switcher->functionNamesByPriority[0] != macro_func); switcher->functionNamesByPriority[0] != macro_func);
lastOnChangeHighlightCheckTime =
std::chrono::high_resolution_clock::now();
onChangeHighlightTimer.setInterval(1500); onChangeHighlightTimer.setInterval(1500);
connect(&onChangeHighlightTimer, SIGNAL(timeout()), this, connect(&onChangeHighlightTimer, SIGNAL(timeout()), this,
SLOT(HighlightOnChange())); SLOT(HighlightOnChange()));

View File

@@ -5,18 +5,17 @@
#include "path-helpers.hpp" #include "path-helpers.hpp"
#include "sync-helpers.hpp" #include "sync-helpers.hpp"
#include "ui-helpers.hpp" #include "ui-helpers.hpp"
#include "utility.hpp"
#include <obs.h> #include <obs.h>
#include <string> #include <string>
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QSpacerItem> #include <QSpacerItem>
#include <QPushButton>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QMouseEvent> #include <QMouseEvent>
#include <QStylePainter> #include <QStylePainter>
#include <QToolTip>
Q_DECLARE_METATYPE(std::shared_ptr<advss::Macro>); Q_DECLARE_METATYPE(std::shared_ptr<advss::Macro>);
@@ -62,12 +61,11 @@ MacroTreeItem::MacroTreeItem(MacroTree *tree, std::shared_ptr<Macro> macroItem,
_boxLayout = new QHBoxLayout(); _boxLayout = new QHBoxLayout();
_boxLayout->setContentsMargins(0, 0, 0, 0); _boxLayout->setContentsMargins(0, 0, 0, 0);
_boxLayout->addWidget(_running);
if (isGroup) { if (isGroup) {
_boxLayout->addWidget(_iconLabel); _boxLayout->addWidget(_iconLabel);
_boxLayout->addSpacing(2); _boxLayout->addSpacing(2);
_running->hide();
} }
_boxLayout->addWidget(_running);
_boxLayout->addWidget(_label); _boxLayout->addWidget(_label);
#ifdef __APPLE__ #ifdef __APPLE__
/* Hack: Fixes a bug where scrollbars would be above the lock icon */ /* Hack: Fixes a bug where scrollbars would be above the lock icon */
@@ -77,29 +75,138 @@ MacroTreeItem::MacroTreeItem(MacroTree *tree, std::shared_ptr<Macro> macroItem,
Update(true); Update(true);
setLayout(_boxLayout); setLayout(_boxLayout);
auto setRunning = [this](bool val) { connect(_running, SIGNAL(clicked(bool)), this,
_macro->SetPaused(!val); SLOT(RunningClicked(bool)));
};
connect(_running, &QAbstractButton::clicked, setRunning);
connect(MacroSignalManager::Instance(), SIGNAL(HighlightChanged(bool)), connect(MacroSignalManager::Instance(), SIGNAL(HighlightChanged(bool)),
this, SLOT(EnableHighlight(bool))); this, SLOT(EnableHighlight(bool)));
connect(MacroSignalManager::Instance(), connect(MacroSignalManager::Instance(),
SIGNAL(Rename(const QString &, const QString &)), this, SIGNAL(Rename(const QString &, const QString &)), this,
SLOT(MacroRenamed(const QString &, const QString &))); SLOT(MacroRenamed(const QString &, const QString &)));
connect(&_timer, SIGNAL(timeout()), this, SLOT(HighlightIfExecuted())); connect(&_timer, SIGNAL(timeout()), this, SLOT(HighlightIfExecuted()));
connect(&_timer, SIGNAL(timeout()), this, SLOT(UpdatePaused())); connect(&_timer, SIGNAL(timeout()), this, SLOT(UpdateRunning()));
UpdateRunning();
_timer.start(1500); _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) void MacroTreeItem::EnableHighlight(bool enable)
{ {
_highlight = enable; _highlight = enable;
} }
void MacroTreeItem::UpdatePaused() void MacroTreeItem::RunningClicked(bool running)
{
const auto updateWidget = [this](Macro *macro) {
if (!macro) {
return;
}
const auto &macros = GetTopLevelMacros();
bool found = false;
int idx = 0;
for (const auto &m : macros) {
if (m.get() == macro) {
found = true;
break;
}
idx++;
}
if (!found) {
return;
}
const auto widget = _tree->GetItemWidget(idx);
if (widget) {
widget->UpdateRunning();
}
};
if (!_macro->IsGroup()) {
_macro->SetPaused(!running);
if (!_macro->IsSubitem()) {
return;
}
const auto group = _macro->Parent();
updateWidget(group.get());
return;
}
const auto macros = GetGroupMacroEntries(_macro.get());
for (const auto &macro : macros) {
macro->SetPaused(!running);
}
// Update backend values before updating UI to prevent flickering in
// running state of the group
for (const auto &macro : macros) {
updateWidget(macro.get());
}
UpdateRunning();
}
void MacroTreeItem::UpdateRunning()
{ {
const QSignalBlocker blocker(_running); const QSignalBlocker blocker(_running);
_running->setChecked(!_macro->Paused());
if (!_macro->IsGroup()) {
_running->setChecked(!_macro->Paused());
return;
}
const auto macros = GetGroupMacroEntries(_macro.get());
bool allRunning = true;
bool allPaused = true;
for (const auto &macro : macros) {
if (macro->Paused()) {
allRunning = false;
} else {
allPaused = false;
}
}
if (allRunning) {
_running->setCheckState(Qt::Checked);
return;
}
if (allPaused) {
_running->setCheckState(Qt::Unchecked);
return;
}
_running->setCheckState(Qt::PartiallyChecked);
} }
void MacroTreeItem::HighlightIfExecuted() void MacroTreeItem::HighlightIfExecuted()
@@ -108,10 +215,19 @@ void MacroTreeItem::HighlightIfExecuted()
return; return;
} }
bool wasHighlighted = false;
if (_lastHighlightCheckTime.time_since_epoch().count() != 0 && if (_lastHighlightCheckTime.time_since_epoch().count() != 0 &&
_macro->WasExecutedSince(_lastHighlightCheckTime)) { _macro->WasExecutedSince(_lastHighlightCheckTime)) {
HighlightWidget(this, Qt::green, QColor(0, 0, 0, 0), true); 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(); _lastHighlightCheckTime = std::chrono::high_resolution_clock::now();
} }
@@ -188,7 +304,7 @@ void MacroTreeItem::Update(bool force)
_expand->blockSignals(true); _expand->blockSignals(true);
_expand->setChecked(_macro->IsCollapsed()); _expand->setChecked(_macro->IsCollapsed());
_expand->blockSignals(false); _expand->blockSignals(false);
connect(_expand, &QPushButton::toggled, this, connect(_expand, &QCheckBox::toggled, this,
&MacroTreeItem::ExpandClicked); &MacroTreeItem::ExpandClicked);
} else { } else {
_spacer = new QSpacerItem(3, 1); _spacer = new QSpacerItem(3, 1);
@@ -214,7 +330,6 @@ void MacroTreeModel::Reset(std::deque<std::shared_ptr<Macro>> &newItems)
_macros = newItems; _macros = newItems;
endResetModel(); endResetModel();
UpdateGroupState(false);
_mt->ResetWidgets(); _mt->ResetWidgets();
} }
@@ -459,9 +574,6 @@ void MacroTreeModel::Remove(std::shared_ptr<Macro> item)
_mt->selectionModel()->clear(); _mt->selectionModel()->clear();
if (isGroup) {
UpdateGroupState(true);
}
assert(IsInValidState()); assert(IsInValidState());
} }
@@ -550,7 +662,6 @@ MacroTreeModel::MacroTreeModel(MacroTree *st_,
_mt(st_), _mt(st_),
_macros(macros) _macros(macros)
{ {
UpdateGroupState(false);
} }
int MacroTreeModel::rowCount(const QModelIndex &parent) const int MacroTreeModel::rowCount(const QModelIndex &parent) const
@@ -704,7 +815,6 @@ void MacroTreeModel::GroupSelectedItems(QModelIndexList &indices)
offset++; offset++;
} }
_hasGroups = true;
_mt->selectionModel()->clear(); _mt->selectionModel()->clear();
Reset(_macros); Reset(_macros);
@@ -762,24 +872,6 @@ void MacroTreeModel::CollapseGroup(std::shared_ptr<Macro> item)
assert(IsInValidState()); assert(IsInValidState());
} }
void MacroTreeModel::UpdateGroupState(bool update)
{
bool nowHasGroups = false;
for (auto &item : _macros) {
if (item->IsGroup()) {
nowHasGroups = true;
break;
}
}
if (nowHasGroups != _hasGroups) {
_hasGroups = nowHasGroups;
if (update) {
_mt->UpdateWidgets(true);
}
}
}
void MacroTree::Reset(std::deque<std::shared_ptr<Macro>> &macros, void MacroTree::Reset(std::deque<std::shared_ptr<Macro>> &macros,
bool highlight) bool highlight)
{ {
@@ -842,7 +934,6 @@ MacroTree::MacroTree(QWidget *parent_) : QListView(parent_)
void MacroTree::ResetWidgets() void MacroTree::ResetWidgets()
{ {
MacroTreeModel *mtm = GetModel(); MacroTreeModel *mtm = GetModel();
mtm->UpdateGroupState(false);
int modelIdx = 0; int modelIdx = 0;
for (int i = 0; i < (int)mtm->_macros.size(); i++) { for (int i = 0; i < (int)mtm->_macros.size(); i++) {
QModelIndex index = mtm->createIndex(modelIdx, 0, nullptr); QModelIndex index = mtm->createIndex(modelIdx, 0, nullptr);

View File

@@ -34,10 +34,14 @@ public:
explicit MacroTreeItem(MacroTree *tree, std::shared_ptr<Macro> macro, explicit MacroTreeItem(MacroTree *tree, std::shared_ptr<Macro> macro,
bool highlight); bool highlight);
protected:
bool event(QEvent *) override;
private slots: private slots:
void ExpandClicked(bool checked); void ExpandClicked(bool checked);
void EnableHighlight(bool enable); void EnableHighlight(bool enable);
void UpdatePaused(); void RunningClicked(bool);
void UpdateRunning();
void HighlightIfExecuted(); void HighlightIfExecuted();
void MacroRenamed(const QString &, const QString &); void MacroRenamed(const QString &, const QString &);
@@ -106,7 +110,6 @@ private:
void UngroupSelectedGroups(QModelIndexList &indices); void UngroupSelectedGroups(QModelIndexList &indices);
void ExpandGroup(std::shared_ptr<Macro> item); void ExpandGroup(std::shared_ptr<Macro> item);
void CollapseGroup(std::shared_ptr<Macro> item); void CollapseGroup(std::shared_ptr<Macro> item);
void UpdateGroupState(bool update);
int GetItemMacroIndex(const std::shared_ptr<Macro> &item) const; int GetItemMacroIndex(const std::shared_ptr<Macro> &item) const;
int GetItemModelIndex(const std::shared_ptr<Macro> &item) const; int GetItemModelIndex(const std::shared_ptr<Macro> &item) const;
bool IsLastItem(std::shared_ptr<Macro> item) const; bool IsLastItem(std::shared_ptr<Macro> item) const;
@@ -114,7 +117,6 @@ private:
MacroTree *_mt; MacroTree *_mt;
std::deque<std::shared_ptr<Macro>> &_macros; std::deque<std::shared_ptr<Macro>> &_macros;
bool _hasGroups = false;
friend class MacroTree; friend class MacroTree;
friend class MacroTreeItem; friend class MacroTreeItem;

View File

@@ -242,8 +242,12 @@ bool Macro::CheckConditions(bool ignorePause)
vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched); vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched);
_conditionSateChanged = _lastMatched != _matched; _conditionSateChanged = _lastMatched != _matched;
if (!_conditionSateChanged && _performActionsOnChange) { const bool hasActionsToExecute = _matched ? (_actions.size() > 0)
_onPreventedActionExecution = true; : (_elseActions.size() > 0);
if (!_conditionSateChanged && _performActionsOnChange &&
hasActionsToExecute) {
_lastOnChangeActionsPreventedTime =
std::chrono::high_resolution_clock::now();
} }
_lastMatched = _matched; _lastMatched = _matched;
@@ -302,6 +306,16 @@ bool Macro::WasExecutedSince(const TimePoint &time) const
return _lastExecutionTime > time; return _lastExecutionTime > time;
} }
bool Macro::OnChangePreventedActionsSince(const TimePoint &time) const
{
return _lastOnChangeActionsPreventedTime > time;
}
Macro::TimePoint Macro::GetLastExecutionTime() const
{
return _lastExecutionTime;
}
bool Macro::ConditionsShouldBeChecked() const bool Macro::ConditionsShouldBeChecked() const
{ {
if (!_useCustomConditionCheckInterval) { if (!_useCustomConditionCheckInterval) {
@@ -948,12 +962,12 @@ bool Macro::PostLoad()
bool Macro::SwitchesScene() const bool Macro::SwitchesScene() const
{ {
for (const auto &a : _actions) { for (const auto &a : _actions) {
if (a->GetId() == GetSceneSwitchActionId()) { if (a->GetId() == MacroAction::GetDefaultID()) {
return true; return true;
} }
} }
for (const auto &a : _elseActions) { for (const auto &a : _elseActions) {
if (a->GetId() == GetSceneSwitchActionId()) { if (a->GetId() == MacroAction::GetDefaultID()) {
return true; return true;
} }
} }
@@ -986,18 +1000,8 @@ bool Macro::HasValidSplitterPositions() const
!_elseActionSplitterPosition.empty(); !_elseActionSplitterPosition.empty();
} }
bool Macro::OnChangePreventedActionsRecently()
{
if (_onPreventedActionExecution) {
_onPreventedActionExecution = false;
return _matched ? _actions.size() > 0 : _elseActions.size() > 0;
}
return false;
}
void Macro::ResetUIHelpers() void Macro::ResetUIHelpers()
{ {
_onPreventedActionExecution = false;
for (auto c : _conditions) { for (auto c : _conditions) {
c->GetHighlightAndReset(); c->GetHighlightAndReset();
} }

View File

@@ -137,7 +137,8 @@ public:
const QList<int> &GetElseActionSplitterPosition() const; const QList<int> &GetElseActionSplitterPosition() const;
bool HasValidSplitterPositions() const; bool HasValidSplitterPositions() const;
bool WasExecutedSince(const TimePoint &) const; bool WasExecutedSince(const TimePoint &) const;
bool OnChangePreventedActionsRecently(); bool OnChangePreventedActionsSince(const TimePoint &) const;
TimePoint GetLastExecutionTime() const;
void ResetUIHelpers(); void ResetUIHelpers();
// Hotkeys // Hotkeys
@@ -168,6 +169,7 @@ private:
TimePoint _lastCheckTime{}; TimePoint _lastCheckTime{};
TimePoint _lastUnpauseTime{}; TimePoint _lastUnpauseTime{};
TimePoint _lastExecutionTime{}; TimePoint _lastExecutionTime{};
TimePoint _lastOnChangeActionsPreventedTime{};
std::vector<std::thread> _helperThreads; std::vector<std::thread> _helperThreads;
std::deque<std::shared_ptr<MacroCondition>> _conditions; std::deque<std::shared_ptr<MacroCondition>> _conditions;
@@ -204,9 +206,6 @@ private:
MacroInputVariables _inputVariables; MacroInputVariables _inputVariables;
// UI helpers
bool _onPreventedActionExecution = false;
QList<int> _actionConditionSplitterPosition; QList<int> _actionConditionSplitterPosition;
QList<int> _elseActionSplitterPosition; QList<int> _elseActionSplitterPosition;

View File

@@ -258,7 +258,7 @@ bool IsFullscreen(const std::string &title)
return false; return false;
} }
std::optional<std::string> GetTextInWindow(const std::string &window) std::optional<std::string> GetTextInWindow(const std::string &)
{ {
// Not implemented // Not implemented
return {}; return {};

View File

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

135
lib/utils/crash-handler.cpp Normal file
View File

@@ -0,0 +1,135 @@
#include "crash-handler.hpp"
#include "log-helper.hpp"
#include "obs-module-helper.hpp"
#include "plugin-state-helpers.hpp"
#include "ui-helpers.hpp"
#include <obs-frontend-api.h>
#include <obs-module.h>
#include <QDir>
#include <QFile>
#include <thread>
namespace advss {
static constexpr std::string_view sentinel = ".running";
#ifndef NDEBUG
static constexpr bool handleUncleanShutdown = false;
#else
static constexpr bool handleUncleanShutdown = true;
#endif
static bool wasCleanShutdown = false;
static void setup();
static bool setupDone = []() {
AddPluginInitStep(setup);
return true;
}();
static void handleShutdown(enum obs_frontend_event event, void *)
{
if (event != OBS_FRONTEND_EVENT_EXIT) {
return;
}
char *sentinelFile = obs_module_config_path(sentinel.data());
if (!sentinelFile) {
return;
}
QFile file(sentinelFile);
if (!file.exists()) {
bfree(sentinelFile);
return;
}
if (!file.remove()) {
blog(LOG_WARNING, "failed to remove sentinel file");
}
bfree(sentinelFile);
}
static void setup()
{
char *sentinelFile = obs_module_config_path(sentinel.data());
if (!sentinelFile) {
return;
}
QString dirPath = QFileInfo(sentinelFile).absolutePath();
QDir dir(dirPath);
if (!dir.exists()) {
if (!dir.mkpath(dirPath)) {
blog(LOG_WARNING,
"failed to create directory for sentinel file");
bfree(sentinelFile);
return;
}
}
QFile file(sentinelFile);
wasCleanShutdown = file.exists();
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
blog(LOG_WARNING, "failed to create sentinel file");
bfree(sentinelFile);
return;
}
file.write("running");
file.close();
bfree(sentinelFile);
obs_frontend_add_event_callback(handleShutdown, nullptr);
return;
}
static bool wasUncleanShutdown()
{
static bool alreadyHandled = false;
if (!handleUncleanShutdown || !wasCleanShutdown || alreadyHandled) {
alreadyHandled = true;
return false;
}
alreadyHandled = true;
blog(LOG_WARNING, "unclean shutdown detected");
return true;
}
static bool askForStartupSkip()
{
return DisplayMessage(obs_module_text("AdvSceneSwitcher.crashDetected"),
true, false);
}
bool ShouldSkipPluginStartOnUncleanShutdown()
{
if (!wasUncleanShutdown()) {
return false;
}
std::thread t([]() {
obs_queue_task(
OBS_TASK_UI,
[](void *) {
const bool skipStart = askForStartupSkip();
if (!skipStart) {
StartPlugin();
}
},
nullptr, false);
});
t.detach();
return true;
}
} // namespace advss

View File

@@ -0,0 +1,7 @@
#pragma once
namespace advss {
bool ShouldSkipPluginStartOnUncleanShutdown();
} // namespace advss

View File

@@ -56,7 +56,7 @@ std::optional<double> GetDouble(const std::string &str)
std::optional<int> GetInt(const std::string &str) std::optional<int> GetInt(const std::string &str)
{ {
char *end = nullptr; char *end = nullptr;
int value = std::strtol(str.c_str(), &end, 10); int value = (int)std::strtol(str.c_str(), &end, 10);
if (end != str.c_str() && *end == '\0' && value != INT_MAX && if (end != str.c_str() && *end == '\0' && value != INT_MAX &&
value != INT_MIN) { value != INT_MIN) {

View File

@@ -65,13 +65,16 @@ ResourceTable::ResourceTable(QWidget *parent, const QString &help,
[openSettings]() { openSettings(); }); [openSettings]() { openSettings(); });
auto settingsShortcut = new QShortcut(QKeySequence("F2"), this); auto settingsShortcut = new QShortcut(QKeySequence("F2"), this);
settingsShortcut->setContext(Qt::WidgetWithChildrenShortcut);
QWidget::connect(settingsShortcut, &QShortcut::activated, this, QWidget::connect(settingsShortcut, &QShortcut::activated, this,
openSettings); openSettings);
auto removeShortcut = new QShortcut(QKeySequence("Del"), this); auto removeShortcut = new QShortcut(QKeySequence("Del"), this);
removeShortcut->setContext(Qt::WidgetWithChildrenShortcut);
QWidget::connect(removeShortcut, &QShortcut::activated, this, QWidget::connect(removeShortcut, &QShortcut::activated, this,
[this]() { Remove(); }); [this]() { Remove(); });
auto newShortcut = new QShortcut(QKeySequence("Ctrl+N"), this); auto newShortcut = new QShortcut(QKeySequence("Ctrl+N"), this);
newShortcut->setContext(Qt::WidgetWithChildrenShortcut);
QWidget::connect(newShortcut, &QShortcut::activated, this, QWidget::connect(newShortcut, &QShortcut::activated, this,
[this]() { Add(); }); [this]() { Add(); });
} }

View File

@@ -1,4 +1,5 @@
#include "source-helpers.hpp" #include "source-helpers.hpp"
#include "canvas-helpers.hpp"
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
@@ -28,15 +29,43 @@ std::string GetWeakSourceName(obs_weak_source_t *weak_source)
OBSWeakSource GetWeakSourceByName(const char *name) OBSWeakSource GetWeakSourceByName(const char *name)
{ {
OBSWeakSource weak; OBSSourceAutoRelease source = obs_get_source_by_name(name);
obs_source_t *source = obs_get_source_by_name(name);
if (source) { if (source) {
weak = obs_source_get_weak_source(source); return OBSGetWeakRef(source);
obs_weak_source_release(weak);
obs_source_release(source);
} }
return weak; #if LIBOBS_API_VER > MAKE_SEMANTIC_VERSION(31, 1, 0)
struct SearchData {
const char *name;
OBSWeakSource &scene;
};
static const auto enumCanvasScenes = [](void *dataPtr,
obs_source_t *source) -> bool {
auto data = static_cast<SearchData *>(dataPtr);
if (strcmp(data->name, obs_source_get_name(source)) == 0) {
data->scene = OBSGetWeakRef(source);
return false;
}
return true;
};
static const auto enumCanvases = [](void *dataPtr,
obs_canvas_t *canvas) -> bool {
obs_canvas_enum_scenes(canvas, enumCanvasScenes, dataPtr);
auto data = static_cast<SearchData *>(dataPtr);
return data->scene == nullptr;
};
OBSWeakSource weakScene;
SearchData searchData{name, weakScene};
obs_enum_canvases(enumCanvases, &searchData);
return weakScene;
#else
return nullptr;
#endif
} }
OBSWeakSource GetWeakSourceByQString(const QString &name) OBSWeakSource GetWeakSourceByQString(const QString &name)

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
#include "tab-helpers.hpp" #include "tab-helpers.hpp"
#include "log-helper.hpp" #include "log-helper.hpp"
#include "obs-module-helper.hpp" #include "obs-module-helper.hpp"
#include "plugin-state-helpers.hpp"
#include <obs.hpp> #include <obs.hpp>
#include <QTabBar> #include <QTabBar>
@@ -33,6 +34,21 @@ struct TabCallbacks {
static std::unordered_map<const char *, TabCallbacks> createTabCallbacks; static std::unordered_map<const char *, TabCallbacks> createTabCallbacks;
static int lastOpenedTab = -1; static int lastOpenedTab = -1;
static bool alwaysShowTabs = false;
[[maybe_unused]] static bool _ = []() {
AddPluginInitStep([]() {
AddSaveStep([](obs_data_t *data) {
obs_data_set_bool(data, "alwaysShowTabs",
alwaysShowTabs);
});
AddLoadStep([](obs_data_t *data) {
alwaysShowTabs =
obs_data_get_bool(data, "alwaysShowTabs");
});
});
return true;
}();
void SetTabVisibleByName(QTabWidget *tabWidget, bool visible, void SetTabVisibleByName(QTabWidget *tabWidget, bool visible,
const QString &name) const QString &name)
@@ -197,7 +213,41 @@ void SetupOtherTabs(QTabWidget *tabWidget)
.c_str()); .c_str());
tabWidget->insertTab(0, widget, tabText); tabWidget->insertTab(0, widget, tabText);
callbacks.setupTab(tabWidget); callbacks.setupTab(tabWidget);
if (alwaysShowTabs) {
tabWidget->setTabVisible(0, true);
}
} }
} }
void SetupShowAllTabsCheckBox(QCheckBox *checkBox, QTabWidget *tabWidget)
{
const auto stateChanged = [tabWidget](int state) {
const bool newState = (state != 0);
if (newState == alwaysShowTabs) {
return;
}
alwaysShowTabs = newState;
// Only show currently hidden tabs, but don't hide tabs which
// are already visible
if (!alwaysShowTabs) {
return;
}
for (const auto &[name, _] : createTabCallbacks) {
const auto localeKey =
std::string("AdvSceneSwitcher.") + name +
".title";
auto tabText = obs_module_text(localeKey.c_str());
SetTabVisibleByName(tabWidget, true, tabText);
}
};
checkBox->setChecked(alwaysShowTabs);
QWidget::connect(checkBox, &QCheckBox::stateChanged, checkBox,
stateChanged);
}
} // namespace advss } // namespace advss

View File

@@ -2,6 +2,8 @@
#include "export-symbol-helper.hpp" #include "export-symbol-helper.hpp"
#include <obs-data.h> #include <obs-data.h>
#include <QCheckBox>
#include <QTabWidget> #include <QTabWidget>
namespace advss { namespace advss {
@@ -19,5 +21,6 @@ void SetupOtherTabs(QTabWidget *tabWidget);
void SaveTabOrder(obs_data_t *obj); void SaveTabOrder(obs_data_t *obj);
void LoadTabOrder(obs_data_t *obj); void LoadTabOrder(obs_data_t *obj);
bool MacroTabIsInFocus(); bool MacroTabIsInFocus();
void SetupShowAllTabsCheckBox(QCheckBox *checkBox, QTabWidget *tabWidget);
} // namespace advss } // namespace advss

View File

@@ -467,9 +467,9 @@ static void setupTab(QTabWidget *tab)
setTabVisible(tab, false); setTabVisible(tab, false);
} }
QWidget::connect( QWidget::connect(VariableSignalManager::Instance(),
VariableSignalManager::Instance(), &VariableSignalManager::Add, &VariableSignalManager::Add, tab,
tab, [tab](const QString &name) { setTabVisible(tab, true); }); [tab](const QString &) { setTabVisible(tab, true); });
} }
void VariableTable::Settings::Save(obs_data_t *data, const char *name) void VariableTable::Settings::Save(obs_data_t *data, const char *name)

View File

@@ -261,6 +261,14 @@ std::optional<std::string> GetTextInWindow(const std::string &window)
return {}; return {};
} }
DWORD pid = 0;
DWORD thid = 0;
thid = GetWindowThreadProcessId(hwnd, &pid);
// Calling CoCreateInstance() on the OBS windows might cause a deadlock
if (GetCurrentProcessId() == pid) {
return {};
}
IUIAutomation *automation = nullptr; IUIAutomation *automation = nullptr;
auto hr = CoCreateInstance(__uuidof(CUIAutomation), nullptr, auto hr = CoCreateInstance(__uuidof(CUIAutomation), nullptr,
CLSCTX_INPROC_SERVER, CLSCTX_INPROC_SERVER,

View File

@@ -166,6 +166,8 @@ target_sources(
utils/scene-item-selection.hpp utils/scene-item-selection.hpp
utils/scene-item-transform-helpers.cpp utils/scene-item-transform-helpers.cpp
utils/scene-item-transform-helpers.hpp utils/scene-item-transform-helpers.hpp
utils/transition-helpers.cpp
utils/transition-helpers.hpp
utils/source-properties-button.cpp utils/source-properties-button.cpp
utils/source-properties-button.hpp utils/source-properties-button.hpp
utils/source-settings-helpers.cpp utils/source-settings-helpers.cpp

View File

@@ -4,6 +4,7 @@
#include "plugin-state-helpers.hpp" #include "plugin-state-helpers.hpp"
#include "scene-switch-helpers.hpp" #include "scene-switch-helpers.hpp"
#include "source-helpers.hpp" #include "source-helpers.hpp"
#include "transition-helpers.hpp"
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
@@ -68,14 +69,6 @@ static int getTransitionOverrideDuration(OBSWeakSource &scene)
return duration; 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) static OBSWeakSource getOverrideTransition(OBSWeakSource &scene)
{ {
OBSWeakSource transition; OBSWeakSource transition;
@@ -101,13 +94,13 @@ static int getExpectedTransitionDuration(OBSWeakSource &scene,
auto overrideTransition = getOverrideTransition(scene); auto overrideTransition = getOverrideTransition(scene);
if (overrideTransition) { if (overrideTransition) {
transition = overrideTransition; transition = overrideTransition;
if (!isUsingFixedLengthTransition(transition)) { if (!IsFixedLengthTransition(transition)) {
return getTransitionOverrideDuration(scene); return getTransitionOverrideDuration(scene);
} }
} }
} }
if (isUsingFixedLengthTransition(transition)) { if (IsFixedLengthTransition(transition)) {
return -1; // no API is available to access the fixed duration return -1; // no API is available to access the fixed duration
} }
if (duration != 0) { if (duration != 0) {
@@ -453,7 +446,7 @@ void MacroActionSwitchSceneEdit::SceneSelectionCanvasChanged(
SetWidgetVisibility(); SetWidgetVisibility();
} }
void MacroActionSwitchSceneEdit::ActionChanged(int value) void MacroActionSwitchSceneEdit::ActionChanged(int)
{ {
GUARD_LOADING_AND_LOCK(); GUARD_LOADING_AND_LOCK();
_entryData->_action = static_cast<MacroActionSwitchScene::Action>( _entryData->_action = static_cast<MacroActionSwitchScene::Action>(
@@ -503,7 +496,7 @@ shouldShowDurationInTransitionLayout(MacroActionSwitchScene::SceneType type,
return true; return true;
} }
if (isUsingFixedLengthTransition(transition.GetTransition())) { if (IsFixedLengthTransition(transition.GetTransition())) {
return false; return false;
} }

View File

@@ -1,5 +1,6 @@
#include "macro-action-scene-visibility.hpp" #include "macro-action-scene-visibility.hpp"
#include "layout-helpers.hpp" #include "layout-helpers.hpp"
#include "transition-helpers.hpp"
namespace advss { namespace advss {
@@ -21,9 +22,74 @@ const static std::map<MacroActionSceneVisibility::Action, std::string>
"AdvSceneSwitcher.action.sceneVisibility.type.toggle"}, "AdvSceneSwitcher.action.sceneVisibility.type.toggle"},
}; };
namespace {
struct TransitionRestoreContext {
obs_sceneitem_t *item;
bool wasVisible;
bool restoreTransition;
OBSSource originalTransition;
bool restoreDuration;
uint32_t originalDuration;
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 setSceneItemVisibility(obs_sceneitem_t *item, static void setSceneItemVisibility(obs_sceneitem_t *item,
const bool setTransition,
const OBSWeakSource &transitionWeak,
const bool setDuration,
const Duration &duration,
MacroActionSceneVisibility::Action action) 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);
obs_source_t *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) { switch (action) {
case MacroActionSceneVisibility::Action::SHOW: case MacroActionSceneVisibility::Action::SHOW:
obs_sceneitem_set_visible(item, true); obs_sceneitem_set_visible(item, true);
@@ -32,16 +98,48 @@ static void setSceneItemVisibility(obs_sceneitem_t *item,
obs_sceneitem_set_visible(item, false); obs_sceneitem_set_visible(item, false);
break; break;
case MacroActionSceneVisibility::Action::TOGGLE: case MacroActionSceneVisibility::Action::TOGGLE:
obs_sceneitem_set_visible(item, !obs_sceneitem_visible(item)); obs_sceneitem_set_visible(item, !itemIsVisible);
break; break;
} }
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;
}
auto ctx = new TransitionRestoreContext{item,
itemIsVisible,
setTransition,
currentTransition,
setDuration,
currentTransitionDuration,
sh};
signal_handler_connect(
sh, "transition_stop",
&TransitionRestoreContext::ResetSceneItemTransition, ctx);
} }
bool MacroActionSceneVisibility::PerformAction() bool MacroActionSceneVisibility::PerformAction()
{ {
auto items = _source.GetSceneItems(_scene); auto items = _source.GetSceneItems(_scene);
for (const auto &item : items) { for (const auto &item : items) {
setSceneItemVisibility(item, _action); setSceneItemVisibility(item, _updateTransition,
_transition.GetTransition(),
_updateDuration, _duration, _action);
} }
return true; return true;
} }
@@ -66,6 +164,10 @@ bool MacroActionSceneVisibility::Save(obs_data_t *obj) const
MacroAction::Save(obj); MacroAction::Save(obj);
_scene.Save(obj); _scene.Save(obj);
_source.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)); obs_data_set_int(obj, "action", static_cast<int>(_action));
return true; return true;
} }
@@ -82,6 +184,10 @@ bool MacroActionSceneVisibility::Load(obs_data_t *obj)
MacroAction::Load(obj); MacroAction::Load(obj);
_scene.Load(obj); _scene.Load(obj);
_source.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>( _action = static_cast<MacroActionSceneVisibility::Action>(
obs_data_get_int(obj, "action")); obs_data_get_int(obj, "action"));
@@ -141,10 +247,17 @@ MacroActionSceneVisibilityEdit::MacroActionSceneVisibilityEdit(
SceneItemSelection::Type::ALL, SceneItemSelection::Type::ALL,
}, },
SceneItemSelectionWidget::NameClashMode::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()) _actions(new QComboBox())
{ {
populateActionSelection(_actions); populateActionSelection(_actions);
_duration->SpinBox()->setSpecialValueText("-");
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int))); SLOT(ActionChanged(int)));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)), QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
@@ -154,18 +267,49 @@ MacroActionSceneVisibilityEdit::MacroActionSceneVisibilityEdit(
QWidget::connect(_sources, QWidget::connect(_sources,
SIGNAL(SceneItemChanged(const SceneItemSelection &)), SIGNAL(SceneItemChanged(const SceneItemSelection &)),
this, SLOT(SourceChanged(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( PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.sceneVisibility.entry"), "AdvSceneSwitcher.action.sceneVisibility.layout"),
layout, sceneItemLayout,
{{"{{scenes}}", _scenes}, {{"{{scenes}}", _scenes},
{"{{sources}}", _sources}, {"{{sources}}", _sources},
{"{{actions}}", _actions}}); {"{{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); setLayout(layout);
_entryData = entryData; _entryData = entryData;
UpdateEntryData(); UpdateEntryData();
SetWidgetVisibility();
_loading = false; _loading = false;
} }
@@ -177,7 +321,11 @@ void MacroActionSceneVisibilityEdit::UpdateEntryData()
_actions->setCurrentIndex(static_cast<int>(_entryData->_action)); _actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_scenes->SetScene(_entryData->_scene); _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) void MacroActionSceneVisibilityEdit::SceneChanged(const SceneSelection &s)
@@ -197,6 +345,34 @@ void MacroActionSceneVisibilityEdit::SourceChanged(
updateGeometry(); 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) void MacroActionSceneVisibilityEdit::ActionChanged(int value)
{ {
GUARD_LOADING_AND_LOCK(); GUARD_LOADING_AND_LOCK();
@@ -204,4 +380,20 @@ void MacroActionSceneVisibilityEdit::ActionChanged(int value)
static_cast<MacroActionSceneVisibility::Action>(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 } // namespace advss

View File

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

View File

@@ -194,6 +194,7 @@ MacroActionSequenceEdit::MacroActionSequenceEdit(
_resetIndex(new VariableSpinBox()), _resetIndex(new VariableSpinBox()),
_layout(new QHBoxLayout()) _layout(new QHBoxLayout())
{ {
_macros->HideGroups();
populateActionSelection(_actions); populateActionSelection(_actions);
_resetIndex->setMinimum(1); _resetIndex->setMinimum(1);

View File

@@ -31,15 +31,16 @@ bool MacroActionTimer::PerformAction()
if (!macro) { if (!macro) {
return true; return true;
} }
auto conditions = *GetMacroConditions(macro.get());
for (auto condition : conditions) { const auto updateCondition = [this](MacroCondition *condition) {
if (condition->GetId() != id) { if (condition->GetId() != id) {
continue; return;
} }
auto timerCondition = auto timerCondition =
dynamic_cast<MacroConditionTimer *>(condition.get()); dynamic_cast<MacroConditionTimer *>(condition);
if (!timerCondition) { if (!timerCondition) {
continue; return;
} }
switch (_actionType) { switch (_actionType) {
@@ -59,6 +60,23 @@ bool MacroActionTimer::PerformAction()
default: default:
break; break;
} }
};
const auto updateMacro = [&](Macro *macro) {
auto conditions = *GetMacroConditions(macro);
for (auto condition : conditions) {
updateCondition(condition.get());
}
};
if (!IsGroupMacro(macro.get())) {
updateMacro(macro.get());
return true;
}
auto macros = GetGroupMacroEntries(macro.get());
for (const auto &macro : macros) {
updateMacro(macro.get());
} }
return true; return true;
} }
@@ -131,7 +149,7 @@ void MacroActionTimer::ResolveVariablesToFixedValues()
_duration.ResolveVariables(); _duration.ResolveVariables();
} }
static inline void populateTypeSelection(QComboBox *list) static void populateTypeSelection(QComboBox *list)
{ {
for (const auto &[_, name] : timerActions) { for (const auto &[_, name] : timerActions) {
list->addItem(obs_module_text(name.c_str())); list->addItem(obs_module_text(name.c_str()));
@@ -140,12 +158,11 @@ static inline void populateTypeSelection(QComboBox *list)
MacroActionTimerEdit::MacroActionTimerEdit( MacroActionTimerEdit::MacroActionTimerEdit(
QWidget *parent, std::shared_ptr<MacroActionTimer> entryData) QWidget *parent, std::shared_ptr<MacroActionTimer> entryData)
: QWidget(parent) : QWidget(parent),
_macros(new MacroSelection(parent)),
_duration(new DurationSelection(this)),
_timerAction(new QComboBox(this))
{ {
_macros = new MacroSelection(parent);
_duration = new DurationSelection();
_timerAction = new QComboBox();
populateTypeSelection(_timerAction); populateTypeSelection(_timerAction);
QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)), QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)),
@@ -155,15 +172,13 @@ MacroActionTimerEdit::MacroActionTimerEdit(
QWidget::connect(_timerAction, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_timerAction, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionTypeChanged(int))); SLOT(ActionTypeChanged(int)));
_mainLayout = new QHBoxLayout; auto layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{macros}}", _macros},
{"{{duration}}", _duration},
{"{{timerAction}}", _timerAction},
};
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.timer.entry"), PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.timer.entry"),
_mainLayout, widgetPlaceholders); layout,
setLayout(_mainLayout); {{"{{macros}}", _macros},
{"{{duration}}", _duration},
{"{{timerAction}}", _timerAction}});
setLayout(layout);
_entryData = entryData; _entryData = entryData;
UpdateEntryData(); UpdateEntryData();

View File

@@ -58,16 +58,14 @@ private slots:
signals: signals:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
protected: private:
void SetWidgetVisibility(); void SetWidgetVisibility();
MacroSelection *_macros; MacroSelection *_macros;
DurationSelection *_duration; DurationSelection *_duration;
QComboBox *_timerAction; QComboBox *_timerAction;
std::shared_ptr<MacroActionTimer> _entryData;
private: std::shared_ptr<MacroActionTimer> _entryData;
QHBoxLayout *_mainLayout;
bool _loading = true; bool _loading = true;
}; };

View File

@@ -1,5 +1,6 @@
#include "macro-action-transition.hpp" #include "macro-action-transition.hpp"
#include "layout-helpers.hpp" #include "layout-helpers.hpp"
#include "transition-helpers.hpp"
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
@@ -75,26 +76,6 @@ static void obs_sceneitem_set_transition_duration(obs_sceneitem_t *item,
} }
#endif #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) void MacroActionTransition::SetSourceTransition(bool show)
{ {
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0) #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); const auto items = _source.GetSceneItems(_scene);
for (const auto &item : items) { for (const auto &item : items) {
if (_setTransitionType) { if (_setTransitionType) {
setSceneItemTransition(item, transition, show); SetSceneItemTransition(item, transition, show);
} }
if (_setDuration) { if (_setDuration) {
obs_sceneitem_set_transition_duration( obs_sceneitem_set_transition_duration(

View File

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

View File

@@ -154,7 +154,7 @@ void MacroConditionProcessEdit::ProcessChanged(const QString &text)
QString::fromStdString(_entryData->GetShortDesc())); QString::fromStdString(_entryData->GetShortDesc()));
} }
void MacroConditionProcessEdit::showEvent(QShowEvent *event) void MacroConditionProcessEdit::showEvent(QShowEvent *)
{ {
const QSignalBlocker b(_processSelection); const QSignalBlocker b(_processSelection);
const auto text = _processSelection->currentText(); const auto text = _processSelection->currentText();

View File

@@ -31,6 +31,10 @@ static int currentRecordingDurationInSeconds = 0;
bool MacroConditionRecord::CheckCondition() bool MacroConditionRecord::CheckCondition()
{ {
char *lastSavePath = obs_frontend_get_last_recording();
SetTempVarValue("lastSavePath", lastSavePath ? lastSavePath : "");
bfree(lastSavePath);
switch (_condition) { switch (_condition) {
case Condition::STOP: case Condition::STOP:
return !obs_frontend_recording_active(); return !obs_frontend_recording_active();
@@ -74,6 +78,14 @@ void MacroConditionRecord::SetCondition(Condition condition)
void MacroConditionRecord::SetupTempVars() void MacroConditionRecord::SetupTempVars()
{ {
MacroCondition::SetupTempVars(); MacroCondition::SetupTempVars();
AddTempvar(
"lastSavePath",
obs_module_text(
"AdvSceneSwitcher.tempVar.recording.lastSavePath"),
obs_module_text(
"AdvSceneSwitcher.tempVar.recording.lastSavePath.description"));
switch (_condition) { switch (_condition) {
case Condition::DURATION: case Condition::DURATION:
AddTempvar( AddTempvar(

View File

@@ -29,6 +29,22 @@ static bool windowContainsText(const std::string &window,
return text == matchText; return text == matchText;
} }
void MacroConditionWindow::SetCheckText(bool value)
{
#ifdef _WIN32
_checkText = value;
#else
(void)value;
_checkText = false;
#endif
SetupTempVars();
}
bool MacroConditionWindow::GetCheckText()
{
return _checkText;
}
bool MacroConditionWindow::WindowMatchesRequirements( bool MacroConditionWindow::WindowMatchesRequirements(
const std::string &window) const const std::string &window) const
{ {
@@ -94,12 +110,18 @@ void MacroConditionWindow::SetVariableValueBasedOnMatch(
#ifdef _WIN32 #ifdef _WIN32
SetTempVarValue("windowClass", SetTempVarValue("windowClass",
GetWindowClassByWindowTitle(matchWindow)); GetWindowClassByWindowTitle(matchWindow));
if (_checkText) {
const auto text = GetTextInWindow(matchWindow);
if (text) {
SetTempVarValue("windowText", *text);
}
}
#endif #endif
if (!IsReferencedInVars()) { if (!IsReferencedInVars()) {
return; return;
} }
if (_checkText) { if (_checkText) {
auto text = GetTextInWindow(matchWindow); const auto text = GetTextInWindow(matchWindow);
SetVariableValue(text.value_or("")); SetVariableValue(text.value_or(""));
} else { } else {
SetVariableValue(ForegroundWindowTitle()); SetVariableValue(ForegroundWindowTitle());
@@ -186,6 +208,16 @@ void MacroConditionWindow::SetupTempVars()
obs_module_text("AdvSceneSwitcher.tempVar.window.windowClass"), obs_module_text("AdvSceneSwitcher.tempVar.window.windowClass"),
obs_module_text( obs_module_text(
"AdvSceneSwitcher.tempVar.window.windowClass.description")); "AdvSceneSwitcher.tempVar.window.windowClass.description"));
if (!_checkText) {
return;
}
AddTempvar(
"windowText",
obs_module_text("AdvSceneSwitcher.tempVar.window.windowText"),
obs_module_text(
"AdvSceneSwitcher.tempVar.window.windowText.description"));
#endif #endif
} }
@@ -349,7 +381,7 @@ void MacroConditionWindowEdit::CheckTitleChanged(int state)
void MacroConditionWindowEdit::CheckTextChanged(int state) void MacroConditionWindowEdit::CheckTextChanged(int state)
{ {
GUARD_LOADING_AND_LOCK(); GUARD_LOADING_AND_LOCK();
_entryData->_checkText = state; _entryData->SetCheckText(state);
SetWidgetVisibility(); SetWidgetVisibility();
} }
@@ -409,8 +441,8 @@ void MacroConditionWindowEdit::SetWidgetVisibility()
_entryData->_focus || _entryData->_windowFocusChanged); _entryData->_focus || _entryData->_windowFocusChanged);
_windowSelection->setVisible(_entryData->_checkTitle); _windowSelection->setVisible(_entryData->_checkTitle);
_windowRegex->setVisible(_entryData->_checkTitle); _windowRegex->setVisible(_entryData->_checkTitle);
_textRegex->setVisible(_entryData->_checkText); _textRegex->setVisible(_entryData->GetCheckText());
_text->setVisible(_entryData->_checkText); _text->setVisible(_entryData->GetCheckText());
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();
} }
@@ -429,7 +461,7 @@ void MacroConditionWindowEdit::UpdateEntryData()
_maximized->setChecked(_entryData->_maximized); _maximized->setChecked(_entryData->_maximized);
_focused->setChecked(_entryData->_focus); _focused->setChecked(_entryData->_focus);
_windowFocusChanged->setChecked(_entryData->_windowFocusChanged); _windowFocusChanged->setChecked(_entryData->_windowFocusChanged);
_checkText->setChecked(_entryData->_checkText); _checkText->setChecked(_entryData->GetCheckText());
_text->setPlainText(_entryData->_text); _text->setPlainText(_entryData->_text);
_textRegex->SetRegexConfig(_entryData->_textRegex); _textRegex->SetRegexConfig(_entryData->_textRegex);
SetWidgetVisibility(); SetWidgetVisibility();

View File

@@ -22,6 +22,9 @@ public:
return std::make_shared<MacroConditionWindow>(m); return std::make_shared<MacroConditionWindow>(m);
} }
void SetCheckText(bool value);
bool GetCheckText();
StringVariable _window; StringVariable _window;
RegexConfig _windowRegex; RegexConfig _windowRegex;
bool _checkTitle = true; bool _checkTitle = true;
@@ -30,8 +33,6 @@ public:
bool _focus = true; bool _focus = true;
bool _windowFocusChanged = false; bool _windowFocusChanged = false;
// For now only supported on Windows
bool _checkText = false;
StringVariable _text; StringVariable _text;
RegexConfig _textRegex = RegexConfig::PartialMatchRegexConfig(); RegexConfig _textRegex = RegexConfig::PartialMatchRegexConfig();
@@ -42,6 +43,9 @@ private:
void SetVariableValueBasedOnMatch(const std::string &matchWindow); void SetVariableValueBasedOnMatch(const std::string &matchWindow);
void SetupTempVars(); void SetupTempVars();
// For now only supported on Windows
bool _checkText = false;
static bool _registered; static bool _registered;
static const std::string id; static const std::string id;
}; };

View File

@@ -10,7 +10,7 @@ namespace advss {
enum class HotkeyType; enum class HotkeyType;
bool CanSimulateKeyPresses(); bool CanSimulateKeyPresses();
void PressKeys(const std::vector<HotkeyType> keys, int duration); void PressKeys(const std::vector<HotkeyType> &keys, int duration);
class Hotkey { class Hotkey {
public: public:

View File

@@ -188,7 +188,7 @@ static const std::unordered_map<HotkeyType, long> keyTable = {
{HotkeyType::Key_NumpadEnter, XK_KP_Enter}, {HotkeyType::Key_NumpadEnter, XK_KP_Enter},
}; };
void PressKeys(const std::vector<HotkeyType> keys, int duration) void PressKeys(const std::vector<HotkeyType> &keys, int duration)
{ {
if (!canSimulateKeyPresses) { if (!canSimulateKeyPresses) {
return; return;

View File

@@ -9,7 +9,7 @@ bool CanSimulateKeyPresses()
return canSimulateKeyPresses; return canSimulateKeyPresses;
} }
void PressKeys(const std::vector<HotkeyType> keys, int duration) void PressKeys(const std::vector<HotkeyType> &, int)
{ {
// Not supported on MacOS // Not supported on MacOS
return; return;

View File

@@ -210,7 +210,7 @@ findListPropertyById(const obs_source_t *source, const std::string &id)
} }
auto property = obs_properties_first(properties); auto property = obs_properties_first(properties);
return {findListPropertyByIdHelper(property, id), return {findListPropertyByIdHelper(property, id),
properties_t(nullptr, obs_properties_destroy)}; properties_t(properties, obs_properties_destroy)};
} }
static std::pair<obs_property_t *, properties_t> static std::pair<obs_property_t *, properties_t>
@@ -335,6 +335,12 @@ void SetSourceSetting(obs_source_t *source, const SourceSetting &setting,
case OBS_DATA_OBJECT: { case OBS_DATA_OBJECT: {
OBSDataAutoRelease json = OBSDataAutoRelease json =
obs_data_create_from_json(value.c_str()); 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); obs_data_set_obj(data, id.c_str(), json);
break; 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

@@ -138,7 +138,7 @@ static const std::unordered_map<HotkeyType, long> keyTable = {
{HotkeyType::Key_NumpadEnter, VK_RETURN}, {HotkeyType::Key_NumpadEnter, VK_RETURN},
}; };
void PressKeys(const std::vector<HotkeyType> keys, int duration) void PressKeys(const std::vector<HotkeyType> &keys, int duration)
{ {
const int repeatInterval = 100; const int repeatInterval = 100;

View File

@@ -70,7 +70,7 @@ struct URLInfo {
std::string path; std::string path;
}; };
static URLInfo getURLInfo(const std::string &input) static URLInfo getURLInfo(const std::string &input, bool keepParams)
{ {
if (input.empty()) { if (input.empty()) {
return {}; return {};
@@ -84,21 +84,35 @@ static URLInfo getURLInfo(const std::string &input)
const QUrl url(urlInput); const QUrl url(urlInput);
auto host = auto host =
url.scheme().toStdString() + "://" + url.host().toStdString(); url.scheme().toStdString() + "://" + url.host().toStdString();
int port = url.port();
const int port = url.port();
if (port != -1) { if (port != -1) {
host += ":" + std::to_string(port); host += ":" + std::to_string(port);
} }
auto path = url.path().toStdString(); auto path = url.path().toStdString();
if (path.empty()) { if (path.empty()) {
path = "/"; 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}; return {host, path};
} }
bool MacroActionHttp::PerformAction() bool MacroActionHttp::PerformAction()
{ {
const auto [host, path] = getURLInfo(_url); const auto [host, path] = getURLInfo(_url, !_setParams);
httplib::Client cli(host); httplib::Client cli(host);
setTimeout(cli, _timeout); setTimeout(cli, _timeout);

View File

@@ -513,7 +513,7 @@ void ScriptHandler::SetVariableValue(void *, calldata_t *data)
RETURN_SUCCESS(); RETURN_SUCCESS();
} }
void ScriptHandler::GetRunningStatus(void *ctx, calldata_t *data) void ScriptHandler::GetRunningStatus(void *, calldata_t *data)
{ {
calldata_set_bool(data, "is_running", PluginIsRunning()); calldata_set_bool(data, "is_running", PluginIsRunning());
} }

View File

@@ -58,14 +58,16 @@ std::string TwitchChannel::GetUserID(const TwitchToken &token) const
} }
OBSDataArrayAutoRelease array = obs_data_get_array(res.data, "data"); OBSDataArrayAutoRelease array = obs_data_get_array(res.data, "data");
size_t count = obs_data_array_count(array); size_t count = obs_data_array_count(array);
for (size_t i = 0; i < count; i++) {
OBSDataAutoRelease arrayObj = obs_data_array_item(array, i); if (count == 0) {
std::string id = obs_data_get_string(arrayObj, "id"); userIDCache[_name] = "invalid";
userIDCache[_name] = id; return "invalid";
return id;
} }
userIDCache[_name] = "invalid";
return "invalid"; OBSDataAutoRelease arrayObj = obs_data_array_item(array, 0);
const std::string id = obs_data_get_string(arrayObj, "id");
userIDCache[_name] = id;
return id;
} }
static QDate parseRFC3339Date(const std::string &rfc3339Date) static QDate parseRFC3339Date(const std::string &rfc3339Date)

View File

@@ -72,7 +72,6 @@ void EventSub::UnregisterInstance()
void EventSub::ConnectThread() void EventSub::ConnectThread()
{ {
_client->reset(); _client->reset();
_connected = true;
websocketpp::lib::error_code ec; websocketpp::lib::error_code ec;
EventSubWSClient::connection_ptr con = EventSubWSClient::connection_ptr con =
_client->get_connection(_url, ec); _client->get_connection(_url, ec);
@@ -90,12 +89,24 @@ void EventSub::ConnectThread()
void EventSub::WaitAndReconnect() void EventSub::WaitAndReconnect()
{ {
if (_reconnecting) {
return;
}
_reconnecting = true;
auto thread = std::thread([this]() { auto thread = std::thread([this]() {
std::unique_lock<std::mutex> lock(_waitMtx); std::unique_lock<std::mutex> lock(_waitMtx);
blog(LOG_INFO, blog(LOG_INFO,
"Twitch EventSub trying to reconnect to in %d seconds.", "Twitch EventSub trying to reconnect to in %d seconds.",
reconnectDelay); reconnectDelay);
_cv.wait_for(lock, std::chrono::seconds(reconnectDelay)); _cv.wait_for(lock, std::chrono::seconds(reconnectDelay));
_reconnecting = false;
if (_disconnect) {
return;
}
Connect(); Connect();
}); });
thread.detach(); thread.detach();
@@ -103,6 +114,10 @@ void EventSub::WaitAndReconnect()
void EventSub::Connect() void EventSub::Connect()
{ {
if (_reconnecting) {
return;
}
std::lock_guard<std::mutex> lock(_connectMtx); std::lock_guard<std::mutex> lock(_connectMtx);
if (_connected) { if (_connected) {
vblog(LOG_INFO, "Twitch EventSub connect already in progress"); vblog(LOG_INFO, "Twitch EventSub connect already in progress");
@@ -113,6 +128,7 @@ void EventSub::Connect()
_thread.join(); _thread.join();
} }
_disconnect = false; _disconnect = false;
_connected = true;
_thread = std::thread(&EventSub::ConnectThread, this); _thread = std::thread(&EventSub::ConnectThread, this);
} }
@@ -448,7 +464,7 @@ void EventSub::StartServerMigrationClient(const std::string &url)
auto client = std::make_unique<EventSubWSClient>(); auto client = std::make_unique<EventSubWSClient>();
SetupClient(*client); SetupClient(*client);
client->set_open_handler([this](connection_hdl hdl) { client->set_open_handler([this](connection_hdl) {
vblog(LOG_INFO, "Twitch EventSub migration client opened"); vblog(LOG_INFO, "Twitch EventSub migration client opened");
}); });
@@ -465,8 +481,8 @@ void EventSub::StartServerMigrationClient(const std::string &url)
const auto &data = msg->payload; const auto &data = msg->payload;
if (type == "session_welcome") { if (type == "session_welcome") {
vblog(LOG_INFO, blog(LOG_INFO,
"Twitch EventSub migration successful - switching to new connection"); "Twitch EventSub migration successful - switching to new connection");
OBSDataAutoRelease session = OBSDataAutoRelease session =
obs_data_get_obj(data, "session"); obs_data_get_obj(data, "session");
_migrationSessionID = _migrationSessionID =
@@ -565,7 +581,7 @@ void EventSub::OnClose(connection_hdl hdl)
blog(LOG_INFO, "Twitch EventSub connection closed: %s / %s (%d)", blog(LOG_INFO, "Twitch EventSub connection closed: %s / %s (%d)",
msg.c_str(), reason.c_str(), code); msg.c_str(), reason.c_str(), code);
if (_migrating) { if (!_migrating) {
ClearActiveSubscriptions(); ClearActiveSubscriptions();
} }
_connected = false; _connected = false;

View File

@@ -110,6 +110,7 @@ private:
std::condition_variable _cv; std::condition_variable _cv;
std::atomic_bool _connected{false}; std::atomic_bool _connected{false};
std::atomic_bool _disconnect{false}; std::atomic_bool _disconnect{false};
std::atomic_bool _reconnecting{false};
std::string _url; std::string _url;
std::string _sessionID; std::string _sessionID;

View File

@@ -83,6 +83,8 @@ const static std::map<MacroActionTwitch::Action, std::string> actionTypes = {
"AdvSceneSwitcher.action.twitch.type.user.getInfo"}, "AdvSceneSwitcher.action.twitch.type.user.getInfo"},
{MacroActionTwitch::Action::POINTS_REWARD_GET_INFO, {MacroActionTwitch::Action::POINTS_REWARD_GET_INFO,
"AdvSceneSwitcher.action.twitch.type.reward.getInfo"}, "AdvSceneSwitcher.action.twitch.type.reward.getInfo"},
{MacroActionTwitch::Action::CHANNEL_GET_INFO,
"AdvSceneSwitcher.action.twitch.type.channel.getInfo"},
}; };
const static std::map<MacroActionTwitch::AnnouncementColor, std::string> const static std::map<MacroActionTwitch::AnnouncementColor, std::string>
@@ -322,6 +324,41 @@ void MacroActionTwitch::GetUserInfo(const std::shared_ptr<TwitchToken> &token)
}); });
} }
void MacroActionTwitch::GetChannelInfo(const std::shared_ptr<TwitchToken> &token)
{
auto info = _channel.GetInfo(*token);
if (!info) {
return;
}
SetTempVarValue("broadcaster_user_id", info->broadcaster_id);
SetTempVarValue("broadcaster_user_login", info->broadcaster_login);
SetTempVarValue("broadcaster_user_name", info->broadcaster_name);
SetTempVarValue("language", info->broadcaster_language);
SetTempVarValue("game_id", info->game_id);
SetTempVarValue("game_name", info->game_name);
SetTempVarValue("title", info->title);
SetTempVarValue("delay", std::to_string(info->delay));
std::string tags;
for (const auto &tag : info->tags) {
tags += tag + " ";
}
if (!tags.empty()) {
tags.pop_back();
}
SetTempVarValue("tags", tags);
std::string classificationLabels;
for (const auto &label : info->content_classification_labels) {
classificationLabels += label + " ";
}
if (!classificationLabels.empty()) {
classificationLabels.pop_back();
}
SetTempVarValue("content_classification_labels", classificationLabels);
SetTempVarValue("is_branded_content",
info->is_branded_content ? "true" : "false");
}
bool MacroActionTwitch::ResolveVariableSelectionToRewardId( bool MacroActionTwitch::ResolveVariableSelectionToRewardId(
const std::shared_ptr<TwitchToken> &token) const std::shared_ptr<TwitchToken> &token)
{ {
@@ -505,6 +542,16 @@ void MacroActionTwitch::SetupTempVars()
setupTempVarHelper("offline_image_url", ".user.getInfo"); setupTempVarHelper("offline_image_url", ".user.getInfo");
setupTempVarHelper("created_at", ".user.getInfo"); setupTempVarHelper("created_at", ".user.getInfo");
break; break;
case Action::CHANNEL_GET_INFO:
setupTempVarHelper("language");
setupTempVarHelper("game_id");
setupTempVarHelper("game_name");
setupTempVarHelper("title");
setupTempVarHelper("delay");
setupTempVarHelper("tags");
setupTempVarHelper("content_classification_labels");
setupTempVarHelper("is_branded_content");
break;
default: default:
break; break;
} }
@@ -563,6 +610,9 @@ bool MacroActionTwitch::PerformAction()
case MacroActionTwitch::Action::POINTS_REWARD_GET_INFO: case MacroActionTwitch::Action::POINTS_REWARD_GET_INFO:
GetRewardInfo(token); GetRewardInfo(token);
break; break;
case MacroActionTwitch::Action::CHANNEL_GET_INFO:
GetChannelInfo(token);
break;
default: default:
break; break;
} }
@@ -772,6 +822,7 @@ bool MacroActionTwitch::ActionIsSupportedByToken()
{Action::WHISPER_SEND, {{"user:manage:whispers"}}}, {Action::WHISPER_SEND, {{"user:manage:whispers"}}},
{Action::SEND_CHAT_MESSAGE, {{"chat:edit"}}}, {Action::SEND_CHAT_MESSAGE, {{"chat:edit"}}},
{Action::USER_GET_INFO, {}}, {Action::USER_GET_INFO, {}},
{Action::CHANNEL_GET_INFO, {}},
{Action::POINTS_REWARD_GET_INFO, {Action::POINTS_REWARD_GET_INFO,
{{"channel:read:redemptions"}, {{"channel:read:redemptions"},
{"channel:manage:redemptions"}}}}; {"channel:manage:redemptions"}}}};
@@ -1127,6 +1178,8 @@ void MacroActionTwitchEdit::SetWidgetVisibility()
_entryData->GetAction() == _entryData->GetAction() ==
MacroActionTwitch::Action::CHANNEL_INFO_CONTENT_LABELS_SET); MacroActionTwitch::Action::CHANNEL_INFO_CONTENT_LABELS_SET);
_channel->setVisible( _channel->setVisible(
_entryData->GetAction() ==
MacroActionTwitch::Action::CHANNEL_GET_INFO ||
_entryData->GetAction() == _entryData->GetAction() ==
MacroActionTwitch::Action::RAID_START || MacroActionTwitch::Action::RAID_START ||
_entryData->GetAction() == _entryData->GetAction() ==
@@ -1273,19 +1326,23 @@ void MacroActionTwitchEdit::SetWidgetLayout()
switch (_entryData->GetAction()) { switch (_entryData->GetAction()) {
case MacroActionTwitch::Action::SEND_CHAT_MESSAGE: case MacroActionTwitch::Action::SEND_CHAT_MESSAGE:
layoutText = obs_module_text( layoutText = obs_module_text(
"AdvSceneSwitcher.action.twitch.entry.chat"); "AdvSceneSwitcher.action.twitch.layout.chat");
break; break;
case MacroActionTwitch::Action::USER_GET_INFO: case MacroActionTwitch::Action::USER_GET_INFO:
layoutText = obs_module_text( layoutText = obs_module_text(
"AdvSceneSwitcher.action.twitch.entry.user.getInfo"); "AdvSceneSwitcher.action.twitch.layout.user.getInfo");
break; break;
case MacroActionTwitch::Action::POINTS_REWARD_GET_INFO: case MacroActionTwitch::Action::POINTS_REWARD_GET_INFO:
layoutText = obs_module_text( layoutText = obs_module_text(
"AdvSceneSwitcher.action.twitch.entry.reward.getInfo"); "AdvSceneSwitcher.action.twitch.layout.reward.getInfo");
break;
case MacroActionTwitch::Action::CHANNEL_GET_INFO:
layoutText = obs_module_text(
"AdvSceneSwitcher.action.twitch.layout.channel.getInfo");
break; break;
default: default:
layoutText = obs_module_text( layoutText = obs_module_text(
"AdvSceneSwitcher.action.twitch.entry.default"); "AdvSceneSwitcher.action.twitch.layout.default");
break; break;
} }

View File

@@ -142,7 +142,8 @@ public:
SEND_CHAT_MESSAGE = 5000, SEND_CHAT_MESSAGE = 5000,
// Get user info // Get user info
USER_GET_INFO = 6000 USER_GET_INFO = 6000,
CHANNEL_GET_INFO = 6100,
}; };
enum class AnnouncementColor { enum class AnnouncementColor {
@@ -203,6 +204,7 @@ private:
void SendChatMessage(const std::shared_ptr<TwitchToken> &); void SendChatMessage(const std::shared_ptr<TwitchToken> &);
void GetUserInfo(const std::shared_ptr<TwitchToken> &); void GetUserInfo(const std::shared_ptr<TwitchToken> &);
void GetRewardInfo(const std::shared_ptr<TwitchToken> &); void GetRewardInfo(const std::shared_ptr<TwitchToken> &);
void GetChannelInfo(const std::shared_ptr<TwitchToken> &token);
bool ResolveVariableSelectionToRewardId( bool ResolveVariableSelectionToRewardId(
const std::shared_ptr<TwitchToken> &); const std::shared_ptr<TwitchToken> &);

View File

@@ -629,7 +629,7 @@ void MacroConditionTwitch::SetTempVarValues(const ChannelInfo &info)
if (!classificationLabels.empty()) { if (!classificationLabels.empty()) {
classificationLabels.pop_back(); classificationLabels.pop_back();
} }
SetTempVarValue("content_classification_labels", tags); SetTempVarValue("content_classification_labels", classificationLabels);
SetTempVarValue("is_branded_content", SetTempVarValue("is_branded_content",
info.is_branded_content ? "true" : "false"); info.is_branded_content ? "true" : "false");
} }

View File

@@ -829,7 +829,7 @@ void TokenGrabberThread::run()
if (_serverThread.joinable()) { if (_serverThread.joinable()) {
_serverThread.join(); _serverThread.join();
} }
_stopWaiting = {false}; _stopWaiting = false;
// Generate URI to request token // Generate URI to request token
auto state = generateStateString(); auto state = generateStateString();

View File

@@ -106,8 +106,11 @@ target_sources(
# --- variable --- # # --- variable --- #
if(NOT OS_WINDOWS) if(OS_WINDOWS)
target_compile_options(${PROJECT_NAME} PUBLIC -Wno-error=shadow) target_compile_options(${PROJECT_NAME} PRIVATE /wd4100)
else()
target_compile_options(${PROJECT_NAME} PRIVATE -Wno-error=shadow
-Wno-unused-parameter)
endif() endif()
target_sources( target_sources(