Compare commits

..

69 Commits

Author SHA1 Message Date
WarmUpTill
b25c1250e5 Try adding tilde key to hotkey action 2024-01-03 19:24:08 +01:00
WarmUpTill
98f3f51ba9 Don't enable drag of macro segment when interacting with scrollbars 2023-12-31 10:13:48 +01:00
WarmUpTill
f0df9ead3c Fix macro segment widgets being squashed below a sensible minimum size
Now a horizontal scroll bar will be displayed in the corresponding
MacroSegmentList widget instead.
2023-12-29 19:31:45 +01:00
WarmUpTill
7b1a57070d Clean up locale 2023-12-29 19:31:45 +01:00
WarmUpTill
a412f2fd5e Fix ResizingPlainTextEdit hiding text in some circumstances
In some scenarios - the exact reason is not known - the widget would
scroll to the very last row, which is always empty, and result in the
text cursor and the already entered text being moved above the displayed
edit area.

It could be fixed by manually scrolling back up (e.g. by attempting to
highlight text with the mouse), but the problem would reappear as soon
as new existing text was modified.

To work around this issue text wrapping was disabled and additional
height will be added for the horizontal scroll bar, if they are visible.

There are still some issues of the height randomly flickering in the
scenarios, which would previously result in the text no longer being
visible.
2023-12-29 19:31:45 +01:00
WarmUpTill
4f0c8c4ab5 Move audio fade helpers to audio action 2023-12-29 19:31:45 +01:00
WarmUpTill
946c90a749 Move tracking of cursor position changes to cursor condition 2023-12-29 19:31:45 +01:00
WarmUpTill
7ba4eaab9c Fix replay buffer save event only triggering in a single condition 2023-12-29 19:31:45 +01:00
WarmUpTill
74553a82fc Remove deprecated "on change" toggle of media condition 2023-12-29 19:31:45 +01:00
WarmUpTill
39fcb2d743 Fix typos 2023-12-29 19:31:45 +01:00
WarmUpTill
7041feaea3 Move handling of stream start / stop time to stream condition 2023-12-29 19:31:45 +01:00
WarmUpTill
9b1ffaeded Remove switcher-data dependency from transition-selection 2023-12-29 19:31:45 +01:00
WarmUpTill
07d397bdac Remove switcher-data dependency from scene-selection 2023-12-29 19:31:45 +01:00
WarmUpTill
a86ff4f1f0 Remove switcher-data dependency from status-control 2023-12-29 19:31:45 +01:00
WarmUpTill
953eca2732 Remove switcher-data dependency from variables 2023-12-29 19:31:45 +01:00
WarmUpTill
d6c5fb149a Fix warning 2023-12-29 19:31:45 +01:00
WarmUpTill
12982708cf Remove curl-helper dependency from switcher-data 2023-12-29 19:31:45 +01:00
WarmUpTill
0cf9e1dd81 Reduce dependencies of macros to switcher data 2023-12-29 19:31:45 +01:00
WarmUpTill
71e20c4983 Move loading / saving of global macro properties 2023-12-29 19:31:45 +01:00
WarmUpTill
2147f6d3bf Rename files 2023-12-29 19:31:45 +01:00
WarmUpTill
23df42d2d3 Add more plugin state helpers 2023-12-29 19:31:45 +01:00
WarmUpTill
bcf0a0b7fd Move functionality to scene-switch-helpers 2023-12-29 19:31:45 +01:00
WarmUpTill
dd78b03a28 Add additional sync-helpers to reduce dependencies to switcher-data 2023-12-29 19:31:45 +01:00
WarmUpTill
84dd5e6930 Add macro-helpers
Added to reduce dependencies to switcher-data
2023-12-29 19:31:45 +01:00
WarmUpTill
dcee98fea7 Move macro segment factory functinos to seperate files 2023-12-29 19:31:45 +01:00
WarmUpTill
9ffb982ae5 Reset date condition type only if necessary 2023-12-29 19:31:45 +01:00
WarmUpTill
428850306f Add option to wait for spawned process to exit 2023-12-25 21:24:48 +01:00
WarmUpTill
abfd6798e3 Adapt to new process start helpers 2023-12-25 21:24:48 +01:00
WarmUpTill
fffadd94de Add helpers to launch process with given config 2023-12-25 21:24:48 +01:00
WarmUpTill
d0919776f3 Add option to set variable to scene item name at given index 2023-12-19 22:59:32 +01:00
WarmUpTill
267855fded Cleanup
* Use OBSSourceAutoRelease
* Do not return true if source selection is invalid
2023-12-17 10:40:29 +01:00
WarmUpTill
4bdebb7feb Fix crash when asking for settings backup
This dialog cannot be modal since other plugins might attempt to display
content which might result in a deadlock.
2023-12-16 19:02:38 +01:00
Michael Kirsch
ab5f69d2ef Decrease the minimal interval time to 10ms 2023-12-13 19:29:14 +01:00
Michael Kirsch
63ee36ddfe Enable Area Selection in ScreenshotHelper class 2023-12-13 14:19:17 +01:00
WarmUpTill
43967f5ded Add temp variable support for video condition 2023-12-10 14:26:30 +01:00
WarmUpTill
c043053b2b Remove unused variable 2023-12-10 14:26:30 +01:00
WarmUpTill
220f27bd0a Move plugin dependencies to Aptfile 2023-12-10 13:42:39 +01:00
WarmUpTill
83914d5378 Linux: resolve procps symbols at runtime
This is done to be able to support both libprocps and libproc2.
Either of them or neither might be available on all platforms and this
should not prevent the plugin from being used at all.
2023-12-10 13:42:39 +01:00
WarmUpTill
57e4b1a3f1 Fix warning 2023-12-10 13:42:39 +01:00
WarmUpTill
c85e556924 Add support for "random()" expression
Generates a random number between [0,1)
2023-12-10 13:33:18 +01:00
WarmUpTill
8c0be8011d Enable use of obs-module-helper.hpp in tests 2023-12-10 13:33:18 +01:00
WarmUpTill
7914f75876 Fix macro references not being resolved when importing macros 2023-12-09 09:52:12 +01:00
WarmUpTill
7d1253e14e Use RAII wrappers for OBS data 2023-12-09 09:52:12 +01:00
WarmUpTill
d7c58b3eb3 Remove websocket dependency to switcher-data internals 2023-12-09 09:52:12 +01:00
WarmUpTill
fb664b6f08 Reduce dependencies to switcher-data.hpp implementation details
This is necessary for the switch to libremidi version 4 due to
incompatabilities  with websocketpp, but also might save some headaches
later on.
2023-12-09 09:52:12 +01:00
WarmUpTill
258a6d076e Fix "or not" logic type check being ignored 2023-12-09 09:52:12 +01:00
WarmUpTill
32ab060153 Ease configuration of token grabber port 2023-12-09 09:52:12 +01:00
WarmUpTill
91d3784f9b Don't wait for transition to complete if already on target scene 2023-12-09 09:52:12 +01:00
WarmUpTill
8a5aac3d31 Add option to check the recording duration 2023-12-09 09:52:12 +01:00
WarmUpTill
06c6effb7d Fix duration value not being updated correctly when changing units 2023-12-09 09:52:12 +01:00
WarmUpTill
f3789c05bd Move Duration and DurationSelection into separate files 2023-12-09 09:52:12 +01:00
WarmUpTill
c64e1f5e51 Rework Process condition
* Add option to en-/disable regex matching
* Add tempvar support for process name
2023-12-09 09:52:12 +01:00
WarmUpTill
5d8e24c19a Add temp variable support to Macro condition 2023-12-09 09:52:12 +01:00
WarmUpTill
f756de05c3 Add option to check if macro is paused 2023-12-09 09:52:12 +01:00
WarmUpTill
574fdb496f Add error logs for token grabber server 2023-12-09 09:52:12 +01:00
WarmUpTill
5580da69eb Fix window condition not clearing tempvars of previous condition type 2023-12-09 09:52:12 +01:00
WarmUpTill
35274863c9 Use available layout stretch space for settings input field 2023-12-09 09:52:12 +01:00
Przemek Pawlas
1f31b81f0d Add else actions toggle button 2023-12-09 09:42:10 +01:00
WarmUpTill
cde111ddeb Ignore settings which don't have a description or name
This fixes a crash when attempting to populate the setttings selection
of a Window Capture or Game Capture source
2023-12-07 07:04:50 +01:00
Przemek Pawlas
845c12c01c Add tooltip for run button explaining else (#931)
* Add tooltip for run button explaining else
* Switch run button modifier to Shift
* Toggle button label only when hovering over it
2023-12-06 10:40:14 +01:00
Przemek Pawlas
99a0118d31 Add clipboard text/image copy actions 2023-11-27 21:07:38 +01:00
Przemek Pawlas
bb7ee17362 Prepare Twitch action for introduction of new action types 2023-11-26 17:48:45 +01:00
Przemek Pawlas
70854ef012 Reorder Twitch condition enum value definitions 2023-11-26 17:48:45 +01:00
Przemek Pawlas
053c14056d Add more token options and reorder existing ones 2023-11-26 17:48:45 +01:00
Przemek Pawlas
9397d50764 Refactor twitch-helpers
Added more http helpers, reordered parameters, and reduced code
duplication
2023-11-26 17:48:45 +01:00
Przemek Pawlas
473e24cc42 Use consistent function naming for setting widget visibility 2023-11-26 17:48:45 +01:00
WarmUpTill
d9394605f4 Set tempvar values for raid events 2023-11-26 15:29:54 +01:00
WarmUpTill
d2b9074852 Fix build for Twitch mock 2023-11-26 15:29:54 +01:00
WarmUpTill
b525eb7a0e Add temp variable support to MIDI condition 2023-11-26 13:05:44 +01:00
152 changed files with 3925 additions and 1832 deletions

View File

@@ -7,3 +7,8 @@ package 'ninja-build', bin: 'ninja'
package 'pkg-config'
package 'clang'
package 'clang-format-13'
package 'libxtst-dev'
package 'libxss-dev'
package 'libopencv-dev'
package 'libtesseract-dev'
package 'libprocps-dev'

View File

@@ -34,13 +34,6 @@ if (( ! (${skips[(Ie)all]} + ${skips[(Ie)deps]}) )) {
log_info 'Installing obs plugin dependencies...'
sudo apt-get install -y \
libxtst-dev \
libxss-dev \
libopencv-dev \
libtesseract-dev \
libprocps-dev
local -a _qt_packages=()
if (( QT_VERSION == 5 )) {

View File

@@ -93,8 +93,12 @@ target_sources(
${LIB_NAME}
PRIVATE src/macro-core/macro-action-audio.cpp
src/macro-core/macro-action-audio.hpp
src/macro-core/macro-action-clipboard.cpp
src/macro-core/macro-action-clipboard.hpp
src/macro-core/macro-action-edit.cpp
src/macro-core/macro-action-edit.hpp
src/macro-core/macro-action-factory.cpp
src/macro-core/macro-action-factory.hpp
src/macro-core/macro-action-file.cpp
src/macro-core/macro-action-file.hpp
src/macro-core/macro-action-filter.cpp
@@ -171,6 +175,8 @@ target_sources(
src/macro-core/macro-condition-display.hpp
src/macro-core/macro-condition-edit.cpp
src/macro-core/macro-condition-edit.hpp
src/macro-core/macro-condition-factory.cpp
src/macro-core/macro-condition-factory.hpp
src/macro-core/macro-condition-file.cpp
src/macro-core/macro-condition-file.hpp
src/macro-core/macro-condition-filter.cpp
@@ -231,6 +237,8 @@ target_sources(
src/macro-core/macro-condition.hpp
src/macro-core/macro-dock.cpp
src/macro-core/macro-dock.hpp
src/macro-core/macro-helpers.cpp
src/macro-core/macro-helpers.hpp
src/macro-core/macro-properties.cpp
src/macro-core/macro-properties.hpp
src/macro-core/macro-ref.cpp
@@ -254,6 +262,8 @@ target_sources(
src/utils/connection-manager.hpp
src/utils/curl-helper.cpp
src/utils/curl-helper.hpp
src/utils/duration.cpp
src/utils/duration.hpp
src/utils/duration-control.cpp
src/utils/duration-control.hpp
src/utils/export-symbol-helper.hpp
@@ -287,6 +297,8 @@ target_sources(
src/utils/obs-module-helper.hpp
src/utils/osc-helpers.cpp
src/utils/osc-helpers.hpp
src/utils/plugin-state-helpers.cpp
src/utils/plugin-state-helpers.hpp
src/utils/priority-helper.cpp
src/utils/priority-helper.hpp
src/utils/process-config.cpp
@@ -317,8 +329,8 @@ target_sources(
src/utils/striped-frame.hpp
src/utils/switch-button.cpp
src/utils/switch-button.hpp
src/utils/sync-helper.cpp
src/utils/sync-helper.hpp
src/utils/sync-helpers.cpp
src/utils/sync-helpers.hpp
src/utils/temp-variable.cpp
src/utils/temp-variable.hpp
src/utils/transition-selection.cpp
@@ -458,27 +470,24 @@ else()
find_path(PROCPS_INCLUDE_DIR NAMES proc/procps.h)
find_path(PROCPS2_INCLUDE_DIR NAMES libproc2/pids.h)
find_library(PROCPS_LIBRARY NAMES procps)
find_library(PROCPS2_LIBRARY NAMES proc2)
if(PROCPS_INCLUDE_DIR AND PROCPS_LIBRARY)
if(PROCPS_INCLUDE_DIR)
message(STATUS "${PROJECT_NAME} using procps")
set(PROC_INCLUDE_DIR "${PROCPS_INCLUDE_DIR}")
set(PROC_LIBRARY "${PROCPS_LIBRARY}")
target_compile_definitions(${LIB_NAME} PRIVATE USE_PROCPS)
elseif(PROCPS2_INCLUDE_DIR AND PROCPS2_LIBRARY)
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS_AVAILABLE)
set(PROCESS_CONDITION_SUPPORTED 1)
endif()
if(PROCPS2_INCLUDE_DIR)
message(STATUS "${PROJECT_NAME} using libproc2")
set(PROC_INCLUDE_DIR "${PROCPS2_INCLUDE_DIR}")
set(PROC_LIBRARY "${PROCPS2_LIBRARY}")
else()
message(
FATAL_ERROR
"found neither procps nor libproc2!
please either set ...
PROCPS_INCLUDE_DIR and PROCPS_LIBRARY
... or ...
PROCPS2_INCLUDE_DIR and PROCPS2_LIBRARY")
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS2_AVAILABLE)
set(PROCESS_CONDITION_SUPPORTED 1)
endif()
if(NOT DEFINED PROCESS_CONDITION_SUPPORTED)
message(
WARNING
"found neither procps nor libproc2! Process condition will not be functional!"
)
endif()
target_link_libraries(${LIB_NAME} PRIVATE "${PROC_LIBRARY}")
target_include_directories(${LIB_NAME} PRIVATE "${PROC_INCLUDE_DIR}")
find_package(CURL)

View File

@@ -159,7 +159,6 @@ AdvSceneSwitcher.condition.media="Medien"
AdvSceneSwitcher.condition.media.source="Quelle"
AdvSceneSwitcher.condition.media.anyOnScene="Beliebige Medienquelle in"
AdvSceneSwitcher.condition.media.allOnScene="Alle Medienquellen in"
AdvSceneSwitcher.condition.media.matchOnChange="Nur bei Änderung übereinstimmen (Hinweis: Diese Option wird in einer zukünftigen Version entfernt werden - bitte stattdessen Dauer-Modifikatoren verwenden)"
AdvSceneSwitcher.condition.media.inconsistencyInfo="Leider verhalten sich nicht alle Medien-Quell-Typen gleich (z.B. Medien-Quelle vs. VLC-Video-Quelle \"Gestoppt\"-Status).\nBitte experimentieren, was in eurem Setup funktioniert!"
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}Status ist{{states}}und{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Video"
@@ -221,9 +220,8 @@ AdvSceneSwitcher.condition.record="Aufnahme"
AdvSceneSwitcher.condition.record.state.start="Aufnahme läuft"
AdvSceneSwitcher.condition.record.state.pause="Aufnahme pausiert"
AdvSceneSwitcher.condition.record.state.stop="Aufnahme gestoppt"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.process="Prozess"
AdvSceneSwitcher.condition.process.entry="{{processes}} läuft {{focused}} und ist fokusiert"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}läuft{{focused}}und ist fokusiert"
AdvSceneSwitcher.condition.process.entry.focus="Aktueller Vordergrundprozess: {{focusProcess}}"
AdvSceneSwitcher.condition.idle="Leerlauf"
AdvSceneSwitcher.condition.idle.entry="Keine Tastatur- oder Mauseingaben für {{duration}}"
@@ -575,7 +573,6 @@ AdvSceneSwitcher.action.variable.invalidSelection="Ungültige Auswahl!"
AdvSceneSwitcher.action.variable.actionNoVariableSupport="Das Abrufen von Variablenwerten aus %1 Aktionen wird nicht unterstützt!"
AdvSceneSwitcher.action.variable.conditionNoVariableSupport="Das Abrufen von Variablenwerten aus %1 Bedingungen wird nicht unterstützt!"
AdvSceneSwitcher.action.variable.currentSegmentValue="Aktueller Wert:"
AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}"
; Transition Tab

View File

@@ -79,6 +79,7 @@ AdvSceneSwitcher.macroTab.edit.action="Action type:"
AdvSceneSwitcher.macroTab.add="Add new macro"
AdvSceneSwitcher.macroTab.name="Name:"
AdvSceneSwitcher.macroTab.run="Run macro"
AdvSceneSwitcher.macroTab.run.tooltip="Run all macro actions regardless of condition.\nIf else actions are available, hold Shift and click the button to execute them instead."
AdvSceneSwitcher.macroTab.runElse="Run macro (else)"
AdvSceneSwitcher.macroTab.runFail="Running \"%1\" failed!\nEither one of the actions failed or the macro is running already."
AdvSceneSwitcher.macroTab.runInParallel="Run macro in parallel to other macros"
@@ -125,6 +126,8 @@ AdvSceneSwitcher.macroTab.currentDockButtonText.unpause="Unpause button text:"
AdvSceneSwitcher.macroTab.currentDockStatusText.true="Conditions true text:"
AdvSceneSwitcher.macroTab.currentDockStatusText.false="Conditions false text:"
AdvSceneSwitcher.macroTab.currentDockHighlightIfExecuted="Highlight dock if macro actions were recently executed"
AdvSceneSwitcher.macroTab.toggleElseActions.hide.tooltip="Hide else actions"
AdvSceneSwitcher.macroTab.toggleElseActions.show.tooltip="Show else actions"
AdvSceneSwitcher.macroDock.pause="Pause"
AdvSceneSwitcher.macroDock.unpause="Unpause"
@@ -204,7 +207,6 @@ AdvSceneSwitcher.condition.media="Media"
AdvSceneSwitcher.condition.media.source="Source"
AdvSceneSwitcher.condition.media.anyOnScene="Any media source on"
AdvSceneSwitcher.condition.media.allOnScene="All media sources on"
AdvSceneSwitcher.condition.media.matchOnChange="Only match on change (Note: This option will be removed in a future version - please use duration modifiers instead)"
AdvSceneSwitcher.condition.media.inconsistencyInfo="Unfortunately not all media source types behave the same (e.g. Media Source vs. VLC Video Source \"Stopped\" state).\nSo please experiment what works for your setup!"
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}state is{{states}}and{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Video"
@@ -295,10 +297,10 @@ AdvSceneSwitcher.condition.record="Recording"
AdvSceneSwitcher.condition.record.state.start="Recording running"
AdvSceneSwitcher.condition.record.state.pause="Recording paused"
AdvSceneSwitcher.condition.record.state.stop="Recording stopped"
AdvSceneSwitcher.condition.record.state.saveDone="Recording saved"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.record.state.duration="Recording duration is longer than"
AdvSceneSwitcher.condition.record.entry="{{condition}}{{duration}}"
AdvSceneSwitcher.condition.process="Process"
AdvSceneSwitcher.condition.process.entry="{{processes}}is running{{focused}}and is focused"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}is running{{focused}}and is focused"
AdvSceneSwitcher.condition.process.entry.focus="Current foreground process:{{focusProcess}}"
AdvSceneSwitcher.condition.idle="Idle"
AdvSceneSwitcher.condition.idle.entry="No keyboard or mouse inputs for{{duration}}"
@@ -327,6 +329,7 @@ AdvSceneSwitcher.condition.macro.type.state="Macro condition state"
AdvSceneSwitcher.condition.macro.type.multiState="Multiple macro condition state"
AdvSceneSwitcher.condition.macro.type.actionDisabled="Macro action disabled"
AdvSceneSwitcher.condition.macro.type.actionEnabled="Macro action enabled"
AdvSceneSwitcher.condition.macro.type.paused="Macro is paused"
AdvSceneSwitcher.condition.macro.type.selection="{{types}}"
AdvSceneSwitcher.condition.macro.count.type.below="Less than"
AdvSceneSwitcher.condition.macro.count.type.above="More than"
@@ -342,6 +345,7 @@ AdvSceneSwitcher.condition.macro.count.entry.line1="{{macros}}was executed{{cond
AdvSceneSwitcher.condition.macro.count.entry.line2="Current count:{{currentCount}}{{resetCount}}"
AdvSceneSwitcher.condition.macro.actionState.disabled.entry="Action{{actionIndex}}of{{macros}}is disabled"
AdvSceneSwitcher.condition.macro.actionState.enabled.entry="Action{{actionIndex}}of{{macros}}is enabled"
AdvSceneSwitcher.condition.macro.paused.entry="Macro{{macros}}is paused"
AdvSceneSwitcher.condition.source="Source"
AdvSceneSwitcher.condition.source.type.active="Is active"
AdvSceneSwitcher.condition.source.type.showing="Is showing"
@@ -605,6 +609,7 @@ AdvSceneSwitcher.action.streaming.type.username="Set username"
AdvSceneSwitcher.action.streaming.type.password="Set password"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="Run"
AdvSceneSwitcher.action.run.entry.wait="{{wait}}Wait for process exit or at most {{timeout}}"
AdvSceneSwitcher.action.sceneVisibility="Scene item visibility"
AdvSceneSwitcher.action.sceneVisibility.type.show="Show"
AdvSceneSwitcher.action.sceneVisibility.type.hide="Hide"
@@ -815,6 +820,7 @@ AdvSceneSwitcher.action.variable.type.sceneItemCount="Set to scene item count of
AdvSceneSwitcher.action.variable.type.stringLength="Set to length of string"
AdvSceneSwitcher.action.variable.type.extractJson="Extract json field with name"
AdvSceneSwitcher.action.variable.type.setToTempvar="Set to macro property"
AdvSceneSwitcher.action.variable.type.sceneItemName="Set to scene item name at index"
AdvSceneSwitcher.action.variable.askForValuePromptDefault="Assign value to variable \"%1\":"
AdvSceneSwitcher.action.variable.askForValuePrompt="Assign value to variable:"
AdvSceneSwitcher.action.variable.mathExpression.example="( 1 + 2 * 3 ) / 4"
@@ -826,7 +832,7 @@ AdvSceneSwitcher.action.variable.invalidSelection="Invalid selection!"
AdvSceneSwitcher.action.variable.actionNoVariableSupport="Getting variable values from %1 actions is not supported!"
AdvSceneSwitcher.action.variable.conditionNoVariableSupport="Getting variable values from %1 conditions is not supported!"
AdvSceneSwitcher.action.variable.currentSegmentValue="Current value:"
AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}"
AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}{{sceneItemIndex}}"
AdvSceneSwitcher.action.variable.entry.substringIndex="Substring start:{{subStringStart}}Substring size:{{subStringSize}}"
AdvSceneSwitcher.action.variable.entry.substringRegex="Assign value of{{regexMatchIdx}}match using regular expression:"
AdvSceneSwitcher.action.variable.entry.findAndReplace="{{findStr}}{{replaceStr}}"
@@ -853,15 +859,15 @@ AdvSceneSwitcher.action.sceneLock.type.unlock="unlock"
AdvSceneSwitcher.action.sceneLock.type.toggle="toggle lock of"
AdvSceneSwitcher.action.sceneLock.entry="On{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.twitch="Twitch"
AdvSceneSwitcher.action.twitch.type.title="Set stream title"
AdvSceneSwitcher.action.twitch.type.category="Set stream category"
AdvSceneSwitcher.action.twitch.type.marker="Create stream marker"
AdvSceneSwitcher.action.twitch.type.clip="Create stream clip"
AdvSceneSwitcher.action.twitch.type.commercial="Start commercial with duration"
AdvSceneSwitcher.action.twitch.type.announcement="Send chat announcement"
AdvSceneSwitcher.action.twitch.type.emoteOnlyEnable="Enable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.emoteOnlyDisable="Disable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.raid="Raid channel"
AdvSceneSwitcher.action.twitch.type.channel.info.title.set="Set stream title"
AdvSceneSwitcher.action.twitch.type.channel.info.category.set="Set stream category"
AdvSceneSwitcher.action.twitch.type.raid.start="Start raid"
AdvSceneSwitcher.action.twitch.type.commercial.start="Start commercial"
AdvSceneSwitcher.action.twitch.type.marker.create="Create stream marker"
AdvSceneSwitcher.action.twitch.type.clip.create="Create stream clip"
AdvSceneSwitcher.action.twitch.type.chat.announcement.send="Send chat announcement"
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable="Enable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="Disable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.sendChatMessage="Send chat message"
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}}"
@@ -875,6 +881,12 @@ AdvSceneSwitcher.action.twitch.announcement.blue="Blue"
AdvSceneSwitcher.action.twitch.announcement.green="Green"
AdvSceneSwitcher.action.twitch.announcement.orange="Orange"
AdvSceneSwitcher.action.twitch.announcement.purple="Purple"
AdvSceneSwitcher.action.clipboard="Clipboard"
AdvSceneSwitcher.action.clipboard.type.copy.text="Copy text"
AdvSceneSwitcher.action.clipboard.type.copy.image="Copy image"
AdvSceneSwitcher.action.clipboard.copy.text.text.placeholder="Enter text"
AdvSceneSwitcher.action.clipboard.copy.image.url.placeholder="Enter direct image URL"
AdvSceneSwitcher.action.clipboard.copy.image.url.tooltip="Currently supported formats: PNG, JPG/JPEG, BMP, GIF."
; Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher"
@@ -1028,14 +1040,10 @@ AdvSceneSwitcher.twitchToken.request.notSet="Account is not connected!"
AdvSceneSwitcher.twitchToken.permissions="Token permissions:"
AdvSceneSwitcher.twitchToken.permissionsInsufficient="Permissions of selected token are insufficient to perform selected action!"
AdvSceneSwitcher.twitchToken.channel.broadcast.manage="Manage a channels broadcast configuration, including updating channel configuration and managing stream markers and stream tags."
AdvSceneSwitcher.twitchToken.channel.commercial.edit="Run commercials on a channel."
AdvSceneSwitcher.twitchToken.clips.edit="Create clips from channel's broadcasts."
AdvSceneSwitcher.twitchToken.moderator.announcements.manage="Manage channel's chat announcements."
AdvSceneSwitcher.twitchToken.moderator.chat.settings.manage="Manage channel's chat settings, such as emote-only or slow mode."
AdvSceneSwitcher.twitchToken.channel.raid="Manage channel's raids."
AdvSceneSwitcher.twitchToken.channel.subscriptions.read="Read channel's subscribers list and info."
AdvSceneSwitcher.twitchToken.moderator.followers.read="Read the followers of a broadcaster."
AdvSceneSwitcher.twitchToken.bits.read="Read channel's Bits information."
AdvSceneSwitcher.twitchToken.channel.raids.manage="Manage channel's raids."
AdvSceneSwitcher.twitchToken.moderator.shoutouts.read="Read channel's shoutouts."
AdvSceneSwitcher.twitchToken.moderator.shoutouts.manage="Manage channel's shoutouts."
AdvSceneSwitcher.twitchToken.channel.polls.read="Read channel's polls' state."
@@ -1050,7 +1058,17 @@ AdvSceneSwitcher.twitchToken.moderator.shieldMode.manage="Manage channel's Shiel
AdvSceneSwitcher.twitchToken.channel.redemptions.read="Read channel's point rewards and their redemptions."
AdvSceneSwitcher.twitchToken.channel.redemptions.manage="Manage channel's point rewards and their redemptions."
AdvSceneSwitcher.twitchToken.channel.moderate="Moderate the channel."
AdvSceneSwitcher.twitchToken.channel.moderation.read="Read channel's moderation data."
AdvSceneSwitcher.twitchToken.moderator.bannedUsers.manage="Manage channel's banned users."
AdvSceneSwitcher.twitchToken.user.blockedUsers.manage="Manage user's blocked users."
AdvSceneSwitcher.twitchToken.moderation.read="Read channel's moderation data."
AdvSceneSwitcher.twitchToken.channel.moderators.manage="Manage channel's moderators."
AdvSceneSwitcher.twitchToken.channel.vips.manage="Manage channel's VIPs."
AdvSceneSwitcher.twitchToken.channel.commercial.edit="Run commercials on a channel."
AdvSceneSwitcher.twitchToken.channel.ads.manage="Manage channel's commercials."
AdvSceneSwitcher.twitchToken.clips.edit="Create clips from channel's broadcasts."
AdvSceneSwitcher.twitchToken.moderator.announcements.manage="Manage channel's chat announcements."
AdvSceneSwitcher.twitchToken.moderator.chat.settings.manage="Manage channel's chat settings, such as emote-only or slow mode."
AdvSceneSwitcher.twitchToken.user.whispers.manage="Manage user's whispers."
AdvSceneSwitcher.twitchToken.chat.read="View live stream chat messages."
AdvSceneSwitcher.twitchToken.chat.edit="Send live stream chat messages."
@@ -1276,9 +1294,9 @@ AdvSceneSwitcher.tempVar.twitch.started_at.shoutoutReceived="Shoutout time"
AdvSceneSwitcher.tempVar.twitch.started_at.shoutoutReceived.description="The UTC timestamp (in RFC3339 format) of when the moderator sent the Shoutout."
AdvSceneSwitcher.tempVar.twitch.id.reward="Reward id"
AdvSceneSwitcher.tempVar.twitch.user_id.reward="User id redeeming reward"
AdvSceneSwitcher.tempVar.twitch.user_login.reward="User login redeeming reward"
AdvSceneSwitcher.tempVar.twitch.user_name.reward="User login redeeming reward"
AdvSceneSwitcher.tempVar.twitch.user_id.reward="User id redeeming the reward"
AdvSceneSwitcher.tempVar.twitch.user_login.reward="User login redeeming the reward"
AdvSceneSwitcher.tempVar.twitch.user_name.reward="User name redeeming the reward"
AdvSceneSwitcher.tempVar.twitch.user_input.reward="User input for reward"
AdvSceneSwitcher.tempVar.twitch.reward.reward="Reward Information"
AdvSceneSwitcher.tempVar.twitch.reward.reward.description="Basic information about the reward that was redeemed, at the time it was redeemed."
@@ -1413,6 +1431,20 @@ AdvSceneSwitcher.tempVar.twitch.chatter="User login"
AdvSceneSwitcher.tempVar.twitch.chatter.description="The user login of the person who sent the chat message."
AdvSceneSwitcher.tempVar.twitch.chat_message="Chat message"
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_id.raid="Raid creator user ID"
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_id.raid.description="The broadcaster ID that created the raid."
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_login.raid="Raid creator user login"
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_login.raid.description="The broadcaster login that created the raid."
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_name.raid="Raid creator user name"
AdvSceneSwitcher.tempVar.twitch.from_broadcaster_user_name.raid.description="The broadcaster display name that created the raid."
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_id.raid="Raid receiver user ID"
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_id.raid.description="The broadcaster ID that created the raid."
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_login.raid="Raid receiver user login"
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_login.raid.description="The broadcaster login that created the raid."
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_name.raid="Raid receiver user name"
AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_name.raid.description="The broadcaster display name that created the raid."
AdvSceneSwitcher.tempVar.twitch.viewers.raid="Raid participants"
AdvSceneSwitcher.tempVar.twitch.viewers.raid.description="The number of viewers in the raid."
AdvSceneSwitcher.tempVar.audio.output_volume="Output volume"
AdvSceneSwitcher.tempVar.audio.output_volume.description="The volume the audio source is outputting."
@@ -1423,6 +1455,11 @@ AdvSceneSwitcher.tempVar.audio.sync_offset="Source audio sync offset"
AdvSceneSwitcher.tempVar.audio.monitor="Source audio monitor type"
AdvSceneSwitcher.tempVar.audio.balance="Source audio balance"
AdvSceneSwitcher.tempVar.clipboard.mimeType.primary="Primary clipboard item MIME type"
AdvSceneSwitcher.tempVar.clipboard.mimeType.primary.description="Highest priority MIME type of the current item stored in clipboard, if available."
AdvSceneSwitcher.tempVar.clipboard.mimeType.all="All clipboard item MIME types"
AdvSceneSwitcher.tempVar.clipboard.mimeType.all.description="All MIME types of the current item stored in clipboard separated with space, if available."
AdvSceneSwitcher.tempVar.scene.current="Current scene"
AdvSceneSwitcher.tempVar.scene.previous="Previous scene"
AdvSceneSwitcher.tempVar.scene.preview="Preview scene"
@@ -1443,6 +1480,33 @@ AdvSceneSwitcher.tempVar.date.minute="Minute"
AdvSceneSwitcher.tempVar.date.second="Second"
AdvSceneSwitcher.tempVar.date.dayOfWeek="Day of week"
AdvSceneSwitcher.tempVar.midi.type="Message type"
AdvSceneSwitcher.tempVar.midi.channel="Channel"
AdvSceneSwitcher.tempVar.midi.note="Note"
AdvSceneSwitcher.tempVar.midi.value1="Value 1"
AdvSceneSwitcher.tempVar.midi.value2="Value 2"
AdvSceneSwitcher.tempVar.macro.runCount="Run count"
AdvSceneSwitcher.tempVar.macro.runCount.description="The number of times a macro was executed."
AdvSceneSwitcher.tempVar.macro.matchedCount="Matched count"
AdvSceneSwitcher.tempVar.macro.matchedCount.description="The number of macros of which the condition state was true."
AdvSceneSwitcher.tempVar.process.name="Process name"
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.video.patternCount="Pattern count"
AdvSceneSwitcher.tempVar.video.patternCount.description="The number of times the given pattern has been found in a given video input frame."
AdvSceneSwitcher.tempVar.video.objectCount="Object count"
AdvSceneSwitcher.tempVar.video.objectCount.description="The number of objects the given model has identified in a given video input frame."
AdvSceneSwitcher.tempVar.video.brightness="Average brightness"
AdvSceneSwitcher.tempVar.video.brightness.description="The average brightness in a given video input frame in a range from 0 to 1 (dark to bright)."
AdvSceneSwitcher.tempVar.video.text="OCR text"
AdvSceneSwitcher.tempVar.video.text.description="The text detected in a given video input frame."
AdvSceneSwitcher.tempVar.video.color="Average color"
AdvSceneSwitcher.tempVar.video.color.description="The average RGB color in a given video input frame in HexArgb format."
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene"

View File

@@ -129,7 +129,6 @@ AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFil
AdvSceneSwitcher.condition.media="Medios"
AdvSceneSwitcher.condition.media.anyOnScene="Cualquier fuente multimedia activada"
AdvSceneSwitcher.condition.media.allOnScene="Todas las fuentes de medios activadas"
AdvSceneSwitcher.condition.media.matchOnChange="Solo coincidir con el cambio (Nota: esta opción se eliminará en una versión futura; use modificadores de duración en su lugar)"
AdvSceneSwitcher.condition.media.inconsistencyInfo="Desafortunadamente, no todos los tipos de fuentes de medios se comportan de la misma manera (p. ej., fuente de medios frente a estado \"Detenido\" de fuente de video VLC).\n¡Así que experimente lo que funciona para su configuración!"
AdvSceneSwitcher.condition.media.entry="El estado de{{sourceTypes}}{{mediaSources}}{{scenes}}es{{states}}y{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Video"
@@ -182,9 +181,8 @@ AdvSceneSwitcher.condition.record="Grabación"
AdvSceneSwitcher.condition.record.state.start="Grabación en ejecución"
AdvSceneSwitcher.condition.record.state.pause="Grabación en pausa"
AdvSceneSwitcher.condition.record.state.stop="Grabación detenida"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.process="Proceso"
AdvSceneSwitcher.condition.process.entry="{{processes}} se está ejecutando {{focused}} y está enfocado"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}se está ejecutando{{focused}}y está enfocado"
AdvSceneSwitcher.condition.idle="Inactivo"
AdvSceneSwitcher.condition.idle.entry="No hay entradas de teclado o ratón durante {{duration}}"
AdvSceneSwitcher.condition.pluginState="Estado del complemento"

View File

@@ -197,7 +197,6 @@ AdvSceneSwitcher.condition.media="Média"
AdvSceneSwitcher.condition.media.source="Source"
AdvSceneSwitcher.condition.media.anyOnScene="Toute source média sur la scène"
AdvSceneSwitcher.condition.media.allOnScene="Toutes les sources média sur la scène"
AdvSceneSwitcher.condition.media.matchOnChange="Ne correspondre qu'au changement (Remarque : Cette option sera supprimée dans une version future - veuillez plutôt utiliser les modificateurs de durée)"
AdvSceneSwitcher.condition.media.inconsistencyInfo="Malheureusement, toutes les sources média ne se comportent pas de la même manière (par exemple, l'état \"Arrêté\" de la source Media par rapport à la source Vidéo VLC).\nPar conséquent, veuillez expérimenter ce qui fonctionne le mieux pour votre configuration !"
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}l'état est{{states}}et{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Vidéo"
@@ -286,7 +285,7 @@ AdvSceneSwitcher.condition.record.state.start="Enregistrement en cours"
AdvSceneSwitcher.condition.record.state.pause="Enregistrement en pause"
AdvSceneSwitcher.condition.record.state.stop="Arrêt de l'enregistrement"
AdvSceneSwitcher.condition.process="Processus"
AdvSceneSwitcher.condition.process.entry="{{processes}}en cours d'exécution{{focused}}et est au premier plan"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}en cours d'exécution{{focused}}et est au premier plan"
AdvSceneSwitcher.condition.process.entry.focus="Processus au premier plan actuel :{{focusProcess}}"
AdvSceneSwitcher.condition.idle="Inactif"
AdvSceneSwitcher.condition.idle.entry="Aucune entrée de clavier ou de souris pendant{{duration}}"
@@ -736,11 +735,10 @@ AdvSceneSwitcher.action.sceneLock.type.unlock="déverrouiller"
AdvSceneSwitcher.action.sceneLock.type.toggle="basculer le verrouillage de"
AdvSceneSwitcher.action.sceneLock.entry="Sur{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.twitch="Twitch"
AdvSceneSwitcher.action.twitch.type.title="Définir le titre du flux"
AdvSceneSwitcher.action.twitch.type.category="Définir la catégorie du flux"
AdvSceneSwitcher.action.twitch.type.commercial="Démarrer une publicité d'une durée de"
AdvSceneSwitcher.action.twitch.type.channel.info.title.set="Définir le titre du flux"
AdvSceneSwitcher.action.twitch.type.channel.info.category.set="Définir la catégorie du flux"
AdvSceneSwitcher.action.twitch.type.commercial.start="Démarrer une publicité d'une durée de"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Impossible de sélectionner une catégorie sans avoir d'abord sélectionné un compte Twitch !"
AdvSceneSwitcher.action.twitch.entry.line1="Sur{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Transition"

View File

@@ -107,7 +107,7 @@ AdvSceneSwitcher.condition.record.state.start="Запись запущена"
AdvSceneSwitcher.condition.record.state.pause="Запись приостановлена"
AdvSceneSwitcher.condition.record.state.stop="Запись остановлена"
AdvSceneSwitcher.condition.process="Процесс"
AdvSceneSwitcher.condition.process.entry="{{processes}} запущен {{focused}} и сфокусирован"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}запущен{{focused}}и сфокусирован"
AdvSceneSwitcher.condition.idle="Простой"
AdvSceneSwitcher.condition.idle.entry="Не было ни клавиатуры, ни мыши в течении{{duration}}"
AdvSceneSwitcher.condition.pluginState="Состояние плагина"

View File

@@ -119,7 +119,6 @@ AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFil
AdvSceneSwitcher.condition.media="Medya"
AdvSceneSwitcher.condition.media.anyOnScene="Herhangi bir medya kaynağı"
AdvSceneSwitcher.condition.media.allOnScene="Tüm medya kaynakları "
AdvSceneSwitcher.condition.media.matchOnChange="Yalnızca değişiklikle eşleştirin (Not: Bu seçenek gelecekteki bir sürümde kaldırılacaktır - lütfen bunun yerine zaman kısıtlamalarını kullanın)"
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}durumu{{states}}ve{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Video"
AdvSceneSwitcher.condition.video.condition.match="Tam olarak eşleşir"
@@ -159,9 +158,8 @@ AdvSceneSwitcher.condition.record="Kayıt"
AdvSceneSwitcher.condition.record.state.start="Kayıt Çalışıyor"
AdvSceneSwitcher.condition.record.state.pause="Kayıt durakladı"
AdvSceneSwitcher.condition.record.state.stop="Kayıt durdu"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.process="İşlem"
AdvSceneSwitcher.condition.process.entry="{{processes}} çalışıyor {{focused}} ve odaklandı"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}çalışıyor{{focused}}ve odaklandı"
AdvSceneSwitcher.condition.idle="Boşta"
AdvSceneSwitcher.condition.idle.entry="...için klavye veya fare girişi yok {{duration}}"
AdvSceneSwitcher.condition.pluginState="Eklenti durumu"

View File

@@ -185,7 +185,6 @@ AdvSceneSwitcher.condition.media="媒体"
AdvSceneSwitcher.condition.media.source="源"
AdvSceneSwitcher.condition.media.anyOnScene="任何媒体源"
AdvSceneSwitcher.condition.media.allOnScene="所有媒体源"
AdvSceneSwitcher.condition.media.matchOnChange="仅在更改时匹配(注意:此选项将在未来版本中删除-请改用时间限制)"
AdvSceneSwitcher.condition.media.inconsistencyInfo="不幸的是并非所有媒体源类型的行为都相同例如媒体源与VLC视频源“停止”状态。\n所以请尝试适合您的设置"
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}} 的状态是 {{states}} 和 {{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="视频"
@@ -267,9 +266,8 @@ AdvSceneSwitcher.condition.record="录制"
AdvSceneSwitcher.condition.record.state.start="录制运行中"
AdvSceneSwitcher.condition.record.state.pause="录制暂停"
AdvSceneSwitcher.condition.record.state.stop="录制停止"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.process="进程"
AdvSceneSwitcher.condition.process.entry="{{processes}} 是正在运行 {{focused}} 并且是获得焦点"
AdvSceneSwitcher.condition.process.entry="{{processes}}{{regex}}是正在运行{{focused}}并且是获得焦点"
AdvSceneSwitcher.condition.process.entry.focus="当前焦点进程: {{focusProcess}}"
AdvSceneSwitcher.condition.idle="闲置检测"
AdvSceneSwitcher.condition.idle.entry="没有键盘或鼠标输入{{duration}}"
@@ -651,7 +649,6 @@ AdvSceneSwitcher.action.variable.invalidSelection="无效选择!"
AdvSceneSwitcher.action.variable.actionNoVariableSupport="不支持从 %1 个操作获取变量值!"
AdvSceneSwitcher.action.variable.conditionNoVariableSupport="不支持从 %1 条件中获取变量值!"
AdvSceneSwitcher.action.variable.currentSegmentValue="当前值:"
AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}"
AdvSceneSwitcher.action.variable.entry.substringIndex="子字符串开始:{{subStringStart}} 子字符串大小:{{subStringSize}}"
AdvSceneSwitcher.action.variable.entry.substringRegex="使用正则表达式为 {{regexMatchIdx}} 匹配的值:"
AdvSceneSwitcher.action.variable.entry.findAndReplace="{{findStr}}{{replaceStr}}"

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet" version="1.1">
<!-- Created with SVG-edit - https://github.com/SVG-Edit/svgedit-->
<g class="layer">
<title>Layer 1</title>
<rect fill="#fefefe" height="2.25" id="svg_1" stroke="#fefefe" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" transform="matrix(1, 0, 0, 1, 0, 0)" width="13" x="1.5" y="4"/>
<rect fill="#fefefe" height="2.25" id="svg_2" stroke="#fefefe" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" transform="matrix(1, 0, 0, 1, 0, 0)" width="13" x="1.5" y="10"/>
<rect fill="#fefefe" height="1" id="svg_4" stroke="#fefefe" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" transform="matrix(0.341951, -0.926734, 0.939718, 0.337226, -1.948, 13.2448)" width="14" x="1" y="7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 931 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet" version="1.1">
<!-- Created with SVG-edit - https://github.com/SVG-Edit/svgedit-->
<g class="layer">
<title>Layer 1</title>
<rect fill="#202020" height="2.25" id="svg_1" stroke="#202020" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" transform="matrix(1, 0, 0, 1, 0, 0)" width="13" x="1.5" y="4"/>
<rect fill="#202020" height="2.25" id="svg_2" stroke="#202020" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" transform="matrix(1, 0, 0, 1, 0, 0)" width="13" x="1.5" y="10"/>
<rect fill="#202020" height="1" id="svg_4" stroke="#202020" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" transform="matrix(0.341951, -0.926734, 0.939718, 0.337226, -1.948, 13.2448)" width="14" x="1" y="7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 931 B

View File

@@ -119,7 +119,7 @@
<string notr="true">ms</string>
</property>
<property name="minimum">
<number>50</number>
<number>10</number>
</property>
<property name="maximum">
<number>20000</number>
@@ -1275,6 +1275,22 @@
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="toggleElseActions">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>

View File

@@ -1,5 +1,6 @@
#include "advanced-scene-switcher.hpp"
#include "switcher-data.hpp"
#include "macro-helpers.hpp"
#include "status-control.hpp"
#include "scene-switch-helpers.hpp"
#include "curl-helper.hpp"
@@ -10,6 +11,7 @@
#include <QMainWindow>
#include <QAction>
#include <QFileDialog>
#include <QTextStream>
#include <QDirIterator>
#include <regex>
#include <filesystem>
@@ -115,10 +117,27 @@ void AdvSceneSwitcher::LoadUI()
loading = false;
}
bool AdvSceneSwitcher::eventFilter(QObject *obj, QEvent *event)
{
auto eventType = event->type();
if (obj == ui->macroElseActions && eventType == QEvent::Resize) {
QResizeEvent *resizeEvent = static_cast<QResizeEvent *>(event);
if (resizeEvent->size().height() == 0) {
SetElseActionsStateToHidden();
return QDialog::eventFilter(obj, event);
}
SetElseActionsStateToVisible();
}
return QDialog::eventFilter(obj, event);
}
/******************************************************************************
* Saving and loading
******************************************************************************/
static void AskForBackup(obs_data_t *obj);
static void AskForBackup(const QString &json);
static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
{
@@ -139,16 +158,25 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
switcher->Stop();
switcher->m.lock();
obs_data_t *obj =
OBSDataAutoRelease obj =
obs_data_get_obj(save_data, "advanced-scene-switcher");
if (!obj) {
obj = obs_data_create();
}
if (switcher->VersionChanged(obj, g_GIT_SHA1)) {
AskForBackup(obj);
auto json = obs_data_get_json(obj);
static QString jsonQString = json ? json : "";
std::thread t([]() {
obs_queue_task(
OBS_TASK_UI,
[](void *) {
AskForBackup(jsonQString);
},
nullptr, false);
});
t.detach();
}
switcher->LoadSettings(obj);
obs_data_release(obj);
switcher->m.unlock();
if (!switcher->stop) {
@@ -157,12 +185,12 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
}
}
static void AskForBackup(obs_data_t *obj)
static void AskForBackup(const QString &json)
{
bool backupSettings = DisplayMessage(
obs_module_text("AdvSceneSwitcher.askBackup"), true);
const bool backupWasConfirmed = DisplayMessage(
obs_module_text("AdvSceneSwitcher.askBackup"), true, false);
if (!backupSettings) {
if (!backupWasConfirmed) {
return;
}
@@ -181,8 +209,8 @@ static void AskForBackup(obs_data_t *obj)
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return;
}
obs_data_save_json(obj, file.fileName().toUtf8().constData());
auto out = QTextStream(&file);
out << json;
}
/******************************************************************************
@@ -209,7 +237,6 @@ void SwitcherData::Thread()
// if a longer transition is used than the configured check interval
bool setPrevSceneAfterLinger = false;
bool macroMatch = false;
macroSceneSwitched = false;
endTime = std::chrono::high_resolution_clock::now();
auto runTime =
std::chrono::duration_cast<std::chrono::milliseconds>(
@@ -320,22 +347,12 @@ void SwitcherData::SetPreconditions()
// Process name
GetForegroundProcessName(currentForegroundProcess);
// Cursor
std::pair<int, int> cursorPos = GetCursorPos();
cursorPosChanged = cursorPos.first != switcher->lastCursorPos.first ||
cursorPos.second != switcher->lastCursorPos.second;
lastCursorPos = GetCursorPos();
// Macro
InvalidateMacroTempVarValues();
}
void ClearWebsocketMessages();
void SwitcherData::ResetForNextInterval()
{
// Core reset functions
ClearWebsocketMessages();
// Plugin reset functions
for (const auto &func : resetIntervalSteps) {
func();
@@ -411,7 +428,7 @@ bool SwitcherData::CheckForMatch(OBSWeakSource &scene,
static void ResetMacros()
{
for (auto &m : switcher->macros) {
for (auto &m : GetMacros()) {
m->ResetRunCount();
m->ResetTimers();
}
@@ -454,9 +471,9 @@ void SwitcherData::Stop()
if (th && th->isRunning()) {
stop = true;
cv.notify_all();
abortMacroWait = true;
macroWaitCv.notify_all();
macroTransitionCv.notify_all();
SetMacroAbortWait(true);
GetMacroWaitCV().notify_all();
GetMacroTransitionCV().notify_all();
// Not waiting if a dialog was closed is a workaround to avoid
// deadlocks when a variable input dialog is opened while Stop()
@@ -529,9 +546,6 @@ extern "C" void FreeSceneSwitcher()
static void handleSceneChange()
{
switcher->lastSceneChangeTime =
std::chrono::high_resolution_clock::now();
// Stop waiting if scene was changed
if (switcher->SceneChangedDuringWait()) {
switcher->cv.notify_one();
@@ -598,28 +612,9 @@ static void handlePeviewSceneChange()
}
}
static void setReplayBufferSaved()
static void handleTransitionEnd()
{
switcher->replayBufferSaved = true;
}
static void setTranstionEnd()
{
switcher->lastTransitionEndTime =
std::chrono::high_resolution_clock::now();
switcher->macroTransitionCv.notify_all();
}
static void setStreamStarting()
{
switcher->lastStreamStartingTime =
std::chrono::high_resolution_clock::now();
}
static void setStreamStopping()
{
switcher->lastStreamStoppingTime =
std::chrono::high_resolution_clock::now();
GetMacroTransitionCV().notify_all();
}
static void handleShutdown()
@@ -628,10 +623,10 @@ static void handleShutdown()
return;
}
switcher->obsIsShuttingDown = true;
if (switcher->shutdownConditionCount) {
if (ShutdownCheckIsNecessary()) {
switcher->Stop();
switcher->CheckMacros();
switcher->RunMacros();
CheckMacros();
RunMacros();
// Unfortunately this will not work as OBS will now allow saving
// the scene collection data at this point, So any OBS specific
@@ -688,19 +683,8 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
case OBS_FRONTEND_EVENT_STREAMING_STOPPED:
resetLiveTime();
break;
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(26, 0, 0)
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED:
setReplayBufferSaved();
break;
#endif
case OBS_FRONTEND_EVENT_TRANSITION_STOPPED:
setTranstionEnd();
break;
case OBS_FRONTEND_EVENT_STREAMING_STARTING:
setStreamStarting();
break;
case OBS_FRONTEND_EVENT_STREAMING_STOPPING:
setStreamStopping();
handleTransitionEnd();
break;
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 2, 0)
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING:
@@ -759,6 +743,9 @@ void OpenSettingsWindow()
}
}
void SetupConnectionManager();
void SetupWebsocketHelpers();
extern "C" void InitSceneSwitcher(obs_module_t *module, translateFunc translate)
{
blog(LOG_INFO, "version: %s", g_GIT_TAG);
@@ -769,6 +756,8 @@ extern "C" void InitSceneSwitcher(obs_module_t *module, translateFunc translate)
PlatformInit();
LoadPlugins();
SetupDock();
SetupConnectionManager();
SetupWebsocketHelpers();
obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr);
obs_frontend_add_event_callback(OBSEvent, switcher);

View File

@@ -39,6 +39,9 @@ public:
void RestoreWindowGeo();
void CheckFirstTimeSetup();
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
/* --- Begin of general tab section --- */
public:
void SetupGeneralTab();
@@ -115,6 +118,7 @@ public slots:
void on_actionUp_clicked();
void on_actionDown_clicked();
void on_actionBottom_clicked();
void on_toggleElseActions_clicked();
void on_elseActionAdd_clicked();
void on_elseActionRemove_clicked();
void on_elseActionTop_clicked();
@@ -146,6 +150,8 @@ public slots:
void MaximizeElseActions();
void MinimizeConditions();
void MaximizeConditions();
void SetElseActionsStateToHidden();
void SetElseActionsStateToVisible();
void MacroActionSelectionChanged(int idx);
void MacroActionReorder(int to, int target);
void AddMacroAction(int idx);
@@ -334,7 +340,7 @@ public slots:
void on_timeUp_clicked();
void on_timeDown_clicked();
// Video tab
// Audio tab
public:
void SetupAudioTab();
public slots:

View File

@@ -3,6 +3,7 @@
#include "status-control.hpp"
#include "file-selection.hpp"
#include "filter-combo-box.hpp"
#include "variable.hpp"
#include "utility.hpp"
#include "version.h"
@@ -33,7 +34,7 @@ void AdvSceneSwitcher::on_noMatchDontSwitch_clicked()
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->switchIfNotMatching = SwitcherData::NoMatch::NO_SWITCH;
switcher->switchIfNotMatching = NoMatchBehavior::NO_SWITCH;
ui->noMatchSwitchScene->setEnabled(false);
ui->randomDisabledWarning->setVisible(true);
}
@@ -45,7 +46,7 @@ void AdvSceneSwitcher::on_noMatchSwitch_clicked()
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->switchIfNotMatching = SwitcherData::NoMatch::SWITCH;
switcher->switchIfNotMatching = NoMatchBehavior::SWITCH;
ui->noMatchSwitchScene->setEnabled(true);
UpdateNonMatchingScene(ui->noMatchSwitchScene->currentText());
ui->randomDisabledWarning->setVisible(true);
@@ -58,7 +59,7 @@ void AdvSceneSwitcher::on_noMatchRandomSwitch_clicked()
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->switchIfNotMatching = SwitcherData::NoMatch::RANDOM_SWITCH;
switcher->switchIfNotMatching = NoMatchBehavior::RANDOM_SWITCH;
ui->noMatchSwitchScene->setEnabled(false);
ui->randomDisabledWarning->setVisible(false);
}
@@ -506,13 +507,13 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
// selections to be available.
loadSceneGroups(obj);
LoadVariables(obj);
LoadConnections(obj);
for (const auto &func : loadSteps) {
func(obj);
}
LoadMacros(obj);
LoadGlobalMacroProperties(obj);
loadWindowTitleSwitches(obj);
loadScreenRegionSwitches(obj);
loadPauseSwitches(obj);
@@ -549,7 +550,7 @@ void SwitcherData::SaveSettings(obs_data_t *obj)
saveSceneGroups(obj);
SaveMacros(obj);
SaveConnections(obj);
SaveGlobalMacroProperties(obj);
SaveVariables(obj);
saveWindowTitleSwitches(obj);
saveScreenRegionSwitches(obj);
@@ -624,9 +625,9 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
interval = obs_data_get_int(obj, "interval");
obs_data_set_default_int(obj, "switch_if_not_matching",
static_cast<int>(NoMatch::NO_SWITCH));
switchIfNotMatching =
(NoMatch)obs_data_get_int(obj, "switch_if_not_matching");
static_cast<int>(NoMatchBehavior::NO_SWITCH));
switchIfNotMatching = static_cast<NoMatchBehavior>(
obs_data_get_int(obj, "switch_if_not_matching"));
std::string nonMatchingSceneName =
obs_data_get_string(obj, "non_matching_scene");
nonMatchingScene = GetWeakSourceByName(nonMatchingSceneName.c_str());
@@ -800,12 +801,13 @@ void SwitcherData::CheckNoMatchSwitch(bool &match, OBSWeakSource &scene,
return;
}
if (switchIfNotMatching == NoMatch::SWITCH && nonMatchingScene) {
if (switchIfNotMatching == NoMatchBehavior::SWITCH &&
nonMatchingScene) {
match = true;
scene = nonMatchingScene;
transition = nullptr;
}
if (switchIfNotMatching == NoMatch::RANDOM_SWITCH) {
if (switchIfNotMatching == NoMatchBehavior::RANDOM_SWITCH) {
match = checkRandom(scene, transition, sleep);
}
}
@@ -956,11 +958,11 @@ void AdvSceneSwitcher::SetupGeneralTab()
{
PopulateSceneSelection(ui->noMatchSwitchScene, false);
if (switcher->switchIfNotMatching == SwitcherData::NoMatch::SWITCH) {
if (switcher->switchIfNotMatching == NoMatchBehavior::SWITCH) {
ui->noMatchSwitch->setChecked(true);
ui->noMatchSwitchScene->setEnabled(true);
} else if (switcher->switchIfNotMatching ==
SwitcherData::NoMatch::NO_SWITCH) {
NoMatchBehavior::NO_SWITCH) {
ui->noMatchDontSwitch->setChecked(true);
ui->noMatchSwitchScene->setEnabled(false);
} else {

View File

@@ -160,7 +160,9 @@ enum class HotkeyType {
Key_NumpadMultiply,
Key_NumpadDivide,
Key_NumpadDecimal,
Key_NumpadEnter
Key_NumpadEnter,
Key_Tilde,
};
} // namespace advss

View File

@@ -12,6 +12,11 @@ namespace advss {
static QMetaObject::Connection addPulse;
SceneGroupEditWidget *typeEdit = nullptr;
std::deque<SceneGroup> &GetSceneGroups()
{
return switcher->sceneGroups;
}
void SceneGroup::advanceIdx()
{
currentIdx++;

View File

@@ -1,5 +1,5 @@
#pragma once
#include <deque>
#include <vector>
#include <QDialog>
#include <QLabel>
@@ -87,5 +87,6 @@ private:
SceneGroup *GetSceneGroupByName(const char *name);
SceneGroup *GetSceneGroupByQString(const QString &name);
std::deque<SceneGroup> &GetSceneGroups();
} // namespace advss

View File

@@ -177,17 +177,17 @@ static std::string getRemoteData(std::string &url)
{
std::string readBuffer;
switcher->curl.SetOpt(CURLOPT_URL, url.c_str());
switcher->curl.SetOpt(CURLOPT_WRITEFUNCTION, WriteCallback);
switcher->curl.SetOpt(CURLOPT_WRITEDATA, &readBuffer);
CurlHelper::SetOpt(CURLOPT_URL, url.c_str());
CurlHelper::SetOpt(CURLOPT_WRITEFUNCTION, WriteCallback);
CurlHelper::SetOpt(CURLOPT_WRITEDATA, &readBuffer);
// Set timeout to at least one second
int timeout = switcher->interval / 1000;
if (timeout == 0) {
timeout = 1;
}
switcher->curl.SetOpt(CURLOPT_TIMEOUT, 1);
switcher->curl.Perform();
CurlHelper::SetOpt(CURLOPT_TIMEOUT, 1);
CurlHelper::Perform();
return readBuffer;
}
@@ -422,7 +422,7 @@ void AdvSceneSwitcher::SetupFileTab()
obs_module_text("AdvSceneSwitcher.fileTab.remoteFileWarning2"));
ui->remoteFileWarningLabel->hide();
if (switcher->curl.Initialized()) {
if (CurlHelper::Initialized()) {
ui->libcurlWarning->setVisible(false);
}

View File

@@ -143,8 +143,7 @@ void AdvSceneSwitcher::SetupRandomTab()
border-radius: 7px; \
border-color: rgb(0,0,0,0) \
}");
if (switcher->switchIfNotMatching !=
SwitcherData::NoMatch::RANDOM_SWITCH) {
if (switcher->switchIfNotMatching != NoMatchBehavior::RANDOM_SWITCH) {
if (!switcher->disableHints) {
PulseWidget(ui->randomDisabledWarning, QColor(Qt::red),
QColor(0, 0, 0, 0));

View File

@@ -1,5 +1,6 @@
#include "platform-funcs.hpp"
#include "hotkey.hpp"
#include "log-helper.hpp"
#include <X11/Xlib.h>
#include <X11/Xatom.h>
@@ -25,9 +26,10 @@
#include <QStringList>
#include <QRegularExpression>
#include <QLibrary>
#ifdef USE_PROCPS
#ifdef PROCPS_AVAILABLE
#include <proc/readproc.h>
#else
#endif
#ifdef PROCPS2_AVAILABLE
#include <libproc2/pids.h>
#endif
#include <fstream>
@@ -53,6 +55,31 @@ std::chrono::high_resolution_clock::time_point lastMouseLeftClickTime{};
std::chrono::high_resolution_clock::time_point lastMouseMiddleClickTime{};
std::chrono::high_resolution_clock::time_point lastMouseRightClickTime{};
static QLibrary *libprocps = nullptr;
#ifdef PROCPS_AVAILABLE
typedef PROCTAB *(*openproc_func)(int flags);
typedef void (*closeproc_func)(PROCTAB *PT);
typedef proc_t *(*readproc_func)(PROCTAB *__restrict const PT,
proc_t *__restrict p);
static openproc_func openproc_ = nullptr;
static closeproc_func closeproc_ = nullptr;
static readproc_func readproc_ = nullptr;
#endif
static bool libprocpsSupported = false;
static QLibrary *libproc2 = nullptr;
#ifdef PROCPS2_AVAILABLE
typedef int (*procps_pids_new_func)(struct pids_info **info,
enum pids_item *items, int numitems);
typedef struct pids_stack *(*procps_pids_get_func)(struct pids_info *info,
enum pids_fetch_type which);
typedef int (*procps_pids_unref_func)(struct pids_info **info);
static procps_pids_new_func procps_pids_new_ = nullptr;
static procps_pids_get_func procps_pids_get_ = nullptr;
static procps_pids_unref_func procps_pids_unref_ = nullptr;
#endif
static bool libprocps2Supported = false;
Display *disp()
{
if (!xdisplay) {
@@ -338,46 +365,57 @@ std::optional<std::string> GetTextInWindow(const std::string &)
return {};
}
#ifdef USE_PROCPS
void GetProcessList(QStringList &processes)
static void getProcessListProcps(QStringList &processes)
{
processes.clear();
PROCTAB *proc = openproc(PROC_FILLSTAT);
#ifdef PROCPS_AVAILABLE
PROCTAB *proc = openproc_(PROC_FILLSTAT);
proc_t proc_info;
memset(&proc_info, 0, sizeof(proc_info));
while (readproc(proc, &proc_info) != NULL) {
while (readproc_(proc, &proc_info) != NULL) {
QString procName(proc_info.cmd);
if (!procName.isEmpty() && !processes.contains(procName)) {
processes << procName;
}
}
closeproc(proc);
closeproc_(proc);
#endif
}
#else
void GetProcessList(QStringList &processes)
static void getProcessListProcps2(QStringList &processes)
{
processes.clear();
#ifdef PROCPS2_AVAILABLE
struct pids_info *info = NULL;
struct pids_stack *stack;
enum pids_item Items[] = {
PIDS_CMD,
};
if (procps_pids_new(&info, Items, sizeof(Items) / sizeof(Items[0])) <
if (procps_pids_new_(&info, Items, sizeof(Items) / sizeof(Items[0])) <
0) {
return;
}
while ((stack = procps_pids_get(info, PIDS_FETCH_TASKS_ONLY))) {
while ((stack = procps_pids_get_(info, PIDS_FETCH_TASKS_ONLY))) {
auto cmd = PIDS_VAL(0, str, stack, info);
QString procName(cmd);
if (!procName.isEmpty() && !processes.contains(procName)) {
processes << procName;
}
}
procps_pids_unref(&info);
}
procps_pids_unref_(&info);
#endif
}
void GetProcessList(QStringList &processes)
{
processes.clear();
if (libprocpsSupported) {
getProcessListProcps(processes);
return;
}
if (libprocps2Supported) {
getProcessListProcps2(processes);
}
}
long getForegroundProcessPid()
{
@@ -517,6 +555,9 @@ static std::unordered_map<HotkeyType, long> keyTable = {
{HotkeyType::Key_8, XK_8},
{HotkeyType::Key_9, XK_9},
// Other
{HotkeyType::Key_Tilde, XK_asciitilde},
{HotkeyType::Key_F1, XK_F1},
{HotkeyType::Key_F2, XK_F2},
{HotkeyType::Key_F3, XK_F3},
@@ -633,6 +674,58 @@ void PressKeys(const std::vector<HotkeyType> keys, int duration)
XFlush(display);
}
static void initXtst()
{
libXtstHandle = new QLibrary("libXtst", nullptr);
pressFunc = (keyPressFunc)libXtstHandle->resolve("XTestFakeKeyEvent");
int _;
canSimulateKeyPresses = pressFunc &&
XQueryExtension(disp(), "XTEST", &_, &_, &_);
}
static void initXss()
{
libXssHandle = new QLibrary("libXss", nullptr);
allocSSFunc = (XScreenSaverAllocInfoFunc)libXssHandle->resolve(
"XScreenSaverAllocInfo");
querySSFunc = (XScreenSaverQueryInfoFunc)libXssHandle->resolve(
"XScreenSaverQueryInfo");
int _;
canGetIdleTime = allocSSFunc && querySSFunc &&
XQueryExtension(disp(), ScreenSaverName, &_, &_, &_);
}
static void initProcps()
{
#ifdef PROCPS_AVAILABLE
libprocps = new QLibrary("libprocps", nullptr);
openproc_ = (openproc_func)libprocps->resolve("openproc");
closeproc_ = (closeproc_func)libprocps->resolve("closeproc");
readproc_ = (readproc_func)libprocps->resolve("readproc");
if (openproc_ && closeproc_ && readproc_) {
libprocpsSupported = true;
blog(LOG_INFO, "libprocps symbols resolved successfully!");
}
#endif
}
static void initProc2()
{
#ifdef PROCPS2_AVAILABLE
libproc2 = new QLibrary("libproc2", nullptr);
procps_pids_new_ =
(procps_pids_new_func)libproc2->resolve("procps_pids_new");
procps_pids_get_ =
(procps_pids_get_func)libproc2->resolve("procps_pids_get");
procps_pids_unref_ =
(procps_pids_unref_func)libproc2->resolve("procps_pids_unref");
if (procps_pids_new_ && procps_pids_get_ && procps_pids_unref_) {
libprocps2Supported = true;
blog(LOG_INFO, "libproc2 symbols resolved successfully!");
}
#endif
}
void PlatformInit()
{
auto display = disp();
@@ -640,31 +733,26 @@ void PlatformInit()
return;
}
libXtstHandle = new QLibrary("libXtst", nullptr);
pressFunc = (keyPressFunc)libXtstHandle->resolve("XTestFakeKeyEvent");
int _;
canSimulateKeyPresses = pressFunc &&
XQueryExtension(disp(), "XTEST", &_, &_, &_);
initXtst();
initXss();
initProcps();
initProc2();
}
libXssHandle = new QLibrary("libXss", nullptr);
allocSSFunc = (XScreenSaverAllocInfoFunc)libXssHandle->resolve(
"XScreenSaverAllocInfo");
querySSFunc = (XScreenSaverQueryInfoFunc)libXssHandle->resolve(
"XScreenSaverQueryInfo");
canGetIdleTime = allocSSFunc && querySSFunc &&
XQueryExtension(disp(), ScreenSaverName, &_, &_, &_);
static void cleanupHelper(QLibrary *lib)
{
if (lib) {
delete lib;
lib = nullptr;
}
}
void PlatformCleanup()
{
if (libXtstHandle) {
delete libXtstHandle;
libXtstHandle = nullptr;
}
if (libXssHandle) {
delete libXssHandle;
libXssHandle = nullptr;
}
cleanupHelper(libXtstHandle);
cleanupHelper(libXssHandle);
cleanupHelper(libprocps);
cleanupHelper(libproc2);
cleanupDisplay();
}

View File

@@ -1,5 +1,5 @@
#include "macro-action-audio.hpp"
#include "switcher-data.hpp"
#include "macro.hpp"
#include "utility.hpp"
namespace advss {
@@ -36,6 +36,16 @@ const static std::map<MacroActionAudio::FadeType, std::string> fadeTypes = {
"AdvSceneSwitcher.action.audio.fade.type.rate"},
};
namespace {
struct FadeInfo {
std::atomic_bool active = {false};
std::atomic_int id = {0};
};
}
static FadeInfo masterAudioFade;
static std::unordered_map<std::string, FadeInfo> audioFades;
constexpr auto fadeInterval = std::chrono::milliseconds(100);
constexpr float minFade = 0.000001f;
@@ -58,10 +68,9 @@ auto set_master_volume = obs_set_master_volume;
void MacroActionAudio::SetFadeActive(bool value)
{
if (_action == Action::SOURCE_VOLUME) {
switcher->activeAudioFades[_audioSource.ToString()].active =
value;
audioFades[_audioSource.ToString()].active = value;
} else {
switcher->masterAudioFade.active = value;
masterAudioFade.active = value;
}
}
@@ -69,14 +78,13 @@ bool MacroActionAudio::FadeActive()
{
bool active = true;
if (_action == Action::SOURCE_VOLUME) {
auto it = switcher->activeAudioFades.find(
_audioSource.ToString());
if (it == switcher->activeAudioFades.end()) {
auto it = audioFades.find(_audioSource.ToString());
if (it == audioFades.end()) {
return false;
}
active = it->second.active;
} else {
active = switcher->masterAudioFade.active;
active = masterAudioFade.active;
}
return active;
@@ -86,14 +94,13 @@ std::atomic_int *MacroActionAudio::GetFadeIdPtr()
{
if (_action == Action::SOURCE_VOLUME) {
auto it = switcher->activeAudioFades.find(
_audioSource.ToString());
if (it == switcher->activeAudioFades.end()) {
auto it = audioFades.find(_audioSource.ToString());
if (it == audioFades.end()) {
return nullptr;
}
return &it->second.id;
}
return &switcher->masterAudioFade.id;
return &masterAudioFade.id;
}
void MacroActionAudio::SetVolume(float vol)

View File

@@ -0,0 +1,256 @@
#include "macro-action-clipboard.hpp"
#include "curl-helper.hpp"
#include <QApplication>
#include <QClipboard>
#include <QImage>
#include <QMimeData>
namespace advss {
const std::string MacroActionClipboard::id = "clipboard";
bool MacroActionClipboard::_registered = MacroActionFactory::Register(
MacroActionClipboard::id,
{MacroActionClipboard::Create, MacroActionClipboardEdit::Create,
"AdvSceneSwitcher.action.clipboard"});
const static std::map<MacroActionClipboard::Action, std::string> actionTypes = {
{MacroActionClipboard::Action::COPY_TEXT,
"AdvSceneSwitcher.action.clipboard.type.copy.text"},
{MacroActionClipboard::Action::COPY_IMAGE,
"AdvSceneSwitcher.action.clipboard.type.copy.image"},
};
static size_t writeCallback(void *ptr, size_t size, size_t nmemb,
QByteArray *buffer)
{
buffer->append((char *)ptr, nmemb);
return size * nmemb;
}
static std::optional<QImage> getImageFromUrl(const char *url)
{
QByteArray response;
CurlHelper::SetOpt(CURLOPT_URL, url);
CurlHelper::SetOpt(CURLOPT_HTTPGET, 1L);
CurlHelper::SetOpt(CURLOPT_TIMEOUT_MS, 30000);
CurlHelper::SetOpt(CURLOPT_WRITEFUNCTION, writeCallback);
CurlHelper::SetOpt(CURLOPT_WRITEDATA, &response);
auto code = CurlHelper::Perform();
if (code != CURLE_OK) {
blog(LOG_WARNING,
"Retrieving image failed in %s with error: %s", __func__,
CurlHelper::GetError(code));
return {};
}
return QImage::fromData(response);
}
static void setMimeTypeParams(ClipboardQueueParams *params,
QClipboard *clipboard)
{
auto mimeData = clipboard->mimeData();
if (!mimeData) {
return;
}
auto mimeTypeList = mimeData->formats();
if (mimeTypeList.empty()) {
return;
}
params->mimeTypePrimary = mimeTypeList.first().toStdString();
params->mimeTypeAll = mimeTypeList.join(" ").toStdString();
}
static void copyText(void *param)
{
auto params = static_cast<ClipboardTextQueueParams *>(param);
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(params->text.c_str());
setMimeTypeParams(params, clipboard);
}
static void copyImageFromUrl(void *param)
{
auto params = static_cast<ClipboardImageQueueParams *>(param);
auto url = params->url.c_str();
auto image = getImageFromUrl(url);
if (!image || (*image).isNull()) {
blog(LOG_WARNING, "Failed to convert %s URL to image!", url);
return;
}
QClipboard *clipboard = QApplication::clipboard();
clipboard->setImage(*image);
setMimeTypeParams(params, clipboard);
}
bool MacroActionClipboard::PerformAction()
{
switch (_action) {
case Action::COPY_TEXT: {
ClipboardTextQueueParams params{"", "", _text};
obs_queue_task(OBS_TASK_UI, copyText, &params, true);
SetTempVarValues(params);
break;
}
case Action::COPY_IMAGE: {
ClipboardImageQueueParams params{"", "", _url};
obs_queue_task(OBS_TASK_UI, copyImageFromUrl, &params, true);
SetTempVarValues(params);
break;
}
default:
break;
}
return true;
}
void MacroActionClipboard::SetupTempVars()
{
MacroAction::SetupTempVars();
AddTempvar(
"mimeType.primary",
obs_module_text(
"AdvSceneSwitcher.tempVar.clipboard.mimeType.primary"),
obs_module_text(
"AdvSceneSwitcher.tempVar.clipboard.mimeType.primary.description"));
AddTempvar(
"mimeType.all",
obs_module_text(
"AdvSceneSwitcher.tempVar.clipboard.mimeType.all"),
obs_module_text(
"AdvSceneSwitcher.tempVar.clipboard.mimeType.all.description"));
}
void MacroActionClipboard::SetTempVarValues(const ClipboardQueueParams &params)
{
SetTempVarValue("mimeType.primary", params.mimeTypePrimary);
SetTempVarValue("mimeType.all", params.mimeTypeAll);
}
bool MacroActionClipboard::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
_text.Save(obj, "text");
_url.Save(obj, "url");
return true;
}
bool MacroActionClipboard::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
_text.Load(obj, "text");
_url.Load(obj, "url");
return true;
}
static inline void populateActionSelection(QComboBox *list)
{
for (const auto &[action, name] : actionTypes) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(action));
}
}
MacroActionClipboardEdit::MacroActionClipboardEdit(
QWidget *parent, std::shared_ptr<MacroActionClipboard> entryData)
: QWidget(parent),
_actions(new FilterComboBox(this)),
_text(new VariableLineEdit(this)),
_url(new VariableLineEdit(this))
{
populateActionSelection(_actions);
_url->setToolTip(obs_module_text(
"AdvSceneSwitcher.action.clipboard.copy.image.url.tooltip"));
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_text, SIGNAL(editingFinished()), this,
SLOT(TextChanged()));
QWidget::connect(_url, SIGNAL(editingFinished()), this,
SLOT(UrlChanged()));
auto mainLayout = new QHBoxLayout();
mainLayout->addWidget(_actions);
mainLayout->addWidget(_text);
mainLayout->addWidget(_url);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionClipboardEdit::ActionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_action = static_cast<MacroActionClipboard::Action>(
_actions->itemData(index).toInt());
SetWidgetVisibility();
}
void MacroActionClipboardEdit::TextChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_text = _text->text().toStdString();
}
void MacroActionClipboardEdit::UrlChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_url = _url->text().toStdString();
}
void MacroActionClipboardEdit::SetWidgetVisibility()
{
_text->setVisible(_entryData->_action ==
MacroActionClipboard::Action::COPY_TEXT);
_url->setVisible(_entryData->_action ==
MacroActionClipboard::Action::COPY_IMAGE);
adjustSize();
updateGeometry();
}
void MacroActionClipboardEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_actions->setCurrentIndex(
_actions->findData(static_cast<int>(_entryData->_action)));
_text->setText(_entryData->_text);
_url->setText(_entryData->_url);
SetWidgetVisibility();
}
} // namespace advss

View File

@@ -0,0 +1,89 @@
#pragma once
#include "macro-action-edit.hpp"
#include "filter-combo-box.hpp"
#include <variable-line-edit.hpp>
namespace advss {
struct ClipboardQueueParams {
std::string mimeTypePrimary;
std::string mimeTypeAll;
};
struct ClipboardTextQueueParams : ClipboardQueueParams {
StringVariable text;
};
struct ClipboardImageQueueParams : ClipboardQueueParams {
StringVariable url;
};
class MacroActionClipboard : public MacroAction {
public:
MacroActionClipboard(Macro *m) : MacroAction(m) {}
static std::shared_ptr<MacroAction> Create(Macro *m)
{
return std::make_shared<MacroActionClipboard>(m);
}
std::string GetId() const { return id; };
bool PerformAction();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
enum class Action {
COPY_TEXT,
COPY_IMAGE,
};
Action _action = Action::COPY_TEXT;
StringVariable _text = obs_module_text(
"AdvSceneSwitcher.action.clipboard.copy.text.text.placeholder");
StringVariable _url = obs_module_text(
"AdvSceneSwitcher.action.clipboard.copy.image.url.placeholder");
private:
void SetupTempVars();
void SetTempVarValues(const ClipboardQueueParams &params);
static bool _registered;
static const std::string id;
};
class MacroActionClipboardEdit : public QWidget {
Q_OBJECT
public:
MacroActionClipboardEdit(
QWidget *parent,
std::shared_ptr<MacroActionClipboard> entryData = nullptr);
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionClipboardEdit(
parent, std::dynamic_pointer_cast<MacroActionClipboard>(
action));
}
void UpdateEntryData();
protected:
std::shared_ptr<MacroActionClipboard> _entryData;
private slots:
void ActionChanged(int index);
void TextChanged();
void UrlChanged();
private:
void SetWidgetVisibility();
bool _loading = true;
FilterComboBox *_actions;
VariableLineEdit *_text;
VariableLineEdit *_url;
};
} // namespace advss

View File

@@ -1,6 +1,8 @@
#include "advanced-scene-switcher.hpp"
#include "switcher-data.hpp"
#include "macro-action-edit.hpp"
#include "macro-helpers.hpp"
#include "macro.hpp"
#include "macro-properties.hpp"
#include "advanced-scene-switcher.hpp"
#include "macro-action-scene-switch.hpp"
#include "section.hpp"
#include "switch-button.hpp"
@@ -10,61 +12,9 @@
namespace advss {
std::map<std::string, MacroActionInfo> &MacroActionFactory::GetMap()
{
static std::map<std::string, MacroActionInfo> _methods;
return _methods;
}
bool MacroActionFactory::Register(const std::string &id, MacroActionInfo info)
{
if (auto it = GetMap().find(id); it == GetMap().end()) {
GetMap()[id] = info;
return true;
}
return false;
}
std::shared_ptr<MacroAction> MacroActionFactory::Create(const std::string &id,
Macro *m)
{
if (auto it = GetMap().find(id); it != GetMap().end())
return it->second._createFunc(m);
return nullptr;
}
QWidget *MacroActionFactory::CreateWidget(const std::string &id,
QWidget *parent,
std::shared_ptr<MacroAction> action)
{
if (auto it = GetMap().find(id); it != GetMap().end())
return it->second._createWidgetFunc(parent, action);
return nullptr;
}
std::string MacroActionFactory::GetActionName(const std::string &id)
{
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._name;
}
return "unknown action";
}
std::string MacroActionFactory::GetIdByName(const QString &name)
{
for (auto it : GetMap()) {
if (name == obs_module_text(it.second._name.c_str())) {
return it.first;
}
}
return "";
}
static inline void populateActionSelection(QComboBox *list)
{
for (auto &[_, action] : MacroActionFactory::GetActionTypes()) {
for (const auto &[_, action] : MacroActionFactory::GetActionTypes()) {
QString entry(obs_module_text(action._name.c_str()));
if (list->findText(entry) == -1) {
list->addItem(entry);
@@ -80,7 +30,8 @@ static inline void populateActionSelection(QComboBox *list)
MacroActionEdit::MacroActionEdit(QWidget *parent,
std::shared_ptr<MacroAction> *entryData,
const std::string &id)
: MacroSegmentEdit(switcher->macroProperties._highlightActions, parent),
: MacroSegmentEdit(GetGlobalMacroProperties()._highlightActions,
parent),
_actionSelection(new FilterComboBox()),
_enable(new SwitchButton()),
_entryData(entryData)
@@ -134,7 +85,7 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
auto idx = _entryData->get()->GetIndex();
auto macro = _entryData->get()->GetMacro();
{
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
_entryData->reset();
*_entryData = MacroActionFactory::Create(id, macro);
(*_entryData)->SetIndex(idx);
@@ -185,7 +136,7 @@ void MacroActionEdit::ActionEnableChanged(bool value)
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
(*_entryData)->SetEnabled(value);
SetDisableEffect(!value);
}
@@ -229,7 +180,7 @@ void AdvSceneSwitcher::AddMacroAction(int idx)
id = temp.GetId();
}
{
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
macro->Actions().emplace(
macro->Actions().begin() + idx,
MacroActionFactory::Create(id, macro.get()));
@@ -280,11 +231,11 @@ void AdvSceneSwitcher::RemoveMacroAction(int idx)
}
{
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
ui->actionsList->Remove(idx);
macro->Actions().erase(macro->Actions().begin() + idx);
switcher->abortMacroWait = true;
switcher->macroWaitCv.notify_all();
SetMacroAbortWait(true);
GetMacroWaitCV().notify_all();
macro->UpdateActionIndices();
SetActionData(*macro);
}
@@ -425,7 +376,7 @@ void AdvSceneSwitcher::SwapActions(Macro *m, int pos1, int pos2)
std::swap(pos1, pos2);
}
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
iter_swap(m->Actions().begin() + pos1, m->Actions().begin() + pos2);
m->UpdateActionIndices();
auto widget1 = static_cast<MacroActionEdit *>(
@@ -485,7 +436,7 @@ void AdvSceneSwitcher::MacroElseActionReorder(int to, int from)
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
auto action = macro->ElseActions().at(from);
macro->ElseActions().erase(macro->ElseActions().begin() + from);
macro->ElseActions().insert(macro->ElseActions().begin() + to,
@@ -518,7 +469,7 @@ void AdvSceneSwitcher::AddMacroElseAction(int idx)
id = temp.GetId();
}
{
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
macro->ElseActions().emplace(
macro->ElseActions().begin() + idx,
MacroActionFactory::Create(id, macro.get()));
@@ -550,11 +501,11 @@ void AdvSceneSwitcher::RemoveMacroElseAction(int idx)
}
{
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
ui->elseActionsList->Remove(idx);
macro->ElseActions().erase(macro->ElseActions().begin() + idx);
switcher->abortMacroWait = true;
switcher->macroWaitCv.notify_all();
SetMacroAbortWait(true);
GetMacroWaitCV().notify_all();
macro->UpdateElseActionIndices();
SetActionData(*macro);
}
@@ -572,7 +523,7 @@ void AdvSceneSwitcher::SwapElseActions(Macro *m, int pos1, int pos2)
std::swap(pos1, pos2);
}
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
iter_swap(m->ElseActions().begin() + pos1,
m->ElseActions().begin() + pos2);
m->UpdateElseActionIndices();
@@ -635,7 +586,7 @@ void AdvSceneSwitcher::MacroActionReorder(int to, int from)
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
auto action = macro->Actions().at(from);
macro->Actions().erase(macro->Actions().begin() + from);
macro->Actions().insert(macro->Actions().begin() + to, action);

View File

@@ -1,5 +1,6 @@
#pragma once
#include "macro-action.hpp"
#include "macro-action-factory.hpp"
#include "filter-combo-box.hpp"
#include <memory>
@@ -8,32 +9,6 @@ namespace advss {
class SwitchButton;
struct MacroActionInfo {
using TCreateMethod = std::shared_ptr<MacroAction> (*)(Macro *m);
using TCreateWidgetMethod = QWidget *(*)(QWidget *parent,
std::shared_ptr<MacroAction>);
TCreateMethod _createFunc = nullptr;
TCreateWidgetMethod _createWidgetFunc = nullptr;
std::string _name;
};
class MacroActionFactory {
public:
MacroActionFactory() = delete;
static bool Register(const std::string &id, MacroActionInfo);
static std::shared_ptr<MacroAction> Create(const std::string &id,
Macro *m);
static QWidget *CreateWidget(const std::string &id, QWidget *parent,
std::shared_ptr<MacroAction> action);
static auto GetActionTypes() { return GetMap(); }
static std::string GetActionName(const std::string &id);
static std::string GetIdByName(const QString &name);
private:
static std::map<std::string, MacroActionInfo> &GetMap();
};
class MacroActionEdit : public MacroSegmentEdit {
Q_OBJECT

View File

@@ -0,0 +1,57 @@
#include "macro-action-factory.hpp"
namespace advss {
std::map<std::string, MacroActionInfo> &MacroActionFactory::GetMap()
{
static std::map<std::string, MacroActionInfo> _methods;
return _methods;
}
bool MacroActionFactory::Register(const std::string &id, MacroActionInfo info)
{
if (auto it = GetMap().find(id); it == GetMap().end()) {
GetMap()[id] = info;
return true;
}
return false;
}
std::shared_ptr<MacroAction> MacroActionFactory::Create(const std::string &id,
Macro *m)
{
if (auto it = GetMap().find(id); it != GetMap().end())
return it->second._create(m);
return nullptr;
}
QWidget *MacroActionFactory::CreateWidget(const std::string &id,
QWidget *parent,
std::shared_ptr<MacroAction> action)
{
if (auto it = GetMap().find(id); it != GetMap().end())
return it->second._createWidget(parent, action);
return nullptr;
}
std::string MacroActionFactory::GetActionName(const std::string &id)
{
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._name;
}
return "unknown action";
}
std::string MacroActionFactory::GetIdByName(const QString &name)
{
for (auto it : GetMap()) {
if (name == obs_module_text(it.second._name.c_str())) {
return it.first;
}
}
return "";
}
} // namespace advss

View File

@@ -0,0 +1,34 @@
#pragma once
#include "macro-action.hpp"
#include <memory>
namespace advss {
struct MacroActionInfo {
using CreateAction = std::shared_ptr<MacroAction> (*)(Macro *m);
using CreateActionWidget = QWidget *(*)(QWidget *parent,
std::shared_ptr<MacroAction>);
CreateAction _create = nullptr;
CreateActionWidget _createWidget = nullptr;
std::string _name;
};
class MacroActionFactory {
public:
MacroActionFactory() = delete;
static bool Register(const std::string &id, MacroActionInfo);
static std::shared_ptr<MacroAction> Create(const std::string &id,
Macro *m);
static QWidget *CreateWidget(const std::string &id, QWidget *parent,
std::shared_ptr<MacroAction> action);
static auto GetActionTypes() { return GetMap(); }
static std::string GetActionName(const std::string &id);
static std::string GetIdByName(const QString &name);
private:
static std::map<std::string, MacroActionInfo> &GetMap();
};
} // namespace advss

View File

@@ -424,11 +424,16 @@ void MacroActionFilterEdit::SetWidgetVisibility()
_entryData->_settingsInputMethod ==
MacroActionFilter::SettingsInputMethod::
INDIVIDUAL_TEMPVAR);
_manualSettingValue->setVisible(
_entryData->_action == MacroActionFilter::Action::SETTINGS &&
_entryData->_settingsInputMethod ==
MacroActionFilter::SettingsInputMethod::
INDIVIDUAL_MANUAL);
if (_entryData->_action == MacroActionFilter::Action::SETTINGS &&
_entryData->_settingsInputMethod ==
MacroActionFilter::SettingsInputMethod::INDIVIDUAL_MANUAL) {
RemoveStretchIfPresent(_settingsLayout);
_manualSettingValue->show();
} else {
AddStretchIfNecessary(_settingsLayout);
_manualSettingValue->hide();
}
adjustSize();
updateGeometry();

View File

@@ -14,14 +14,14 @@ bool MacroActionHotkey::_registered = MacroActionFactory::Register(
{MacroActionHotkey::Create, MacroActionHotkeyEdit::Create,
"AdvSceneSwitcher.action.hotkey"});
const static std::map<MacroActionHotkey::Action, std::string> actionTypes = {
static const std::map<MacroActionHotkey::Action, std::string> actionTypes = {
{MacroActionHotkey::Action::OBS_HOTKEY,
"AdvSceneSwitcher.action.hotkey.type.obs"},
{MacroActionHotkey::Action::CUSTOM,
"AdvSceneSwitcher.action.hotkey.type.custom"},
};
const static std::map<obs_hotkey_registerer_type, std::string> hotkeyTypes = {
static const std::map<obs_hotkey_registerer_type, std::string> hotkeyTypes = {
{OBS_HOTKEY_REGISTERER_FRONTEND,
"AdvSceneSwitcher.action.hotkey.type.obs.type.frontend"},
{OBS_HOTKEY_REGISTERER_SOURCE,
@@ -34,7 +34,7 @@ const static std::map<obs_hotkey_registerer_type, std::string> hotkeyTypes = {
"AdvSceneSwitcher.action.hotkey.type.obs.type.service"},
};
static std::unordered_map<HotkeyType, long> keyTable = {
static const std::unordered_map<HotkeyType, long> keyTable = {
// Chars
{HotkeyType::Key_A, OBS_KEY_A},
{HotkeyType::Key_B, OBS_KEY_B},
@@ -75,6 +75,9 @@ static std::unordered_map<HotkeyType, long> keyTable = {
{HotkeyType::Key_8, OBS_KEY_8},
{HotkeyType::Key_9, OBS_KEY_9},
// Other
{HotkeyType::Key_Tilde, OBS_KEY_ASCIITILDE},
{HotkeyType::Key_F1, OBS_KEY_F1},
{HotkeyType::Key_F2, OBS_KEY_F2},
{HotkeyType::Key_F3, OBS_KEY_F3},
@@ -525,6 +528,7 @@ static inline void populateKeySelection(QComboBox *list)
list->addItem("NumpadDivide");
list->addItem("NumpadDecimal");
list->addItem("NumpadEnter");
list->addItem("Tilde");
}
static void populateActionSelection(QComboBox *list)

View File

@@ -1,5 +1,4 @@
#include "macro-action-http.hpp"
#include "switcher-data.hpp"
#include "utility.hpp"
#include "curl-helper.hpp"
@@ -36,44 +35,44 @@ void MacroActionHttp::SetupHeaders()
}
struct curl_slist *headers = nullptr;
for (auto &header : _headers) {
headers = switcher->curl.SlistAppend(headers, header.c_str());
headers = CurlHelper::SlistAppend(headers, header.c_str());
}
if (!_headers.empty()) {
switcher->curl.SetOpt(CURLOPT_HTTPHEADER, headers);
CurlHelper::SetOpt(CURLOPT_HTTPHEADER, headers);
}
}
void MacroActionHttp::Get()
{
switcher->curl.SetOpt(CURLOPT_URL, _url.c_str());
switcher->curl.SetOpt(CURLOPT_HTTPGET, 1L);
switcher->curl.SetOpt(CURLOPT_TIMEOUT_MS, _timeout.Milliseconds());
CurlHelper::SetOpt(CURLOPT_URL, _url.c_str());
CurlHelper::SetOpt(CURLOPT_HTTPGET, 1L);
CurlHelper::SetOpt(CURLOPT_TIMEOUT_MS, _timeout.Milliseconds());
SetupHeaders();
std::string response;
if (IsReferencedInVars()) {
switcher->curl.SetOpt(CURLOPT_WRITEFUNCTION, WriteCB);
CurlHelper::SetOpt(CURLOPT_WRITEFUNCTION, WriteCB);
} else {
switcher->curl.SetOpt(CURLOPT_WRITEFUNCTION, DropCB);
CurlHelper::SetOpt(CURLOPT_WRITEFUNCTION, DropCB);
}
switcher->curl.SetOpt(CURLOPT_WRITEDATA, &response);
switcher->curl.Perform();
CurlHelper::SetOpt(CURLOPT_WRITEDATA, &response);
CurlHelper::Perform();
SetVariableValue(response);
}
void MacroActionHttp::Post()
{
switcher->curl.SetOpt(CURLOPT_URL, _url.c_str());
switcher->curl.SetOpt(CURLOPT_POSTFIELDS, _data.c_str());
switcher->curl.SetOpt(CURLOPT_TIMEOUT_MS, _timeout.Milliseconds());
CurlHelper::SetOpt(CURLOPT_URL, _url.c_str());
CurlHelper::SetOpt(CURLOPT_POSTFIELDS, _data.c_str());
CurlHelper::SetOpt(CURLOPT_TIMEOUT_MS, _timeout.Milliseconds());
SetupHeaders();
switcher->curl.Perform();
CurlHelper::Perform();
}
bool MacroActionHttp::PerformAction()
{
if (!switcher->curl.Initialized()) {
if (!CurlHelper::Initialized()) {
blog(LOG_WARNING,
"cannot perform http action (curl not found)");
return true;

View File

@@ -1,8 +1,9 @@
#include "macro-action-plugin-state.hpp"
#include "switcher-data.hpp"
#include "plugin-state-helpers.hpp"
#include "utility.hpp"
#include <thread>
#include <condition_variable>
#include <QMainWindow>
using namespace std::chrono_literals;
@@ -27,40 +28,38 @@ const static std::map<PluginStateAction, std::string> actionTypes = {
"AdvSceneSwitcher.action.pluginState.type.terminate"},
};
const static std::map<SwitcherData::NoMatch, std::string> noMatchValues = {
{SwitcherData::NoMatch::NO_SWITCH,
const static std::map<NoMatchBehavior, std::string> noMatchValues = {
{NoMatchBehavior::NO_SWITCH,
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch"},
{SwitcherData::NoMatch::SWITCH,
{NoMatchBehavior::SWITCH,
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo"},
{SwitcherData::NoMatch::RANDOM_SWITCH,
{NoMatchBehavior::RANDOM_SWITCH,
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom"},
};
static void stopPlugin()
{
std::thread t([]() { switcher->Stop(); });
std::thread t([]() { StopPlugin(); });
t.detach();
}
static void importSettings(const std::string &path)
{
if (switcher->settingsWindowOpened) {
if (SettingsWindowIsOpened()) {
return;
}
obs_data_t *obj = obs_data_create_from_json_file(path.c_str());
OBSDataAutoRelease obj = obs_data_create_from_json_file(path.c_str());
if (!obj) {
return;
}
switcher->LoadSettings(obj);
obs_data_release(obj);
LoadPluginSettings(obj);
}
static void setNoMatchBehaviour(int value, OBSWeakSource &scene)
{
switcher->switchIfNotMatching =
static_cast<SwitcherData::NoMatch>(value);
if (switcher->switchIfNotMatching == SwitcherData::NoMatch::SWITCH) {
switcher->nonMatchingScene = scene;
SetPluginNoMatchBehavior(static_cast<NoMatchBehavior>(value));
if (GetPluginNoMatchBehavior() == NoMatchBehavior::SWITCH) {
SetNoMatchScene(scene);
}
}
@@ -342,8 +341,8 @@ void MacroActionPluginStateEdit::SetWidgetVisibility()
break;
case PluginStateAction::NO_MATCH_BEHAVIOUR:
_values->show();
if (static_cast<SwitcherData::NoMatch>(_entryData->_value) ==
SwitcherData::NoMatch::SWITCH) {
if (static_cast<NoMatchBehavior>(_entryData->_value) ==
NoMatchBehavior::SWITCH) {
_scenes->show();
}
break;

View File

@@ -14,9 +14,14 @@ bool MacroActionRun::_registered = MacroActionFactory::Register(
bool MacroActionRun::PerformAction()
{
bool procStarted = QProcess::startDetached(
QString::fromStdString(_procConfig.Path()), _procConfig.Args(),
QString::fromStdString(_procConfig.WorkingDir()));
if (_wait) {
_procConfig.StartProcessAndWait(_timeout.Milliseconds());
return true;
}
bool procStarted = _procConfig.StartProcessDetached();
// Fall back to using default application to open given file
if (!procStarted && _procConfig.Args().empty()) {
vblog(LOG_INFO, "run \"%s\" using QDesktopServices",
_procConfig.Path().c_str());
@@ -35,6 +40,9 @@ bool MacroActionRun::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
_procConfig.Save(obj);
_timeout.Save(obj);
obs_data_set_bool(obj, "wait", _wait);
obs_data_set_int(obj, "version", 1);
return true;
}
@@ -42,6 +50,12 @@ bool MacroActionRun::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_procConfig.Load(obj);
// TODO: Remove this fallback in a future version
if (!obs_data_has_user_value(obj, "version")) {
return true;
}
_timeout.Load(obj);
_wait = obs_data_get_bool(obj, "wait");
return true;
}
@@ -52,14 +66,30 @@ std::string MacroActionRun::GetShortDesc() const
MacroActionRunEdit::MacroActionRunEdit(
QWidget *parent, std::shared_ptr<MacroActionRun> entryData)
: QWidget(parent), _procConfig(new ProcessConfigEdit(this))
: QWidget(parent),
_procConfig(new ProcessConfigEdit(this)),
_waitLayout(new QHBoxLayout()),
_wait(new QCheckBox()),
_timeout(new DurationSelection(this, true, 0.1))
{
QWidget::connect(_procConfig,
SIGNAL(ConfigChanged(const ProcessConfig &)), this,
SLOT(ProcessConfigChanged(const ProcessConfig &)));
QWidget::connect(_procConfig, SIGNAL(AdvancedSettingsEnabled()), this,
SLOT(ProcessConfigAdvancedSettingsShown()));
QWidget::connect(_wait, SIGNAL(stateChanged(int)), this,
SLOT(WaitChanged(int)));
QWidget::connect(_timeout, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(TimeoutChanged(const Duration &)));
auto *layout = new QVBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.run.entry.wait"),
_waitLayout,
{{"{{wait}}", _wait}, {"{{timeout}}", _timeout}});
SetLayoutVisible(_waitLayout, false);
auto layout = new QVBoxLayout;
layout->addWidget(_procConfig);
layout->addLayout(_waitLayout);
setLayout(layout);
_entryData = entryData;
@@ -73,6 +103,31 @@ void MacroActionRunEdit::UpdateEntryData()
return;
}
_procConfig->SetProcessConfig(_entryData->_procConfig);
_wait->setChecked(_entryData->_wait);
_timeout->SetDuration(_entryData->_timeout);
}
void MacroActionRunEdit::ProcessConfigAdvancedSettingsShown()
{
SetLayoutVisible(_waitLayout, true);
}
void MacroActionRunEdit::WaitChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_wait = value;
}
void MacroActionRunEdit::TimeoutChanged(const Duration &timeout)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_timeout = timeout;
}
void MacroActionRunEdit::ProcessConfigChanged(const ProcessConfig &conf)

View File

@@ -1,6 +1,9 @@
#pragma once
#include "macro-action-edit.hpp"
#include "process-config.hpp"
#include "duration-control.hpp"
#include <QCheckBox>
namespace advss {
@@ -19,6 +22,8 @@ public:
}
ProcessConfig _procConfig;
bool _wait = false;
Duration _timeout = 1;
private:
static bool _registered;
@@ -42,14 +47,19 @@ public:
private slots:
void ProcessConfigChanged(const ProcessConfig &);
void ProcessConfigAdvancedSettingsShown();
void WaitChanged(int);
void TimeoutChanged(const Duration &);
signals:
void HeaderInfoChanged(const QString &);
protected:
std::shared_ptr<MacroActionRun> _entryData;
private:
ProcessConfigEdit *_procConfig;
QHBoxLayout *_waitLayout;
QCheckBox *_wait;
DurationSelection *_timeout;
std::shared_ptr<MacroActionRun> _entryData;
bool _loading = true;
};

View File

@@ -1,5 +1,5 @@
#include "macro-action-scene-collection.hpp"
#include "switcher-data.hpp"
#include "plugin-state-helpers.hpp"
#include "utility.hpp"
namespace advss {
@@ -17,7 +17,7 @@ bool MacroActionSceneCollection::PerformAction()
// Changing the scene collection will also reload the settings of the
// scene switcher, so to avoid issues of not being able to change
// settings ignore this action if the settings dialog is opened.
if (switcher->settingsWindowOpened) {
if (SettingsWindowIsOpened()) {
return false;
}
obs_frontend_set_current_scene_collection(_sceneCollection.c_str());

View File

@@ -1,5 +1,7 @@
#include "macro-action-scene-switch.hpp"
#include "switcher-data.hpp"
#include "macro-helpers.hpp"
#include "plugin-state-helpers.hpp"
#include "macro.hpp"
#include "scene-switch-helpers.hpp"
#include "utility.hpp"
@@ -7,7 +9,7 @@ namespace advss {
using namespace std::chrono_literals;
const std::string MacroActionSwitchScene::id = "scene_switch";
const std::string MacroActionSwitchScene::id = GetSceneSwitchActionId().data();
bool MacroActionSwitchScene::_registered = MacroActionFactory::Register(
MacroActionSwitchScene::id,
@@ -33,9 +35,9 @@ static void waitForTransitionChange(OBSWeakSource &transition,
}
bool stillTransitioning = true;
while (stillTransitioning && !switcher->abortMacroWait &&
while (stillTransitioning && !MacroWaitShouldAbort() &&
!macro->GetStop()) {
switcher->macroTransitionCv.wait_for(*lock, time);
GetMacroTransitionCV().wait_for(*lock, time);
float t = obs_transition_get_time(source);
stillTransitioning = t < 1.0f && t > 0.0f;
}
@@ -49,8 +51,8 @@ static void waitForTransitionChangeFixedDuration(
auto time = std::chrono::high_resolution_clock::now() +
std::chrono::milliseconds(duration);
while (!switcher->abortMacroWait && !macro->GetStop()) {
if (switcher->macroTransitionCv.wait_until(*lock, time) ==
while (!MacroWaitShouldAbort() && !macro->GetStop()) {
if (GetMacroTransitionCV().wait_until(*lock, time) ==
std::cv_status::timeout) {
break;
}
@@ -91,11 +93,16 @@ static OBSWeakSource getOverrideTransition(OBSWeakSource &scene)
return transition;
}
static int getExpectedTransitionDuration(OBSWeakSource &scene, OBSWeakSource &t,
static int getExpectedTransitionDuration(OBSWeakSource &scene,
OBSWeakSource &transiton_,
double duration)
{
OBSWeakSource transition = t;
if (!switcher->transitionOverrideOverride) {
OBSWeakSource transition = transiton_;
// If we are not modifying the transition override, we will have to
// check, if a transition override is set by the user and use its
// duration instead
if (!ShouldModifyTransitionOverrides()) {
auto overrideTransition = getOverrideTransition(scene);
if (overrideTransition) {
transition = overrideTransition;
@@ -104,6 +111,7 @@ static int getExpectedTransitionDuration(OBSWeakSource &scene, OBSWeakSource &t,
}
}
}
if (isUsingFixedLengthTransition(transition)) {
return -1; // no API is available to access the fixed duration
}
@@ -118,9 +126,9 @@ bool MacroActionSwitchScene::WaitForTransition(OBSWeakSource &scene,
{
const int expectedTransitionDuration = getExpectedTransitionDuration(
scene, transition, _duration.Seconds());
switcher->abortMacroWait = false;
SetMacroAbortWait(false);
std::unique_lock<std::mutex> lock(switcher->m);
std::unique_lock<std::mutex> lock(*GetMutex());
if (expectedTransitionDuration < 0) {
waitForTransitionChange(transition, &lock, GetMacro());
} else {
@@ -128,7 +136,7 @@ bool MacroActionSwitchScene::WaitForTransition(OBSWeakSource &scene,
&lock, GetMacro());
}
return !switcher->abortMacroWait;
return !MacroWaitShouldAbort();
}
bool MacroActionSwitchScene::PerformAction()
@@ -145,7 +153,7 @@ bool MacroActionSwitchScene::PerformAction()
auto transition = _transition.GetTransition();
SwitchScene({scene, transition, (int)(_duration.Milliseconds())},
obs_frontend_preview_program_mode_active());
if (_blockUntilTransitionDone && scene) {
if (_blockUntilTransitionDone && scene && scene != GetCurrentScene()) {
return WaitForTransition(scene, transition);
}
return true;

View File

@@ -32,7 +32,7 @@ void MacroActionScreenshot::CustomScreenshot(OBSWeakSource &source)
}
auto s = obs_weak_source_get_source(source);
_screenshot.~ScreenshotHelper();
new (&_screenshot) ScreenshotHelper(s, false, 0, true, _path);
new (&_screenshot) ScreenshotHelper(s, QRect(), false, 0, true, _path);
obs_source_release(s);
}

View File

@@ -601,11 +601,17 @@ void MacroActionSourceEdit::SetWidgetVisibility()
_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_TEMPVAR);
_manualSettingValue->setVisible(
_entryData->_action == MacroActionSource::Action::SETTINGS &&
_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_MANUAL);
if (_entryData->_action == MacroActionSource::Action::SETTINGS &&
_entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::INDIVIDUAL_MANUAL) {
RemoveStretchIfPresent(_settingsLayout);
_manualSettingValue->show();
} else {
AddStretchIfNecessary(_settingsLayout);
_manualSettingValue->hide();
}
const bool showWarning =
_entryData->_action == MacroActionSource::Action::ENABLE ||
_entryData->_action == MacroActionSource::Action::DISABLE;

View File

@@ -1,6 +1,5 @@
#include "macro-action-variable.hpp"
#include "macro-condition-edit.hpp"
#include "switcher-data.hpp"
#include "advanced-scene-switcher.hpp"
#include "macro.hpp"
#include "math-helpers.hpp"
@@ -51,6 +50,8 @@ const static std::map<MacroActionVariable::Type, std::string> actionTypes = {
"AdvSceneSwitcher.action.variable.type.extractJson"},
{MacroActionVariable::Type::SET_TO_TEMPVAR,
"AdvSceneSwitcher.action.variable.type.setToTempvar"},
{MacroActionVariable::Type::SCENE_ITEM_NAME,
"AdvSceneSwitcher.action.variable.type.sceneItemName"},
};
static void apppend(Variable &var, const std::string &value)
@@ -134,6 +135,49 @@ void MacroActionVariable::HandleMathExpression(Variable *var)
var->SetValue(std::get<double>(result));
}
struct GetSceneItemNameHelper {
int curIdx = 0;
int targetIdx = 0;
std::string name = "";
};
static bool getSceneItemAtIdx(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
{
auto data = reinterpret_cast<GetSceneItemNameHelper *>(ptr);
if (obs_sceneitem_is_group(item)) {
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
obs_scene_enum_items(scene, getSceneItemAtIdx, ptr);
}
if (data->curIdx == data->targetIdx) {
data->name =
obs_source_get_name(obs_sceneitem_get_source(item));
data->curIdx--;
return false;
}
data->curIdx--;
return true;
}
void MacroActionVariable::SetToSceneItemName(Variable *var)
{
auto weakSource = _scene.GetScene();
if (!weakSource) {
var->SetValue("");
return;
}
auto index = _sceneItemIndex.GetValue();
if (index < 1) {
var->SetValue("");
return;
}
auto sceneItemCount = GetSceneItemCount(weakSource);
OBSSourceAutoRelease source = obs_weak_source_get_source(weakSource);
auto scene = obs_scene_from_source(source);
GetSceneItemNameHelper data{sceneItemCount, index};
obs_scene_enum_items(scene, getSceneItemAtIdx, &data);
var->SetValue(data.name);
}
struct AskForInputParams {
QString prompt;
QString placeholder;
@@ -268,6 +312,9 @@ bool MacroActionVariable::PerformAction()
var->SetValue(*value);
return true;
}
case Type::SCENE_ITEM_NAME:
SetToSceneItemName(var.get());
return true;
}
return true;
@@ -299,6 +346,7 @@ bool MacroActionVariable::Save(obs_data_t *obj) const
_envVariableName.Save(obj, "environmentVariableName");
_scene.Save(obj);
_tempVar.Save(obj);
_sceneItemIndex.Save(obj, "sceneItemIndex");
return true;
}
@@ -328,6 +376,7 @@ bool MacroActionVariable::Load(obs_data_t *obj)
_envVariableName.Load(obj, "environmentVariableName");
_scene.Load(obj);
_tempVar.Load(obj, GetMacro());
_sceneItemIndex.Load(obj, "sceneItemIndex");
return true;
}
@@ -457,7 +506,8 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_envVariable(new VariableLineEdit(this)),
_scenes(new SceneSelectionWidget(this, true, false, true, true,
true)),
_tempVars(new TempVariableSelection(this))
_tempVars(new TempVariableSelection(this)),
_sceneItemIndex(new VariableSpinBox())
{
_numValue->setMinimum(-9999999999);
_numValue->setMaximum(9999999999);
@@ -475,6 +525,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_regexMatchIdx->setSuffix(".");
_inputPrompt->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_sceneItemIndex->setMinimum(1);
populateTypeSelection(_actions);
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
@@ -523,6 +574,10 @@ MacroActionVariableEdit::MacroActionVariableEdit(
QWidget::connect(_tempVars,
SIGNAL(SelectionChanged(const TempVariableRef &)),
this, SLOT(SelectionChanged(const TempVariableRef &)));
QWidget::connect(
_sceneItemIndex,
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this, SLOT(SceneItemIndexChanged(const NumberVariable<int> &)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{variables}}", _variables},
@@ -544,6 +599,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
{"{{envVariableName}}", _envVariable},
{"{{scenes}}", _scenes},
{"{{tempVars}}", _tempVars},
{"{{sceneItemIndex}}", _sceneItemIndex},
};
auto entryLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.variable.entry"),
@@ -638,6 +694,7 @@ void MacroActionVariableEdit::UpdateEntryData()
_envVariable->setText(_entryData->_envVariableName);
_scenes->SetScene(_entryData->_scene);
_tempVars->SetVariable(_entryData->_tempVar);
_sceneItemIndex->SetValue(_entryData->_sceneItemIndex);
SetWidgetVisibility();
}
@@ -982,6 +1039,17 @@ void MacroActionVariableEdit::SelectionChanged(const TempVariableRef &var)
_entryData->_tempVar = var;
}
void MacroActionVariableEdit::SceneItemIndexChanged(
const NumberVariable<int> &value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_sceneItemIndex = value;
}
void MacroActionVariableEdit::SetWidgetVisibility()
{
if (!_entryData) {
@@ -1058,10 +1126,15 @@ void MacroActionVariableEdit::SetWidgetVisibility()
}
_envVariable->setVisible(_entryData->_type ==
MacroActionVariable::Type::ENV_VARIABLE);
_scenes->setVisible(_entryData->_type ==
MacroActionVariable::Type::SCENE_ITEM_COUNT);
_scenes->setVisible(
_entryData->_type ==
MacroActionVariable::Type::SCENE_ITEM_COUNT ||
_entryData->_type ==
MacroActionVariable::Type::SCENE_ITEM_NAME);
_tempVars->setVisible(_entryData->_type ==
MacroActionVariable::Type::SET_TO_TEMPVAR);
_sceneItemIndex->setVisible(_entryData->_type ==
MacroActionVariable::Type::SCENE_ITEM_NAME);
adjustSize();
updateGeometry();
}

View File

@@ -6,6 +6,7 @@
#include "scene-selection.hpp"
#include "variable-line-edit.hpp"
#include "variable-text-edit.hpp"
#include "variable-spinbox.hpp"
namespace advss {
@@ -44,6 +45,7 @@ public:
STRING_LENGTH,
EXTRACT_JSON,
SET_TO_TEMPVAR,
SCENE_ITEM_NAME,
};
Type _type = Type::SET_FIXED_VALUE;
@@ -75,6 +77,7 @@ public:
#endif
SceneSelection _scene;
TempVariableRef _tempVar;
IntVariable _sceneItemIndex = 1;
private:
void DecrementCurrentSegmentVariableRef();
@@ -82,6 +85,7 @@ private:
void HandleRegexSubString(Variable *);
void HandleFindAndReplace(Variable *);
void HandleMathExpression(Variable *);
void SetToSceneItemName(Variable *);
std::weak_ptr<MacroSegment> _macroSegment;
int _segmentIdxLoadValue = -1;
@@ -129,6 +133,7 @@ private slots:
void EnvVariableChanged();
void SceneChanged(const SceneSelection &);
void SelectionChanged(const TempVariableRef &var);
void SceneItemIndexChanged(const NumberVariable<int> &);
signals:
void HeaderInfoChanged(const QString &);
@@ -167,6 +172,7 @@ private:
VariableLineEdit *_envVariable;
SceneSelectionWidget *_scenes;
TempVariableSelection *_tempVars;
VariableSpinBox *_sceneItemIndex;
std::shared_ptr<MacroActionVariable> _entryData;
QTimer _timer;

View File

@@ -1,5 +1,7 @@
#include "macro-action-wait.hpp"
#include "switcher-data.hpp"
#include "macro-helpers.hpp"
#include "macro.hpp"
#include "sync-helpers.hpp"
#include "utility.hpp"
#include <random>
@@ -24,8 +26,8 @@ static std::default_random_engine re(rd());
static void waitHelper(std::unique_lock<std::mutex> *lock, Macro *macro,
std::chrono::high_resolution_clock::time_point &time)
{
while (!switcher->abortMacroWait && !macro->GetStop()) {
if (switcher->macroWaitCv.wait_until(*lock, time) ==
while (!MacroWaitShouldAbort() && !macro->GetStop()) {
if (GetMacroWaitCV().wait_until(*lock, time) ==
std::cv_status::timeout) {
break;
}
@@ -53,11 +55,11 @@ bool MacroActionWait::PerformAction()
auto time = std::chrono::high_resolution_clock::now() +
std::chrono::milliseconds((int)(sleepDuration * 1000));
switcher->abortMacroWait = false;
std::unique_lock<std::mutex> lock(switcher->m);
SetMacroAbortWait(false);
std::unique_lock<std::mutex> lock(*GetMutex());
waitHelper(&lock, GetMacro(), time);
return !switcher->abortMacroWait;
return !MacroWaitShouldAbort();
}
bool MacroActionWait::Save(obs_data_t *obj) const

View File

@@ -192,7 +192,7 @@ void MacroActionWebsocketEdit::CheckForSettingsConflict()
updateGeometry();
}
void MacroActionWebsocketEdit::SetupWidgetVisibility()
void MacroActionWebsocketEdit::SetWidgetVisibility()
{
_messageType->setVisible(_entryData->_api ==
MacroActionWebsocket::API::SCENE_SWITCHER);
@@ -283,7 +283,7 @@ void MacroActionWebsocketEdit::UpdateEntryData()
_message->setPlainText(_entryData->_message);
_connection->SetConnection(_entryData->_connection);
SetupWidgetVisibility();
SetWidgetVisibility();
}
void MacroActionWebsocketEdit::APITypeChanged(int index)
@@ -294,7 +294,7 @@ void MacroActionWebsocketEdit::APITypeChanged(int index)
auto lock = LockContext();
_entryData->_api = static_cast<MacroActionWebsocket::API>(index);
SetupWidgetVisibility();
SetWidgetVisibility();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
@@ -308,7 +308,7 @@ void MacroActionWebsocketEdit::MessageTypeChanged(int index)
auto lock = LockContext();
_entryData->_type =
static_cast<MacroActionWebsocket::MessageType>(index);
SetupWidgetVisibility();
SetWidgetVisibility();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}

View File

@@ -76,7 +76,7 @@ protected:
private:
void CheckForSettingsConflict();
void SetupWidgetVisibility();
void SetWidgetVisibility();
void SetupRequestEdit();
void SetupEventEdit();
void SetupGenericEdit();

View File

@@ -62,17 +62,17 @@ MacroConditionAudio::~MacroConditionAudio()
bool MacroConditionAudio::CheckOutputCondition()
{
bool ret = false;
auto s = obs_weak_source_get_source(_audioSource.GetSource());
OBSSourceAutoRelease source =
obs_weak_source_get_source(_audioSource.GetSource());
// peak will have a value from -60 db to 0 db so we need to scale it
double curVolume = ((double)_peak + 60) * 1.7;
switch (_outputCondition) {
case OutputCondition::ABOVE:
// peak will have a value from -60 db to 0 db
ret = curVolume > _volume;
break;
case OutputCondition::BELOW:
// peak will have a value from -60 db to 0 db
ret = curVolume < _volume;
break;
default:
@@ -84,21 +84,21 @@ bool MacroConditionAudio::CheckOutputCondition()
// Reset for next check
_peak = -std::numeric_limits<float>::infinity();
obs_source_release(s);
if (_audioSource.GetType() == SourceSelection::Type::VARIABLE) {
ResetVolmeter();
}
return ret;
return ret && source;
}
bool MacroConditionAudio::CheckVolumeCondition()
{
bool ret = false;
auto s = obs_weak_source_get_source(_audioSource.GetSource());
OBSSourceAutoRelease source =
obs_weak_source_get_source(_audioSource.GetSource());
float curVolume = obs_source_get_volume(s);
bool muted = obs_source_muted(s);
float curVolume = obs_source_get_volume(source);
bool muted = obs_source_muted(source);
switch (_volumeCondition) {
case VolumeCondition::ABOVE:
@@ -126,10 +126,9 @@ bool MacroConditionAudio::CheckVolumeCondition()
}
SetTempVarValue("configured_volume", std::to_string(curVolume));
SetTempVarValue("muted", muted ? "true" : "false");
SetTempVarValue("muted", (source && muted) ? "true" : "false");
obs_source_release(s);
return ret;
return ret && source;
}
bool MacroConditionAudio::CheckSyncOffset()
@@ -139,8 +138,9 @@ bool MacroConditionAudio::CheckSyncOffset()
}
bool ret = false;
auto s = obs_weak_source_get_source(_audioSource.GetSource());
auto curOffset = obs_source_get_sync_offset(s) / nsPerMs;
OBSSourceAutoRelease source =
obs_weak_source_get_source(_audioSource.GetSource());
auto curOffset = obs_source_get_sync_offset(source) / nsPerMs;
if (_outputCondition == OutputCondition::ABOVE) {
ret = curOffset > _syncOffset;
} else {
@@ -148,8 +148,7 @@ bool MacroConditionAudio::CheckSyncOffset()
}
SetVariableValue(std::to_string(curOffset));
SetTempVarValue("sync_offset", std::to_string(curOffset));
obs_source_release(s);
return ret;
return ret && source;
}
bool MacroConditionAudio::CheckMonitor()
@@ -159,13 +158,13 @@ bool MacroConditionAudio::CheckMonitor()
}
bool ret = false;
auto s = obs_weak_source_get_source(_audioSource.GetSource());
ret = obs_source_get_monitoring_type(s) == _monitorType;
OBSSourceAutoRelease source =
obs_weak_source_get_source(_audioSource.GetSource());
ret = obs_source_get_monitoring_type(source) == _monitorType;
SetVariableValue("");
SetTempVarValue("monitor",
std::to_string(obs_source_get_monitoring_type(s)));
obs_source_release(s);
return ret;
std::to_string(obs_source_get_monitoring_type(source)));
return ret && source;
}
bool MacroConditionAudio::CheckBalance()
@@ -175,8 +174,9 @@ bool MacroConditionAudio::CheckBalance()
}
bool ret = false;
auto s = obs_weak_source_get_source(_audioSource.GetSource());
auto curBalance = obs_source_get_balance_value(s);
OBSSourceAutoRelease source =
obs_weak_source_get_source(_audioSource.GetSource());
auto curBalance = obs_source_get_balance_value(source);
if (_outputCondition == OutputCondition::ABOVE) {
ret = curBalance > _balance;
} else {
@@ -184,8 +184,7 @@ bool MacroConditionAudio::CheckBalance()
}
SetVariableValue(std::to_string(curBalance));
SetTempVarValue("balance", std::to_string(curBalance));
obs_source_release(s);
return ret;
return ret && source;
}
bool MacroConditionAudio::CheckCondition()
@@ -239,13 +238,12 @@ obs_volmeter_t *AddVolmeterToSource(MacroConditionAudio *entry,
obs_volmeter_t *volmeter = obs_volmeter_create(OBS_FADER_LOG);
obs_volmeter_add_callback(volmeter, MacroConditionAudio::SetVolumeLevel,
entry);
obs_source_t *as = obs_weak_source_get_source(source);
if (!obs_volmeter_attach_source(volmeter, as)) {
const char *name = obs_source_get_name(as);
OBSSourceAutoRelease audioSource = obs_weak_source_get_source(source);
if (!obs_volmeter_attach_source(volmeter, audioSource)) {
const char *name = obs_source_get_name(audioSource);
blog(LOG_WARNING, "failed to attach volmeter to source %s",
name);
}
obs_source_release(as);
return volmeter;
}
@@ -455,10 +453,9 @@ MacroConditionAudioEdit::MacroConditionAudioEdit(
void MacroConditionAudioEdit::UpdateVolmeterSource()
{
delete _volMeter;
obs_source_t *soruce = obs_weak_source_get_source(
OBSSourceAutoRelease soruce = obs_weak_source_get_source(
_entryData->_audioSource.GetSource());
_volMeter = new VolControl(soruce);
obs_source_release(soruce);
_volMeter = new VolControl(soruce.Get());
QLayout *layout = this->layout();
layout->addWidget(_volMeter);

View File

@@ -1,5 +1,4 @@
#include "macro-condition-cursor.hpp"
#include "switcher-data.hpp"
#include "platform-funcs.hpp"
#include "utility.hpp"
@@ -61,13 +60,15 @@ bool MacroConditionCursor::CheckCondition()
std::to_string(cursorPos.second));
break;
case Condition::MOVING:
ret = switcher->cursorPosChanged;
ret = cursorPos.first != _lastCursorPosition.first ||
cursorPos.second != _lastCursorPosition.second;
break;
case Condition::CLICK:
ret = CheckClick();
break;
}
_lastCheckTime = std::chrono::high_resolution_clock::now();
_lastCursorPosition = cursorPos;
if (GetVariableValue().empty()) {
SetVariableValue(ret ? "true" : "false");

View File

@@ -41,6 +41,7 @@ public:
private:
bool CheckClick();
std::chrono::high_resolution_clock::time_point _lastCheckTime{};
std::pair<int, int> _lastCursorPosition;
static bool _registered;
static const std::string id;

View File

@@ -638,10 +638,24 @@ void MacroConditionDateEdit::AdvancedSettingsToggleClicked()
{
auto lock = LockContext();
_entryData->_dayOfWeekCheck = !_entryData->_dayOfWeekCheck;
_entryData->_condition = MacroConditionDate::Condition::AT;
}
_condition->setCurrentIndex(0);
_weekCondition->setCurrentIndex(0);
switch (_entryData->_condition) {
case MacroConditionDate::Condition::BETWEEN:
case MacroConditionDate::Condition::PATTERN:
_condition->setCurrentIndex(0);
_weekCondition->setCurrentIndex(0);
break;
case MacroConditionDate::Condition::AT:
case MacroConditionDate::Condition::AFTER:
case MacroConditionDate::Condition::BEFORE:
_weekCondition->setCurrentIndex(
static_cast<int>(_entryData->_condition));
_condition->setCurrentIndex(
static_cast<int>(_entryData->_condition));
break;
}
SetWidgetStatus();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));

View File

@@ -1,84 +1,24 @@
#include "advanced-scene-switcher.hpp"
#include "switcher-data.hpp"
#include "macro-condition-edit.hpp"
#include "macro.hpp"
#include "macro-properties.hpp"
#include "advanced-scene-switcher.hpp"
#include "macro-condition-scene.hpp"
#include "section.hpp"
#include "utility.hpp"
namespace advss {
std::map<std::string, MacroConditionInfo> &MacroConditionFactory::GetMap()
{
static std::map<std::string, MacroConditionInfo> _methods;
return _methods;
}
bool MacroConditionFactory::Register(const std::string &id,
MacroConditionInfo info)
{
if (auto it = GetMap().find(id); it == GetMap().end()) {
GetMap()[id] = info;
return true;
}
return false;
}
std::shared_ptr<MacroCondition>
MacroConditionFactory::Create(const std::string &id, Macro *m)
{
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._createFunc(m);
}
return nullptr;
}
QWidget *
MacroConditionFactory::CreateWidget(const std::string &id, QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._createWidgetFunc(parent, cond);
}
return nullptr;
}
std::string MacroConditionFactory::GetConditionName(const std::string &id)
{
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._name;
}
return "unknown condition";
}
std::string MacroConditionFactory::GetIdByName(const QString &name)
{
for (auto it : GetMap()) {
if (name == obs_module_text(it.second._name.c_str())) {
return it.first;
}
}
return "";
}
bool MacroConditionFactory::UsesDurationModifier(const std::string &id)
{
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._useDurationModifier;
}
return false;
}
static inline void populateLogicSelection(QComboBox *list, bool root = false)
{
if (root) {
for (auto entry : MacroCondition::logicTypes) {
for (const auto &entry : MacroCondition::logicTypes) {
if (static_cast<int>(entry.first) < logic_root_offset) {
list->addItem(obs_module_text(
entry.second._name.c_str()));
}
}
} else {
for (auto entry : MacroCondition::logicTypes) {
for (const auto &entry : MacroCondition::logicTypes) {
if (static_cast<int>(entry.first) >=
logic_root_offset) {
list->addItem(obs_module_text(
@@ -90,7 +30,7 @@ static inline void populateLogicSelection(QComboBox *list, bool root = false)
static inline void populateConditionSelection(QComboBox *list)
{
for (auto &[_, condition] :
for (const auto &[_, condition] :
MacroConditionFactory::GetConditionTypes()) {
QString entry(obs_module_text(condition._name.c_str()));
if (list->findText(entry) == -1) {
@@ -179,7 +119,7 @@ void DurationModifierEdit::Collapse(bool collapse)
MacroConditionEdit::MacroConditionEdit(
QWidget *parent, std::shared_ptr<MacroCondition> *entryData,
const std::string &id, bool root)
: MacroSegmentEdit(switcher->macroProperties._highlightConditions,
: MacroSegmentEdit(GetGlobalMacroProperties()._highlightConditions,
parent),
_logicSelection(new QComboBox()),
_conditionSelection(new FilterComboBox()),

View File

@@ -1,38 +1,12 @@
#pragma once
#include "macro-condition.hpp"
#include "macro-condition-factory.hpp"
#include "filter-combo-box.hpp"
#include <memory>
namespace advss {
struct MacroConditionInfo {
using TCreateMethod = std::shared_ptr<MacroCondition> (*)(Macro *m);
using TCreateWidgetMethod =
QWidget *(*)(QWidget *parent, std::shared_ptr<MacroCondition>);
TCreateMethod _createFunc = nullptr;
TCreateWidgetMethod _createWidgetFunc = nullptr;
std::string _name;
bool _useDurationModifier = true;
};
class MacroConditionFactory {
public:
MacroConditionFactory() = delete;
static bool Register(const std::string &, MacroConditionInfo);
static std::shared_ptr<MacroCondition> Create(const std::string &,
Macro *m);
static QWidget *CreateWidget(const std::string &id, QWidget *parent,
std::shared_ptr<MacroCondition>);
static auto GetConditionTypes() { return GetMap(); }
static std::string GetConditionName(const std::string &);
static std::string GetIdByName(const QString &name);
static bool UsesDurationModifier(const std::string &id);
private:
static std::map<std::string, MacroConditionInfo> &GetMap();
};
class DurationModifierEdit : public QWidget {
Q_OBJECT
public:

View File

@@ -0,0 +1,66 @@
#include "macro-condition-factory.hpp"
namespace advss {
std::map<std::string, MacroConditionInfo> &MacroConditionFactory::GetMap()
{
static std::map<std::string, MacroConditionInfo> _methods;
return _methods;
}
bool MacroConditionFactory::Register(const std::string &id,
MacroConditionInfo info)
{
if (auto it = GetMap().find(id); it == GetMap().end()) {
GetMap()[id] = info;
return true;
}
return false;
}
std::shared_ptr<MacroCondition>
MacroConditionFactory::Create(const std::string &id, Macro *m)
{
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._create(m);
}
return nullptr;
}
QWidget *
MacroConditionFactory::CreateWidget(const std::string &id, QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._createWidget(parent, cond);
}
return nullptr;
}
std::string MacroConditionFactory::GetConditionName(const std::string &id)
{
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._name;
}
return "unknown condition";
}
std::string MacroConditionFactory::GetIdByName(const QString &name)
{
for (auto it : GetMap()) {
if (name == obs_module_text(it.second._name.c_str())) {
return it.first;
}
}
return "";
}
bool MacroConditionFactory::UsesDurationModifier(const std::string &id)
{
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._useDurationModifier;
}
return false;
}
} // namespace advss

View File

@@ -0,0 +1,35 @@
#pragma once
#include "macro-condition.hpp"
#include <memory>
namespace advss {
struct MacroConditionInfo {
using CreateCondition = std::shared_ptr<MacroCondition> (*)(Macro *m);
using CreateConditionWidget =
QWidget *(*)(QWidget *parent, std::shared_ptr<MacroCondition>);
CreateCondition _create = nullptr;
CreateConditionWidget _createWidget = nullptr;
std::string _name;
bool _useDurationModifier = true;
};
class MacroConditionFactory {
public:
MacroConditionFactory() = delete;
static bool Register(const std::string &, MacroConditionInfo);
static std::shared_ptr<MacroCondition> Create(const std::string &,
Macro *m);
static QWidget *CreateWidget(const std::string &id, QWidget *parent,
std::shared_ptr<MacroCondition>);
static auto GetConditionTypes() { return GetMap(); }
static std::string GetConditionName(const std::string &);
static std::string GetIdByName(const QString &name);
static bool UsesDurationModifier(const std::string &id);
private:
static std::map<std::string, MacroConditionInfo> &GetMap();
};
} // namespace advss

View File

@@ -1,7 +1,7 @@
#include "macro-condition-file.hpp"
#include "utility.hpp"
#include "switcher-data.hpp"
#include "curl-helper.hpp"
#include "plugin-state-helpers.hpp"
#include <QTextStream>
#include <QFileDialog>
@@ -28,16 +28,16 @@ static size_t WriteCallback(void *contents, size_t size, size_t nmemb,
static std::string getRemoteData(std::string &url)
{
std::string readBuffer;
switcher->curl.SetOpt(CURLOPT_URL, url.c_str());
switcher->curl.SetOpt(CURLOPT_WRITEFUNCTION, WriteCallback);
switcher->curl.SetOpt(CURLOPT_WRITEDATA, &readBuffer);
CurlHelper::SetOpt(CURLOPT_URL, url.c_str());
CurlHelper::SetOpt(CURLOPT_WRITEFUNCTION, WriteCallback);
CurlHelper::SetOpt(CURLOPT_WRITEDATA, &readBuffer);
// Set timeout to at least one second
int timeout = switcher->interval / 1000;
int timeout = GetIntervalValue() / 1000;
if (timeout == 0) {
timeout = 1;
}
switcher->curl.SetOpt(CURLOPT_TIMEOUT, 1);
switcher->curl.Perform();
CurlHelper::SetOpt(CURLOPT_TIMEOUT, 1);
CurlHelper::Perform();
return readBuffer;
}

View File

@@ -24,6 +24,8 @@ const static std::map<MacroConditionMacro::Type, std::string>
"AdvSceneSwitcher.condition.macro.type.actionDisabled"},
{MacroConditionMacro::Type::ACTION_ENABLED,
"AdvSceneSwitcher.condition.macro.type.actionEnabled"},
{MacroConditionMacro::Type::PAUSED,
"AdvSceneSwitcher.condition.macro.type.paused"},
};
const static std::map<MacroConditionMacro::CounterCondition, std::string>
@@ -75,6 +77,8 @@ bool MacroConditionMacro::CheckMultiStateCondition()
}
}
SetTempVarValue("matchedCount", std::to_string(matchedCount));
switch (_multiSateCondition) {
case MacroConditionMacro::MultiStateCondition::BELOW:
return matchedCount < _multiSateCount;
@@ -107,6 +111,16 @@ bool MacroConditionMacro::CheckActionStateCondition()
return false;
}
bool MacroConditionMacro::CheckPauseState()
{
auto macro = _macro.GetMacro();
if (!macro) {
return false;
}
return macro->Paused();
}
bool MacroConditionMacro::CheckCountCondition()
{
auto macro = _macro.GetMacro();
@@ -114,6 +128,8 @@ bool MacroConditionMacro::CheckCountCondition()
return false;
}
SetTempVarValue("runCount", std::to_string(macro->RunCount()));
switch (_counterCondition) {
case CounterCondition::BELOW:
return macro->RunCount() < _count;
@@ -140,6 +156,8 @@ bool MacroConditionMacro::CheckCondition()
case Type::ACTION_DISABLED:
case Type::ACTION_ENABLED:
return CheckActionStateCondition();
case Type::PAUSED:
return CheckPauseState();
default:
break;
}
@@ -193,7 +211,7 @@ bool MacroConditionMacro::Load(obs_data_t *obj)
bool MacroConditionMacro::PostLoad()
{
return MacroRefCondition::PostLoad() &&
return MacroCondition::PostLoad() && MacroRefCondition::PostLoad() &&
MultiMacroRefCondtition::PostLoad();
}
@@ -202,6 +220,45 @@ std::string MacroConditionMacro::GetShortDesc() const
return _macro.Name();
}
void MacroConditionMacro::SetType(Type type)
{
_type = type;
SetupTempVars();
}
void MacroConditionMacro::SetupTempVars()
{
MacroCondition::SetupTempVars();
switch (_type) {
case Type::COUNT:
AddTempvar(
"runCount",
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.runCount"),
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.runCount.description"));
break;
case Type::STATE:
break;
case Type::MULTI_STATE:
AddTempvar(
"matchedCount",
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.matchedCount"),
obs_module_text(
"AdvSceneSwitcher.tempVar.macro.matchedCount.description"));
break;
case Type::ACTION_DISABLED:
break;
case Type::ACTION_ENABLED:
break;
case Type::PAUSED:
break;
default:
break;
}
}
static inline void populateTypeSelection(QComboBox *list)
{
for (auto entry : macroConditionTypes) {
@@ -335,7 +392,10 @@ void MacroConditionMacroEdit::ClearLayouts()
void MacroConditionMacroEdit::SetupWidgets()
{
switch (_entryData->_type) {
SetWidgetVisibility();
ClearLayouts();
switch (_entryData->GetType()) {
case MacroConditionMacro::Type::COUNT:
SetupCountWidgets();
break;
@@ -351,6 +411,9 @@ void MacroConditionMacroEdit::SetupWidgets()
case MacroConditionMacro::Type::ACTION_ENABLED:
SetupActionStateWidgets(true);
break;
case MacroConditionMacro::Type::PAUSED:
SetupPauseWidgets();
break;
default:
break;
}
@@ -358,38 +421,24 @@ void MacroConditionMacroEdit::SetupWidgets()
void MacroConditionMacroEdit::SetupStateWidgets()
{
SetWidgetVisibility();
ClearLayouts();
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{macros}}", _macros},
};
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.macro.state.entry"),
_settingsLine1, widgetPlaceholders);
_settingsLine1, {{"{{macros}}", _macros}});
}
void MacroConditionMacroEdit::SetupMultiStateWidgets()
{
SetWidgetVisibility();
ClearLayouts();
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{multiStateConditions}}", _multiStateConditions},
{"{{multiStateCount}}", _multiStateCount},
};
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.macro.multistate.entry"),
_settingsLine1, widgetPlaceholders);
_settingsLine1,
{{"{{multiStateConditions}}", _multiStateConditions},
{"{{multiStateCount}}", _multiStateCount}});
}
void MacroConditionMacroEdit::SetupCountWidgets()
{
SetWidgetVisibility();
ClearLayouts();
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{macros}}", _macros},
{"{{conditions}}", _counterConditions},
{"{{count}}", _count},
@@ -408,9 +457,6 @@ void MacroConditionMacroEdit::SetupCountWidgets()
void MacroConditionMacroEdit::SetupActionStateWidgets(bool enable)
{
SetWidgetVisibility();
ClearLayouts();
PlaceWidgets(
obs_module_text(
enable ? "AdvSceneSwitcher.condition.macro.actionState.enabled.entry"
@@ -419,35 +465,49 @@ void MacroConditionMacroEdit::SetupActionStateWidgets(bool enable)
{{"{{macros}}", _macros}, {"{{actionIndex}}", _actionIndex}});
}
void MacroConditionMacroEdit::SetupPauseWidgets()
{
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.macro.paused.entry"),
_settingsLine1, {{"{{macros}}", _macros}});
}
void MacroConditionMacroEdit::SetWidgetVisibility()
{
_macros->setVisible(
_entryData->_type == MacroConditionMacro::Type::COUNT ||
_entryData->_type == MacroConditionMacro::Type::STATE ||
_entryData->_type ==
_entryData->GetType() == MacroConditionMacro::Type::COUNT ||
_entryData->GetType() == MacroConditionMacro::Type::STATE ||
_entryData->GetType() ==
MacroConditionMacro::Type::ACTION_DISABLED ||
_entryData->_type == MacroConditionMacro::Type::ACTION_ENABLED);
_counterConditions->setVisible(_entryData->_type ==
_entryData->GetType() ==
MacroConditionMacro::Type::ACTION_ENABLED ||
_entryData->GetType() == MacroConditionMacro::Type::PAUSED);
_counterConditions->setVisible(_entryData->GetType() ==
MacroConditionMacro::Type::COUNT);
_count->setVisible(_entryData->_type ==
_count->setVisible(_entryData->GetType() ==
MacroConditionMacro::Type::COUNT);
_currentCount->setVisible(_entryData->_type ==
_currentCount->setVisible(_entryData->GetType() ==
MacroConditionMacro::Type::COUNT);
_resetCount->setVisible(_entryData->_type ==
_resetCount->setVisible(_entryData->GetType() ==
MacroConditionMacro::Type::COUNT);
_macroList->setVisible(_entryData->_type ==
_macroList->setVisible(_entryData->GetType() ==
MacroConditionMacro::Type::MULTI_STATE);
_multiStateConditions->setVisible(
_entryData->_type == MacroConditionMacro::Type::MULTI_STATE);
_multiStateCount->setVisible(_entryData->_type ==
_entryData->GetType() ==
MacroConditionMacro::Type::MULTI_STATE);
_multiStateCount->setVisible(_entryData->GetType() ==
MacroConditionMacro::Type::MULTI_STATE);
_actionIndex->setVisible(
_entryData->_type ==
_entryData->GetType() ==
MacroConditionMacro::Type::ACTION_DISABLED ||
_entryData->_type == MacroConditionMacro::Type::ACTION_ENABLED);
if (_entryData->_type == MacroConditionMacro::Type::MULTI_STATE ||
_entryData->_type == MacroConditionMacro::Type::ACTION_DISABLED ||
_entryData->_type == MacroConditionMacro::Type::ACTION_ENABLED) {
_entryData->GetType() ==
MacroConditionMacro::Type::ACTION_ENABLED);
if (_entryData->GetType() == MacroConditionMacro::Type::MULTI_STATE ||
_entryData->GetType() ==
MacroConditionMacro::Type::ACTION_DISABLED ||
_entryData->GetType() ==
MacroConditionMacro::Type::ACTION_ENABLED ||
_entryData->GetType() == MacroConditionMacro::Type::PAUSED) {
_pausedWarning->hide();
}
@@ -463,7 +523,7 @@ void MacroConditionMacroEdit::UpdateEntryData()
SetupWidgets();
_macros->SetCurrentMacro(_entryData->_macro);
_types->setCurrentIndex(static_cast<int>(_entryData->_type));
_types->setCurrentIndex(static_cast<int>(_entryData->GetType()));
_counterConditions->setCurrentIndex(
static_cast<int>(_entryData->_counterCondition));
_count->SetValue(_entryData->_count);
@@ -535,7 +595,7 @@ void MacroConditionMacroEdit::TypeChanged(int type)
}
auto lock = LockContext();
_entryData->_type = static_cast<MacroConditionMacro::Type>(type);
_entryData->SetType(static_cast<MacroConditionMacro::Type>(type));
SetupWidgets();
}
@@ -571,7 +631,8 @@ void MacroConditionMacroEdit::UpdatePaused()
auto macro = _entryData->_macro.GetMacro();
_pausedWarning->setVisible(
_entryData &&
_entryData->_type != MacroConditionMacro::Type::MULTI_STATE &&
_entryData->GetType() !=
MacroConditionMacro::Type::MULTI_STATE &&
macro && macro->Paused());
adjustSize();
updateGeometry();

View File

@@ -39,8 +39,10 @@ public:
MULTI_STATE,
ACTION_DISABLED,
ACTION_ENABLED,
PAUSED,
};
Type _type = Type::STATE;
void SetType(Type);
Type GetType() const { return _type; }
enum class CounterCondition {
BELOW,
@@ -64,7 +66,11 @@ private:
bool CheckStateCondition();
bool CheckMultiStateCondition();
bool CheckActionStateCondition();
bool CheckPauseState();
void SetupTempVars();
Type _type = Type::STATE;
static bool _registered;
static const std::string id;
};
@@ -128,6 +134,7 @@ private:
void SetupMultiStateWidgets();
void SetupCountWidgets();
void SetupActionStateWidgets(bool enable);
void SetupPauseWidgets();
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -1,5 +1,7 @@
#include "macro-condition-media.hpp"
#include "switcher-data.hpp"
#include "macro.hpp"
#include "plugin-state-helpers.hpp"
#include "scene-switch-helpers.hpp"
#include "utility.hpp"
namespace advss {
@@ -148,26 +150,20 @@ bool MacroConditionMedia::CheckMediaMatch()
if (!_source.GetSource()) {
return false;
}
bool match = false;
bool matched = CheckState() && CheckTime();
if (matched && !(_onlyMatchOnChagne && _alreadyMatched)) {
match = true;
}
_alreadyMatched = matched;
// reset for next check
_stopped = false;
_ended = false;
_next = false;
return match;
return matched;
}
void MacroConditionMedia::HandleSceneChange()
{
UpdateMediaSourcesOfSceneList();
_lastConfigureScene = switcher->currentScene;
_lastConfigureScene = GetCurrentScene();
}
bool MacroConditionMedia::CheckCondition()
@@ -194,7 +190,7 @@ bool MacroConditionMedia::CheckCondition()
break;
}
if (_lastConfigureScene != switcher->currentScene) {
if (_lastConfigureScene != GetCurrentScene()) {
HandleSceneChange();
}
@@ -210,7 +206,6 @@ bool MacroConditionMedia::Save(obs_data_t *obj) const
obs_data_set_int(obj, "state", static_cast<int>(_state));
obs_data_set_int(obj, "restriction", static_cast<int>(_restriction));
_time.Save(obj);
obs_data_set_bool(obj, "matchOnChagne", _onlyMatchOnChagne);
obs_data_set_int(obj, "version", 0);
return true;
}
@@ -268,7 +263,6 @@ bool MacroConditionMedia::Load(obs_data_t *obj)
_restriction = static_cast<MacroConditionMedia::Time>(
obs_data_get_int(obj, "restriction"));
_time.Load(obj);
_onlyMatchOnChagne = obs_data_get_bool(obj, "matchOnChagne");
if (_sourceType == Type::SOURCE) {
obs_source_t *mediasource =
@@ -408,9 +402,7 @@ MacroConditionMediaEdit::MacroConditionMediaEdit(
_sources(new SourceSelectionWidget(this, QStringList(), true)),
_states(new QComboBox()),
_timeRestrictions(new QComboBox()),
_time(new DurationSelection()),
_onChange(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.media.matchOnChange")))
_time(new DurationSelection())
{
_states->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.media.inconsistencyInfo"));
@@ -432,14 +424,12 @@ MacroConditionMediaEdit::MacroConditionMediaEdit(
this, SLOT(TimeRestrictionChanged(int)));
QWidget::connect(_time, SIGNAL(DurationChanged(const Duration &)), this,
SLOT(TimeChanged(const Duration &)));
QWidget::connect(_onChange, SIGNAL(stateChanged(int)), this,
SLOT(OnChangeChanged(int)));
populateSourceTypes(_sourceTypes);
populateMediaStates(_states);
populateMediaTimes(_timeRestrictions);
QHBoxLayout *entryLayout = new QHBoxLayout;
auto layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{sourceTypes}}", _sourceTypes},
{"{{mediaSources}}", _sources},
@@ -449,11 +439,8 @@ MacroConditionMediaEdit::MacroConditionMediaEdit(
{"{{time}}", _time},
};
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.media.entry"),
entryLayout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLayout);
mainLayout->addWidget(_onChange);
setLayout(mainLayout);
layout, widgetPlaceholders);
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
@@ -572,28 +559,12 @@ void MacroConditionMediaEdit::TimeChanged(const Duration &dur)
}
}
void MacroConditionMediaEdit::OnChangeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_onlyMatchOnChagne = value;
if (_entryData->_sourceType != MacroConditionMedia::Type::SOURCE) {
_entryData->UpdateMediaSourcesOfSceneList();
}
}
void MacroConditionMediaEdit::SetWidgetVisibility()
{
_sources->setVisible(_entryData->_sourceType ==
MacroConditionMedia::Type::SOURCE);
_scenes->setVisible(_entryData->_sourceType !=
MacroConditionMedia::Type::SOURCE);
if (!_onChange->isChecked()) {
_onChange->hide();
}
}
static int getIdxFromMediaState(MacroConditionMedia::State state)
@@ -625,7 +596,6 @@ void MacroConditionMediaEdit::UpdateEntryData()
MacroConditionMedia::Time::TIME_RESTRICTION_NONE) {
_time->setDisabled(true);
}
_onChange->setChecked(_entryData->_onlyMatchOnChagne);
SetWidgetVisibility();
}

View File

@@ -72,7 +72,6 @@ public:
OBSWeakSource _rawSource = nullptr;
std::vector<MacroConditionMedia> _sourceGroup;
Duration _time;
bool _onlyMatchOnChagne = false;
private:
bool CheckTime();
@@ -84,9 +83,7 @@ private:
bool _stopped = false;
bool _ended = false;
bool _next = false;
// TODO: Remove _alreadyMatched as it does not make much sense when
// time restrictions for macro conditions are available.
bool _alreadyMatched = false;
// Workaround to enable use of "ended" to specify end of VLC playlist
bool _previousStateEnded = false;
// Used to keep track of scene changes
@@ -119,7 +116,6 @@ private slots:
void StateChanged(int index);
void TimeRestrictionChanged(int index);
void TimeChanged(const Duration &seconds);
void OnChangeChanged(int);
signals:
void HeaderInfoChanged(const QString &);
@@ -130,7 +126,6 @@ protected:
QComboBox *_states;
QComboBox *_timeRestrictions;
DurationSelection *_time;
QCheckBox *_onChange;
std::shared_ptr<MacroConditionMedia> _entryData;
private:

View File

@@ -1,5 +1,6 @@
#include "macro-condition-plugin-state.hpp"
#include "switcher-data.hpp"
#include "macro-helpers.hpp"
#include "plugin-state-helpers.hpp"
#include "utility.hpp"
namespace advss {
@@ -33,7 +34,7 @@ const static std::map<MacroConditionPluginState::Condition, std::string>
MacroConditionPluginState::~MacroConditionPluginState()
{
if (_condition == Condition::OBS_SHUTDOWN) {
switcher->shutdownConditionCount--;
GetShutdownConditionCount()--;
}
}
@@ -41,15 +42,15 @@ bool MacroConditionPluginState::CheckCondition()
{
switch (_condition) {
case Condition::PLUGIN_SCENE_CHANGE:
return switcher->macroSceneSwitched;
return MacroSwitchedScene();
case Condition::PLUGIN_RUNNING:
return true;
case Condition::OBS_SHUTDOWN:
return switcher->obsIsShuttingDown;
return OBSIsShuttingDown();
case Condition::PLUGIN_START:
return switcher->firstInterval;
return IsFirstInterval();
case Condition::PLUGIN_RESTART:
return switcher->firstIntervalAfterStop;
return IsFirstIntervalAfterStop();
case Condition::SCENE_COLLECTION_CHANGE:
if (_firstCheckAfterSceneCollectionChange) {
_firstCheckAfterSceneCollectionChange = false;
@@ -83,25 +84,33 @@ bool MacroConditionPluginState::Load(obs_data_t *obj)
obs_data_get_int(obj, "condition"));
switch (oldValue) {
case PluginStateCondition::SCENE_SWITCHED:
_condition = Condition::PLUGIN_SCENE_CHANGE;
SetCondition(Condition::PLUGIN_SCENE_CHANGE);
break;
case PluginStateCondition::RUNNING:
_condition = Condition::PLUGIN_RUNNING;
SetCondition(Condition::PLUGIN_RUNNING);
break;
case PluginStateCondition::SHUTDOWN:
_condition = Condition::OBS_SHUTDOWN;
SetCondition(Condition::OBS_SHUTDOWN);
break;
}
} else {
_condition = static_cast<Condition>(
obs_data_get_int(obj, "condition"));
}
if (_condition == Condition::OBS_SHUTDOWN) {
switcher->shutdownConditionCount++;
SetCondition(static_cast<Condition>(
obs_data_get_int(obj, "condition")));
}
return true;
}
void MacroConditionPluginState::SetCondition(Condition newValue)
{
if (_condition == Condition::OBS_SHUTDOWN) {
GetShutdownConditionCount()--;
}
if (newValue == Condition::OBS_SHUTDOWN) {
GetShutdownConditionCount()++;
}
_condition = newValue;
}
static inline void populateConditionSelection(QComboBox *list)
{
for (const auto &[action, name] : pluginStateConditionTypes) {
@@ -148,17 +157,9 @@ void MacroConditionPluginStateEdit::ConditionChanged(int idx)
}
auto lock = LockContext();
if (_entryData->_condition ==
MacroConditionPluginState::Condition::OBS_SHUTDOWN) {
switcher->shutdownConditionCount--;
}
_entryData->_condition =
_entryData->SetCondition(
static_cast<MacroConditionPluginState::Condition>(
_condition->itemData(idx).toInt());
if (_entryData->_condition ==
MacroConditionPluginState::Condition::OBS_SHUTDOWN) {
switcher->shutdownConditionCount++;
}
_condition->itemData(idx).toInt()));
SetWidgetVisibility();
}
@@ -168,15 +169,15 @@ void MacroConditionPluginStateEdit::UpdateEntryData()
return;
}
_condition->setCurrentIndex(
_condition->findData(static_cast<int>(_entryData->_condition)));
_condition->setCurrentIndex(_condition->findData(
static_cast<int>(_entryData->GetCondition())));
SetWidgetVisibility();
}
void MacroConditionPluginStateEdit::SetWidgetVisibility()
{
_shutdownLimitation->setVisible(
_entryData->_condition ==
_entryData->GetCondition() ==
MacroConditionPluginState::Condition::OBS_SHUTDOWN);
adjustSize();
updateGeometry();

View File

@@ -26,10 +26,11 @@ public:
SCENE_COLLECTION_CHANGE,
PLUGIN_SCENE_CHANGE,
};
Condition _condition = Condition::PLUGIN_SCENE_CHANGE;
void SetCondition(Condition);
Condition GetCondition() const { return _condition; }
private:
Condition _condition = Condition::PLUGIN_SCENE_CHANGE;
bool _firstCheckAfterSceneCollectionChange = true;
const static uint32_t _version;

View File

@@ -1,5 +1,4 @@
#include "macro-condition-process.hpp"
#include "switcher-data.hpp"
#include "platform-funcs.hpp"
#include "utility.hpp"
@@ -19,25 +18,43 @@ bool MacroConditionProcess::CheckCondition()
QStringList runningProcesses;
QString proc = QString::fromStdString(_process);
GetProcessList(runningProcesses);
std::string foregroundProcessName;
GetForegroundProcessName(foregroundProcessName);
bool equals = runningProcesses.contains(proc);
bool matches = runningProcesses.indexOf(QRegularExpression(proc)) != -1;
bool focus = !_focus || IsInFocus(proc);
SetVariableValue(foregroundProcessName);
if (IsReferencedInVars()) {
std::string name;
GetForegroundProcessName(name);
SetVariableValue(name);
if (!_regex.Enabled()) {
if (runningProcesses.contains(proc) &&
(!_checkFocus || IsInFocus(proc))) {
SetTempVarValue("name", proc.toStdString());
return true;
}
return false;
}
return (equals || matches) && focus;
auto matchIndex = runningProcesses.indexOf(QRegularExpression(proc));
if (matchIndex == -1) {
return false;
}
if (!_checkFocus) {
SetTempVarValue("name",
runningProcesses.at(matchIndex).toStdString());
return true;
}
if (!IsInFocus(proc)) {
return false;
}
SetTempVarValue("name", foregroundProcessName);
return true;
}
bool MacroConditionProcess::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
obs_data_set_string(obj, "process", _process.c_str());
obs_data_set_bool(obj, "focus", _focus);
obs_data_set_bool(obj, "focus", _checkFocus);
_regex.Save(obj);
obs_data_set_int(obj, "version", 1);
return true;
}
@@ -45,7 +62,13 @@ bool MacroConditionProcess::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_process = obs_data_get_string(obj, "process");
_focus = obs_data_get_bool(obj, "focus");
_checkFocus = obs_data_get_bool(obj, "focus");
// Fall back to partial match regex as default for old version
if (!obs_data_has_user_value(obj, "version")) {
_regex.SetEnabled(true);
} else {
_regex.Load(obj);
}
return true;
}
@@ -54,10 +77,18 @@ std::string MacroConditionProcess::GetShortDesc() const
return _process;
}
void MacroConditionProcess::SetupTempVars()
{
MacroCondition::SetupTempVars();
AddTempvar("name",
obs_module_text("AdvSceneSwitcher.tempVar.process.name"));
}
MacroConditionProcessEdit::MacroConditionProcessEdit(
QWidget *parent, std::shared_ptr<MacroConditionProcess> entryData)
: QWidget(parent),
_processSelection(new QComboBox()),
_regex(new RegexConfigWidget(this)),
_focused(new QCheckBox()),
_focusProcess(new QLabel()),
_focusLayout(new QHBoxLayout())
@@ -68,6 +99,8 @@ MacroConditionProcessEdit::MacroConditionProcessEdit(
QWidget::connect(_processSelection,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(ProcessChanged(const QString &)));
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
SLOT(RegexChanged(RegexConfig)));
QWidget::connect(_focused, SIGNAL(stateChanged(int)), this,
SLOT(FocusChanged(int)));
QWidget::connect(&_timer, SIGNAL(timeout()), this,
@@ -77,6 +110,7 @@ MacroConditionProcessEdit::MacroConditionProcessEdit(
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{processes}}", _processSelection},
{"{{regex}}", _regex},
{"{{focused}}", _focused},
{"{{focusProcess}}", _focusProcess},
};
@@ -112,6 +146,18 @@ void MacroConditionProcessEdit::ProcessChanged(const QString &text)
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroConditionProcessEdit::RegexChanged(RegexConfig conf)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_regex = conf;
adjustSize();
updateGeometry();
}
void MacroConditionProcessEdit::FocusChanged(int state)
{
if (_loading || !_entryData) {
@@ -119,14 +165,15 @@ void MacroConditionProcessEdit::FocusChanged(int state)
}
auto lock = LockContext();
_entryData->_focus = state;
_entryData->_checkFocus = state;
SetWidgetVisibility();
}
void MacroConditionProcessEdit::UpdateFocusProcess()
{
_focusProcess->setText(
QString::fromStdString(switcher->currentForegroundProcess));
std::string name;
GetForegroundProcessName(name);
_focusProcess->setText(QString::fromStdString(name));
}
void MacroConditionProcessEdit::SetWidgetVisibility()
@@ -134,7 +181,7 @@ void MacroConditionProcessEdit::SetWidgetVisibility()
if (!_entryData) {
return;
}
SetLayoutVisible(_focusLayout, _entryData->_focus);
SetLayoutVisible(_focusLayout, _entryData->_checkFocus);
adjustSize();
}
@@ -145,7 +192,8 @@ void MacroConditionProcessEdit::UpdateEntryData()
}
_processSelection->setCurrentText(_entryData->_process.c_str());
_focused->setChecked(_entryData->_focus);
_regex->SetRegexConfig(_entryData->_regex);
_focused->setChecked(_entryData->_checkFocus);
SetWidgetVisibility();
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "regex-config.hpp"
#include <QComboBox>
#include <QCheckBox>
@@ -20,9 +21,12 @@ public:
}
std::string _process;
bool _focus = true;
bool _checkFocus = true;
RegexConfig _regex = RegexConfig::PartialMatchRegexConfig();
private:
void SetupTempVars();
static bool _registered;
static const std::string id;
};
@@ -45,22 +49,22 @@ public:
private slots:
void ProcessChanged(const QString &text);
void RegexChanged(RegexConfig);
void FocusChanged(int state);
void UpdateFocusProcess();
signals:
void HeaderInfoChanged(const QString &);
protected:
private:
void SetWidgetVisibility();
QComboBox *_processSelection;
RegexConfigWidget *_regex;
QCheckBox *_focused;
QLabel *_focusProcess;
QHBoxLayout *_focusLayout;
QTimer _timer;
std::shared_ptr<MacroConditionProcess> _entryData;
private:
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -1,7 +1,8 @@
#include "macro-condition-recording.hpp"
#include "macro.hpp"
#include "utility.hpp"
#include <thread>
namespace advss {
const std::string MacroConditionRecord::id = "recording";
@@ -19,29 +20,13 @@ const static std::map<MacroConditionRecord::Condition, std::string>
"AdvSceneSwitcher.condition.record.state.pause"},
{MacroConditionRecord::Condition::START,
"AdvSceneSwitcher.condition.record.state.start"},
{MacroConditionRecord::Condition::SAVE_DONE,
"AdvSceneSwitcher.condition.record.state.saveDone"},
{MacroConditionRecord::Condition::DURATION,
"AdvSceneSwitcher.condition.record.state.duration"},
};
MacroConditionRecord::MacroConditionRecord(Macro *m) : MacroCondition(m)
{
OBSOutputAutoRelease output = obs_frontend_get_recording_output();
if (!output) {
return;
}
auto sh = obs_output_get_signal_handler(output);
signal_handler_connect(sh, "stop", StopSignal, this);
}
MacroConditionRecord::~MacroConditionRecord()
{
OBSOutputAutoRelease output = obs_frontend_get_recording_output();
if (!output) {
return;
}
auto sh = obs_output_get_signal_handler(output);
signal_handler_disconnect(sh, "stop", StopSignal, this);
}
static bool SetupRecordingTimer();
static bool recordingTimerIsSetup = SetupRecordingTimer();
static int currentRecordingDurationInSeconds = 0;
bool MacroConditionRecord::CheckCondition()
{
@@ -52,12 +37,11 @@ bool MacroConditionRecord::CheckCondition()
return obs_frontend_recording_paused();
case Condition::START:
return obs_frontend_recording_active();
case Condition::SAVE_DONE:
if (_recordingSaveDone) {
_recordingSaveDone = false;
return true;
}
return false;
case Condition::DURATION:
SetTempVarValue(
"durationSeconds",
std::to_string(currentRecordingDurationInSeconds));
return currentRecordingDurationInSeconds > _duration.Seconds();
default:
break;
}
@@ -68,6 +52,7 @@ bool MacroConditionRecord::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
obs_data_set_int(obj, "state", static_cast<int>(_condition));
_duration.Save(obj);
return true;
}
@@ -75,24 +60,30 @@ bool MacroConditionRecord::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_condition = static_cast<Condition>(obs_data_get_int(obj, "state"));
_duration.Load(obj);
return true;
}
void MacroConditionRecord::StopSignal(void *c, calldata_t *data)
void MacroConditionRecord::SetCondition(Condition condition)
{
auto condition = static_cast<MacroConditionRecord *>(c);
const auto macro = condition->GetMacro();
if (macro && macro->Paused()) {
return;
}
_condition = condition;
SetupTempVars();
}
long long code = 0;
if (!calldata_get_int(data, "code", &code)) {
condition->_recordingSaveDone = false;
}
if (code == OBS_OUTPUT_SUCCESS) {
condition->_recordingSaveDone = true;
void MacroConditionRecord::SetupTempVars()
{
MacroCondition::SetupTempVars();
switch (_condition) {
case Condition::DURATION:
AddTempvar(
"durationSeconds",
obs_module_text(
"AdvSceneSwitcher.tempVar.recording.durationSeconds"),
obs_module_text(
"AdvSceneSwitcher.tempVar.recording.durationSeconds.description"));
break;
default:
break;
}
}
@@ -105,39 +96,39 @@ static inline void populateStateSelection(QComboBox *list)
MacroConditionRecordEdit::MacroConditionRecordEdit(
QWidget *parent, std::shared_ptr<MacroConditionRecord> entryData)
: QWidget(parent)
: QWidget(parent),
_condition(new QComboBox(this)),
_duration(new DurationSelection(this))
{
_recordState = new QComboBox();
populateStateSelection(_condition);
QWidget::connect(_recordState, SIGNAL(currentIndexChanged(int)), this,
SLOT(StateChanged(int)));
populateStateSelection(_recordState);
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{recordState}}", _recordState},
};
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(DurationChanged(const Duration &)));
auto layout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.record.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
layout,
{{"{{condition}}", _condition},
{"{{duration}}", _duration}});
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionRecordEdit::StateChanged(int value)
void MacroConditionRecordEdit::ConditionChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_condition =
static_cast<MacroConditionRecord::Condition>(value);
_entryData->SetCondition(
static_cast<MacroConditionRecord::Condition>(value));
SetWidgetVisibility();
}
void MacroConditionRecordEdit::UpdateEntryData()
@@ -146,7 +137,67 @@ void MacroConditionRecordEdit::UpdateEntryData()
return;
}
_recordState->setCurrentIndex(static_cast<int>(_entryData->_condition));
_condition->setCurrentIndex(
static_cast<int>(_entryData->GetCondition()));
_duration->SetDuration(_entryData->_duration);
SetWidgetVisibility();
}
void MacroConditionRecordEdit::DurationChanged(const Duration &duration)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_duration = duration;
}
void MacroConditionRecordEdit::SetWidgetVisibility()
{
_duration->setVisible(_entryData->GetCondition() ==
MacroConditionRecord::Condition::DURATION);
}
bool SetupRecordingTimer()
{
static std::atomic_bool recordingStopped = {true};
static std::atomic_bool recordingPaused = {false};
static auto handleRecordingEvents = [](enum obs_frontend_event event,
void *) {
switch (event) {
case OBS_FRONTEND_EVENT_RECORDING_STOPPED:
recordingStopped = true;
break;
case OBS_FRONTEND_EVENT_RECORDING_STARTED:
recordingStopped = false;
break;
case OBS_FRONTEND_EVENT_RECORDING_PAUSED:
recordingPaused = true;
break;
case OBS_FRONTEND_EVENT_RECORDING_UNPAUSED:
recordingPaused = false;
break;
default:
break;
};
};
obs_frontend_add_event_callback(handleRecordingEvents, nullptr);
std::thread thread([]() {
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
if (recordingStopped) {
currentRecordingDurationInSeconds = 0;
continue;
}
if (recordingPaused) {
continue;
}
++currentRecordingDurationInSeconds;
}
});
thread.detach();
return true;
}
} // namespace advss

View File

@@ -8,8 +8,7 @@ namespace advss {
class MacroConditionRecord : public MacroCondition {
public:
MacroConditionRecord(Macro *m);
~MacroConditionRecord();
MacroConditionRecord(Macro *m) : MacroCondition(m) {}
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
@@ -23,14 +22,17 @@ public:
STOP,
PAUSE,
START,
SAVE_DONE,
DURATION,
};
Condition _condition = Condition::STOP;
void SetCondition(Condition);
Condition GetCondition() const { return _condition; }
Duration _duration;
private:
static void StopSignal(void *condition, calldata_t *);
bool _recordingSaveDone = false;
void SetupTempVars();
Condition _condition = Condition::STOP;
static bool _registered;
static const std::string id;
};
@@ -52,13 +54,15 @@ public:
}
private slots:
void StateChanged(int value);
protected:
QComboBox *_recordState;
std::shared_ptr<MacroConditionRecord> _entryData;
void ConditionChanged(int value);
void DurationChanged(const Duration &);
private:
void SetWidgetVisibility();
QComboBox *_condition;
DurationSelection *_duration;
std::shared_ptr<MacroConditionRecord> _entryData;
bool _loading = true;
};

View File

@@ -1,5 +1,4 @@
#include "macro-condition-replay-buffer.hpp"
#include "switcher-data.hpp"
#include "utility.hpp"
namespace advss {
@@ -11,35 +10,63 @@ bool MacroConditionReplayBuffer::_registered = MacroConditionFactory::Register(
MacroConditionReplayBufferEdit::Create,
"AdvSceneSwitcher.condition.replay"});
const static std::map<ReplayBufferState, std::string> ReplayBufferStates = {
{ReplayBufferState::STOP,
"AdvSceneSwitcher.condition.replay.state.stopped"},
{ReplayBufferState::START,
"AdvSceneSwitcher.condition.replay.state.started"},
{ReplayBufferState::SAVE,
"AdvSceneSwitcher.condition.replay.state.saved"},
const static std::map<MacroConditionReplayBuffer::Condition, std::string>
conditions = {
{MacroConditionReplayBuffer::Condition::STOP,
"AdvSceneSwitcher.condition.replay.state.stopped"},
{MacroConditionReplayBuffer::Condition::START,
"AdvSceneSwitcher.condition.replay.state.started"},
{MacroConditionReplayBuffer::Condition::SAVE,
"AdvSceneSwitcher.condition.replay.state.saved"},
};
static bool replayBufferSaved = false;
static std::chrono::high_resolution_clock::time_point replayBufferSaveTime{};
static bool setupReplayBufferEventHandler();
static bool replayBufferEventHandlerIsSetup = setupReplayBufferEventHandler();
static bool setupReplayBufferEventHandler()
{
static auto handleReplayBufferEvent = [](enum obs_frontend_event event,
void *) {
switch (event) {
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED:
replayBufferSaveTime =
std::chrono::high_resolution_clock::now();
break;
default:
break;
};
};
obs_frontend_add_event_callback(handleReplayBufferEvent, nullptr);
return true;
}
bool MacroConditionReplayBuffer::ReplayBufferWasSaved()
{
bool timersAreInitialized =
_saveTime.time_since_epoch().count() != 0 &&
replayBufferSaveTime.time_since_epoch().count() != 0;
bool newSaveOccurred = timersAreInitialized &&
_saveTime != replayBufferSaveTime;
_saveTime = replayBufferSaveTime;
return newSaveOccurred;
}
bool MacroConditionReplayBuffer::CheckCondition()
{
bool stateMatch = false;
switch (_state) {
case ReplayBufferState::STOP:
stateMatch = !obs_frontend_replay_buffer_active();
break;
case ReplayBufferState::START:
stateMatch = obs_frontend_replay_buffer_active();
break;
case ReplayBufferState::SAVE:
if (switcher->replayBufferSaved) {
stateMatch = true;
switcher->replayBufferSaved = false;
}
break;
case Condition::STOP:
return !obs_frontend_replay_buffer_active();
case Condition::START:
return obs_frontend_replay_buffer_active();
case Condition::SAVE:
return ReplayBufferWasSaved();
default:
break;
}
return stateMatch;
return false;
}
bool MacroConditionReplayBuffer::Save(obs_data_t *obj) const
@@ -52,37 +79,30 @@ bool MacroConditionReplayBuffer::Save(obs_data_t *obj) const
bool MacroConditionReplayBuffer::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_state = static_cast<ReplayBufferState>(obs_data_get_int(obj, "state"));
_state = static_cast<Condition>(obs_data_get_int(obj, "state"));
return true;
}
static inline void populateStateSelection(QComboBox *list)
{
for (auto entry : ReplayBufferStates) {
for (const auto &entry : conditions) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroConditionReplayBufferEdit::MacroConditionReplayBufferEdit(
QWidget *parent, std::shared_ptr<MacroConditionReplayBuffer> entryData)
: QWidget(parent)
: QWidget(parent), _state(new QComboBox())
{
_state = new QComboBox();
QWidget::connect(_state, SIGNAL(currentIndexChanged(int)), this,
SLOT(StateChanged(int)));
populateStateSelection(_state);
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{state}}", _state},
};
auto layout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.replay.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
layout, {{"{{state}}", _state}});
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
@@ -96,7 +116,8 @@ void MacroConditionReplayBufferEdit::StateChanged(int value)
}
auto lock = LockContext();
_entryData->_state = static_cast<ReplayBufferState>(value);
_entryData->_state =
static_cast<MacroConditionReplayBuffer::Condition>(value);
}
void MacroConditionReplayBufferEdit::UpdateEntryData()

View File

@@ -6,12 +6,6 @@
namespace advss {
enum class ReplayBufferState {
STOP,
START,
SAVE,
};
class MacroConditionReplayBuffer : public MacroCondition {
public:
MacroConditionReplayBuffer(Macro *m) : MacroCondition(m) {}
@@ -24,9 +18,18 @@ public:
return std::make_shared<MacroConditionReplayBuffer>(m);
}
ReplayBufferState _state = ReplayBufferState::STOP;
enum class Condition {
STOP,
START,
SAVE,
};
Condition _state = Condition::STOP;
private:
bool ReplayBufferWasSaved();
std::chrono::high_resolution_clock::time_point _saveTime = {};
static bool _registered;
static const std::string id;
};

View File

@@ -2,7 +2,6 @@
#include "utility.hpp"
#include <QProcess>
#include <QDesktopServices>
namespace advss {
@@ -28,17 +27,18 @@ bool MacroConditionRun::CheckCondition()
bool ret = false;
switch (_procStatus) {
case MacroConditionRun::Status::FAILED_TO_START:
switch (_error) {
case ProcessConfig::ProcStartError::FAILED_TO_START:
SetVariableValue("Failed to start process");
ret = false;
break;
case MacroConditionRun::Status::TIMEOUT:
case ProcessConfig::ProcStartError::TIMEOUT:
SetVariableValue("Timeout while running process");
ret = false;
break;
case MacroConditionRun::Status::OK:
ret = _checkExitCode ? _exitCode == _procExitCode : true;
case ProcessConfig::ProcStartError::CRASH:
SetVariableValue("Timeout while running process");
break;
case ProcessConfig::ProcStartError::NONE:
ret = _checkExitCode ? _exitCodeToCheck == _procExitCode : true;
SetVariableValue(std::to_string(_procExitCode));
break;
default:
@@ -56,42 +56,14 @@ bool MacroConditionRun::CheckCondition()
void MacroConditionRun::RunProcess()
{
QProcess process;
process.setWorkingDirectory(
QString::fromStdString(_procConfig.WorkingDir()));
process.start(QString::fromStdString(_procConfig.Path()),
_procConfig.Args());
int timeout = _timeout.Milliseconds();
auto result = _procConfig.StartProcessAndWait(_timeout.Milliseconds());
vblog(LOG_INFO, "run \"%s\" with a timeout of %d ms",
_procConfig.Path().c_str(), timeout);
bool procFinishedInTime = process.waitForFinished(timeout);
if (!procFinishedInTime) {
if (process.error() == QProcess::FailedToStart) {
vblog(LOG_INFO, "failed to start \"%s\"!",
_procConfig.Path().c_str());
_procStatus = Status::FAILED_TO_START;
} else {
vblog(LOG_INFO,
"timeout while running \"%s\"\nAttempting to kill process!",
_procConfig.Path().c_str());
process.kill();
process.waitForFinished();
_procStatus = Status::TIMEOUT;
}
if (std::holds_alternative<ProcessConfig::ProcStartError>(result)) {
_error = std::get<ProcessConfig::ProcStartError>(result);
} else {
_error = ProcessConfig::ProcStartError::NONE;
_procExitCode = std::get<int>(result);
}
bool validExitCode = process.exitStatus() == QProcess::NormalExit;
if ((_checkExitCode && !validExitCode) || !procFinishedInTime) {
_threadDone = true;
return;
}
_procExitCode = process.exitCode();
_procStatus = Status::OK;
_threadDone = true;
}
@@ -100,7 +72,7 @@ bool MacroConditionRun::Save(obs_data_t *obj) const
MacroCondition::Save(obj);
_procConfig.Save(obj);
obs_data_set_bool(obj, "checkExitCode", _checkExitCode);
obs_data_set_int(obj, "exitCode", _exitCode);
obs_data_set_int(obj, "exitCode", _exitCodeToCheck);
_timeout.Save(obj, "timeout");
return true;
}
@@ -110,7 +82,7 @@ bool MacroConditionRun::Load(obs_data_t *obj)
MacroCondition::Load(obj);
_procConfig.Load(obj);
_checkExitCode = obs_data_get_bool(obj, "checkExitCode");
_exitCode = obs_data_get_int(obj, "exitCode");
_exitCodeToCheck = obs_data_get_int(obj, "exitCode");
_timeout.Load(obj, "timeout");
return true;
}
@@ -174,7 +146,7 @@ void MacroConditionRunEdit::UpdateEntryData()
_procConfig->SetProcessConfig(_entryData->_procConfig);
_timeout->SetDuration(_entryData->_timeout);
_checkExitCode->setChecked(_entryData->_checkExitCode);
_exitCode->setValue(_entryData->_exitCode);
_exitCode->setValue(_entryData->_exitCodeToCheck);
}
void MacroConditionRunEdit::TimeoutChanged(const Duration &dur)
@@ -204,7 +176,7 @@ void MacroConditionRunEdit::ExitCodeChanged(int exitCode)
}
auto lock = LockContext();
_entryData->_exitCode = exitCode;
_entryData->_exitCodeToCheck = exitCode;
}
void MacroConditionRunEdit::ProcessConfigChanged(const ProcessConfig &conf)

View File

@@ -25,22 +25,15 @@ public:
ProcessConfig _procConfig;
bool _checkExitCode = true;
int _exitCode = 0;
int _exitCodeToCheck = 0;
Duration _timeout = Duration(0.1);
private:
enum class Status {
NONE,
FAILED_TO_START,
TIMEOUT,
OK,
};
void RunProcess();
std::thread _thread;
std::atomic_bool _threadDone{true};
Status _procStatus = Status::NONE;
ProcessConfig::ProcStartError _error =
ProcessConfig::ProcStartError::NONE;
int _procExitCode = 0;
static bool _registered;

View File

@@ -1,5 +1,5 @@
#include "macro-condition-scene.hpp"
#include "switcher-data.hpp"
#include "scene-switch-helpers.hpp"
#include "utility.hpp"
namespace advss {
@@ -53,23 +53,22 @@ static OBSWeakSource getCurrentSceneHelper(bool useTransitionTargetScene)
obs_source_release(current);
return weak;
}
return switcher->currentScene;
return GetCurrentScene();
}
static OBSWeakSource getPreviousSceneHelper(bool useTransitionTargetScene)
{
if (switcher->AnySceneTransitionStarted() && useTransitionTargetScene) {
return switcher->currentScene;
if (AnySceneTransitionStarted() && useTransitionTargetScene) {
return GetCurrentScene();
}
return switcher->previousScene;
return GetPreviousScene();
}
bool MacroConditionScene::CheckCondition()
{
bool sceneChanged = _lastSceneChangeTime !=
switcher->lastSceneChangeTime;
bool sceneChanged = _lastSceneChangeTime != GetLastSceneChangeTime();
if (sceneChanged) {
_lastSceneChangeTime = switcher->lastSceneChangeTime;
_lastSceneChangeTime = GetLastSceneChangeTime();
}
switch (_type) {
@@ -95,14 +94,14 @@ bool MacroConditionScene::CheckCondition()
return scene == _scene.GetScene(false);
}
case Type::CHANGED:
SetVariableValue(GetWeakSourceName(switcher->currentScene));
SetVariableValue(GetWeakSourceName(GetCurrentScene()));
SetTempVarValue("current",
GetWeakSourceName(switcher->currentScene));
GetWeakSourceName(GetCurrentScene()));
return sceneChanged;
case Type::NOT_CHANGED:
SetVariableValue(GetWeakSourceName(switcher->currentScene));
SetVariableValue(GetWeakSourceName(GetCurrentScene()));
SetTempVarValue("current",
GetWeakSourceName(switcher->currentScene));
GetWeakSourceName(GetCurrentScene()));
return !sceneChanged;
case Type::CURRENT_PATTERN: {
auto scene = getCurrentSceneHelper(_useTransitionTargetScene);
@@ -189,8 +188,8 @@ bool MacroConditionScene::Load(obs_data_t *obj)
break;
default:
blog(LOG_WARNING,
"failed to convert scene condition type (%d) for macro %s",
oldType, GetMacro()->Name().c_str());
"failed to convert scene condition type (%d)",
oldType);
_type = Type::CURRENT;
break;
}

View File

@@ -1,5 +1,4 @@
#include "macro-condition-streaming.hpp"
#include "switcher-data.hpp"
#include "utility.hpp"
namespace advss {
@@ -25,6 +24,32 @@ const static std::map<MacroConditionStream::Condition, std::string>
"AdvSceneSwitcher.condition.stream.state.keyFrameInterval"},
};
static bool setupStreamingEventHandler();
static bool steamingEventHandlerIsSetup = setupStreamingEventHandler();
static std::chrono::high_resolution_clock::time_point streamStartTime{};
static std::chrono::high_resolution_clock::time_point streamStopTime{};
bool setupStreamingEventHandler()
{
static auto handleStreamingEvents = [](enum obs_frontend_event event,
void *) {
switch (event) {
case OBS_FRONTEND_EVENT_STREAMING_STARTING:
streamStartTime =
std::chrono::high_resolution_clock::now();
break;
case OBS_FRONTEND_EVENT_STREAMING_STOPPING:
streamStopTime =
std::chrono::high_resolution_clock::now();
break;
default:
break;
};
};
obs_frontend_add_event_callback(handleStreamingEvents, nullptr);
return true;
}
int MacroConditionStream::GetKeyFrameInterval()
{
const auto configPath = GetPathInProfileDir("streamEncoder.json");
@@ -42,10 +67,8 @@ bool MacroConditionStream::CheckCondition()
{
bool match = false;
bool streamStarting = switcher->lastStreamStartingTime !=
_lastStreamStartingTime;
bool streamStopping = switcher->lastStreamStoppingTime !=
_lastStreamStoppingTime;
bool streamStarting = streamStartTime != _lastStreamStartingTime;
bool streamStopping = streamStopTime != _lastStreamStoppingTime;
switch (_condition) {
case Condition::STOP:
@@ -68,10 +91,10 @@ bool MacroConditionStream::CheckCondition()
}
if (streamStarting) {
_lastStreamStartingTime = switcher->lastStreamStartingTime;
_lastStreamStartingTime = streamStartTime;
}
if (streamStopping) {
_lastStreamStoppingTime = switcher->lastStreamStoppingTime;
_lastStreamStoppingTime = streamStopTime;
}
return match;
}

View File

@@ -1,5 +1,5 @@
#include "macro-condition-transition.hpp"
#include "switcher-data.hpp"
#include "scene-switch-helpers.hpp"
#include "utility.hpp"
namespace advss {
@@ -50,8 +50,8 @@ static bool isTargetScene(OBSWeakSource &target)
bool MacroConditionTransition::CheckCondition()
{
bool anyTransitionEnded = _lastTransitionEndTime !=
switcher->lastTransitionEndTime;
bool anyTransitionStarted = switcher->AnySceneTransitionStarted();
GetLastTransitionEndTime();
bool anyTransitionStarted = AnySceneTransitionStarted();
bool transitionStarted = false;
bool transitionEnded = false;
@@ -82,7 +82,7 @@ bool MacroConditionTransition::CheckCondition()
break;
case TransitionCondition::TRANSITION_SOURCE:
ret = anyTransitionStarted &&
_scene.GetScene() == switcher->currentScene;
_scene.GetScene() == GetCurrentScene();
break;
case TransitionCondition::TRANSITION_TARGET: {
auto scene = _scene.GetScene();
@@ -101,7 +101,7 @@ bool MacroConditionTransition::CheckCondition()
_ended = false;
}
if (anyTransitionEnded) {
_lastTransitionEndTime = switcher->lastTransitionEndTime;
_lastTransitionEndTime = GetLastTransitionEndTime();
}
return ret;

View File

@@ -1,5 +1,4 @@
#include "macro-condition-websocket.hpp"
#include "switcher-data.hpp"
#include "utility.hpp"
#include <regex>
@@ -34,28 +33,24 @@ static bool matchRegex(const RegexConfig &conf, const std::string &msg,
bool MacroConditionWebsocket::CheckCondition()
{
const std::vector<std::string> *messages = nullptr;
std::vector<std::string> messages;
switch (_type) {
case MacroConditionWebsocket::Type::REQUEST:
messages = &switcher->websocketMessages;
messages = GetWebsocketMessages();
break;
case MacroConditionWebsocket::Type::EVENT: {
auto connection = _connection.lock();
if (!connection) {
return false;
}
messages = &connection->Events();
messages = connection->Events();
break;
}
default:
break;
}
if (!messages) {
return false;
}
for (const auto &msg : *messages) {
for (const auto &msg : messages) {
if (_regex.Enabled()) {
if (matchRegex(_regex, msg, _message)) {
SetVariableValue(msg);

View File

@@ -1,5 +1,5 @@
#include "macro-condition-window.hpp"
#include "switcher-data.hpp"
#include "plugin-state-helpers.hpp"
#include "platform-funcs.hpp"
#include "utility.hpp"
@@ -43,7 +43,8 @@ static bool windowContainsText(const std::string &window,
bool MacroConditionWindow::WindowMatchesRequirements(
const std::string &window) const
{
const bool focusCheckOK = (!_focus || window == switcher->currentTitle);
const bool focusCheckOK =
(!_focus || window == ForegroundWindowTitle());
if (!focusCheckOK) {
return false;
}
@@ -104,13 +105,13 @@ void MacroConditionWindow::SetVariableValueBasedOnMatch(
auto text = GetTextInWindow(matchWindow);
SetVariableValue(text.value_or(""));
} else {
SetVariableValue(switcher->currentTitle);
SetVariableValue(ForegroundWindowTitle());
}
}
static bool foregroundWindowChanged()
{
return switcher->currentTitle != switcher->lastTitle;
return ForegroundWindowTitle() != PreviousForegroundWindowTitle();
}
bool MacroConditionWindow::CheckCondition()
@@ -176,6 +177,7 @@ std::string MacroConditionWindow::GetShortDesc() const
void MacroConditionWindow::SetupTempVars()
{
MacroCondition::SetupTempVars();
AddTempvar(
"window",
obs_module_text("AdvSceneSwitcher.tempVar.window.window"),
@@ -431,7 +433,7 @@ void MacroConditionWindowEdit::WindowFocusChanged(int state)
void MacroConditionWindowEdit::UpdateFocusWindow()
{
_focusWindow->setText(QString::fromStdString(switcher->currentTitle));
_focusWindow->setText(QString::fromStdString(ForegroundWindowTitle()));
}
void MacroConditionWindowEdit::SetWidgetVisibility()

View File

@@ -0,0 +1,59 @@
#include "macro-helpers.hpp"
#include "plugin-state-helpers.hpp"
namespace advss {
static std::atomic_bool abortMacroWait = {false};
static std::atomic_bool macroSceneSwitched = {false};
static std::atomic_int shutdownConditionCount = {0};
std::condition_variable &GetMacroWaitCV()
{
static std::condition_variable cv;
return cv;
}
std::condition_variable &GetMacroTransitionCV()
{
static std::condition_variable cv;
return cv;
}
std::atomic_bool &MacroWaitShouldAbort()
{
return abortMacroWait;
}
void SetMacroAbortWait(bool value)
{
abortMacroWait = value;
}
bool ShutdownCheckIsNecessary()
{
return shutdownConditionCount > 0;
}
std::atomic_int &GetShutdownConditionCount()
{
return shutdownConditionCount;
}
void SetMacroSwitchedScene(bool value)
{
static bool setupDone = false;
if (!setupDone) {
// Will always be called with switcher lock already held
AddIntervalResetStep([]() { macroSceneSwitched = false; },
false);
setupDone = true;
}
macroSceneSwitched = value;
}
bool MacroSwitchedScene()
{
return macroSceneSwitched;
}
} // namespace advss

View File

@@ -0,0 +1,21 @@
#pragma once
#include <atomic>
#include <condition_variable>
#include <string_view>
namespace advss {
constexpr std::string_view GetSceneSwitchActionId()
{
return "scene_switch";
}
std::condition_variable &GetMacroWaitCV();
std::condition_variable &GetMacroTransitionCV();
std::atomic_bool &MacroWaitShouldAbort();
void SetMacroAbortWait(bool);
bool ShutdownCheckIsNecessary();
std::atomic_int &GetShutdownConditionCount();
void SetMacroSwitchedScene(bool value);
bool MacroSwitchedScene();
} // namespace advss

View File

@@ -9,6 +9,8 @@
namespace advss {
static MacroProperties macroProperties;
void MacroProperties::Save(obs_data_t *obj) const
{
auto data = obs_data_create();
@@ -325,4 +327,19 @@ bool MacroPropertiesDialog::AskForSettings(QWidget *parent,
return true;
}
MacroProperties &GetGlobalMacroProperties()
{
return macroProperties;
}
void SaveGlobalMacroProperties(obs_data_t *obj)
{
macroProperties.Save(obj);
}
void LoadGlobalMacroProperties(obs_data_t *obj)
{
macroProperties.Load(obj);
}
} // namespace advss

View File

@@ -68,4 +68,8 @@ private:
int _conditionsFalseTextRow = -1;
};
MacroProperties &GetGlobalMacroProperties();
void SaveGlobalMacroProperties(obs_data_t *obj);
void LoadGlobalMacroProperties(obs_data_t *obj);
} // namespace advss

View File

@@ -44,8 +44,28 @@ MacroSegmentList::~MacroSegmentList()
}
}
static bool posIsInScrollbar(const QScrollBar *scrollbar, const QPoint &pos)
{
if (!scrollbar) {
return false;
}
if (!scrollbar->isVisible()) {
return false;
}
const auto geo = scrollbar->geometry();
const auto globalGeo = QRect(scrollbar->mapToGlobal(geo.topLeft()),
scrollbar->mapToGlobal(geo.bottomRight()));
return globalGeo.contains(pos);
}
int MacroSegmentList::GetDragIndex(const QPoint &pos)
{
// Don't drag widget when interacting with the scrollbars
if (posIsInScrollbar(horizontalScrollBar(), mapTo(this, pos)) ||
posIsInScrollbar(verticalScrollBar(), mapTo(this, pos))) {
return -1;
}
for (int idx = 0; idx < _contentLayout->count(); ++idx) {
auto item = _contentLayout->itemAt(idx);
if (!item) {
@@ -161,7 +181,7 @@ void MacroSegmentList::mousePressEvent(QMouseEvent *event)
#else
_dragPosition = GetDragIndex(event->globalPosition().toPoint());
#endif
emit SelectionChagned(_dragPosition);
emit SelectionChanged(_dragPosition);
} else {
_dragPosition = -1;
}

View File

@@ -28,7 +28,7 @@ public:
QVBoxLayout *ContentLayout() { return _contentLayout; }
signals:
void SelectionChagned(int idx);
void SelectionChanged(int idx);
void Reorder(int source, int target);
protected:

View File

@@ -146,7 +146,6 @@ void MacroSegment::InvalidateTempVarValues()
std::optional<const TempVariable>
MacroSegment::GetTempVar(const std::string &id) const
{
TempVariable *result = nullptr;
for (auto &var : _tempVariables) {
if (var.ID() == id) {
return var;

View File

@@ -4,7 +4,7 @@
// so it makes sense to include them here:
#include "log-helper.hpp"
#include "obs-module-helper.hpp"
#include "sync-helper.hpp"
#include "sync-helpers.hpp"
#include "temp-variable.hpp"
#include <QWidget>

View File

@@ -1,6 +1,6 @@
#include "macro-selection.hpp"
#include "macro.hpp"
#include "advanced-scene-switcher.hpp"
#include "switcher-data.hpp"
#include "utility.hpp"
#include <QStandardItemModel>
@@ -12,7 +12,7 @@ MacroSelection::MacroSelection(QWidget *parent)
: FilterComboBox(parent,
obs_module_text("AdvSceneSwitcher.selectMacro"))
{
for (const auto &m : switcher->macros) {
for (const auto &m : GetMacros()) {
if (m->IsGroup()) {
continue;
}

View File

@@ -63,7 +63,7 @@ bool AdvSceneSwitcher::AddNewMacro(std::shared_ptr<Macro> &res,
}
res = std::make_shared<Macro>(
name, switcher->macroProperties._newMacroRegisterHotkeys);
name, GetGlobalMacroProperties()._newMacroRegisterHotkeys);
return true;
}
@@ -197,8 +197,8 @@ static void addGroupSubitems(std::vector<std::shared_ptr<Macro>> &macros,
subitems.reserve(group->GroupSize());
// Find all subitems
for (auto it = switcher->macros.begin(); it < switcher->macros.end();
it++) {
auto allMacros = GetMacros();
for (auto it = allMacros.begin(); it < allMacros.end(); it++) {
if ((*it)->Name() == group->Name()) {
for (uint32_t i = 1; i <= group->GroupSize(); i++) {
subitems.emplace_back(*std::next(it, i));
@@ -235,21 +235,18 @@ void AdvSceneSwitcher::ExportMacros()
}
}
auto data = obs_data_create();
auto macroArray = obs_data_array_create();
OBSDataAutoRelease data = obs_data_create();
OBSDataArrayAutoRelease macroArray = obs_data_array_create();
for (const auto &macro : macros) {
obs_data_t *obj = obs_data_create();
OBSDataAutoRelease obj = obs_data_create();
macro->Save(obj);
obs_data_array_push_back(macroArray, obj);
obs_data_release(obj);
}
obs_data_set_array(data, "macros", macroArray);
switcher->SaveVariables(data);
obs_data_array_release(macroArray);
SaveVariables(data);
obs_data_set_string(data, "version", g_GIT_TAG);
auto json = obs_data_get_json(data);
QString exportString(json);
obs_data_release(data);
MacroExportImportDialog::ExportMacros(exportString);
}
@@ -268,8 +265,9 @@ void AdvSceneSwitcher::SetupMacroSegmentSelection(MacroSection type, int idx)
return;
}
MacroSegmentList *setList, *resetList1, *resetList2;
int *setIdx, *resetIdx1, *resetIdx2;
MacroSegmentList *setList = nullptr, *resetList1 = nullptr,
*resetList2 = nullptr;
int *setIdx = nullptr, *resetIdx1 = nullptr, *resetIdx2 = nullptr;
std::deque<std::shared_ptr<MacroSegment>> segements;
switch (type) {
@@ -384,7 +382,7 @@ static void importVariables(obs_data_t *obj)
if (variableWithNameExists(var->Name())) {
continue;
}
switcher->variables.emplace_back(var);
GetVariables().emplace_back(var);
}
}
@@ -394,7 +392,8 @@ void AdvSceneSwitcher::ImportMacros()
if (!MacroExportImportDialog::ImportMacros(json)) {
return;
}
auto data = obs_data_create_from_json(json.toStdString().c_str());
OBSDataAutoRelease data =
obs_data_create_from_json(json.toStdString().c_str());
if (!data) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.macroTab.import.invalid"));
@@ -410,28 +409,27 @@ void AdvSceneSwitcher::ImportMacros()
version);
}
auto array = obs_data_get_array(data, "macros");
OBSDataArrayAutoRelease array = obs_data_get_array(data, "macros");
size_t count = obs_data_array_count(array);
int groupSize = 0;
std::shared_ptr<Macro> group;
std::vector<std::shared_ptr<Macro>> importedMacros;
auto lock = LockContext();
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(array, i);
OBSDataAutoRelease array_obj = obs_data_array_item(array, i);
auto macro = std::make_shared<Macro>();
macro->Load(array_obj);
macro->PostLoad();
if (macroNameExists(macro->Name()) &&
!ResolveMacroImportNameConflict(macro)) {
obs_data_release(array_obj);
groupSize--;
continue;
}
switcher->macros.emplace_back(macro);
importedMacros.emplace_back(macro);
GetMacros().emplace_back(macro);
if (groupSize > 0 && !macro->IsGroup()) {
Macro::PrepareMoveToGroup(group, macro);
groupSize--;
@@ -445,14 +443,14 @@ void AdvSceneSwitcher::ImportMacros()
// to the group as they come up.
macro->ResetGroupSize();
}
obs_data_release(array_obj);
}
obs_data_array_release(array);
obs_data_release(data);
ui->macros->Reset(switcher->macros,
switcher->macroProperties._highlightExecuted);
for (const auto &macro : importedMacros) {
macro->PostLoad();
}
ui->macros->Reset(GetMacros(),
GetGlobalMacroProperties()._highlightExecuted);
}
void AdvSceneSwitcher::on_macroName_editingFinished()
@@ -700,8 +698,7 @@ void AdvSceneSwitcher::MacroSelectionAboutToChange()
// actions nor elseActions being visible when the condition <-> action
// splitter is moved
if (elsePos[0] == 0 && elsePos[1] == 0) {
macro->SetElseActionSplitterPosition(QList<int>()
<< 999999 << 0);
maximizeFirstSplitterEntry(ui->macroElseActionSplitter);
return;
}
macro->SetElseActionSplitterPosition(
@@ -732,8 +729,8 @@ void AdvSceneSwitcher::MacroSelectionChanged()
void AdvSceneSwitcher::HighlightOnChange()
{
if (!switcher->macroProperties._highlightActions &&
!switcher->macroProperties._highlightExecuted) {
if (!GetGlobalMacroProperties()._highlightActions &&
!GetGlobalMacroProperties()._highlightExecuted) {
return;
}
@@ -750,13 +747,13 @@ void AdvSceneSwitcher::HighlightOnChange()
void AdvSceneSwitcher::on_macroProperties_clicked()
{
MacroProperties prop = switcher->macroProperties;
MacroProperties prop = GetGlobalMacroProperties();
bool accepted = MacroPropertiesDialog::AskForSettings(
this, prop, GetSelectedMacro().get());
if (!accepted) {
return;
}
switcher->macroProperties = prop;
GetGlobalMacroProperties() = prop;
emit HighlightMacrosChanged(prop._highlightExecuted);
emit HighlightActionsChanged(prop._highlightActions);
emit HighlightConditionsChanged(prop._highlightConditions);
@@ -790,11 +787,13 @@ bool shouldRestoreSplitter(const QList<int> &pos)
void AdvSceneSwitcher::SetupMacroTab()
{
if (switcher->macros.size() == 0 && !switcher->disableHints) {
ui->macroElseActions->installEventFilter(this);
if (GetMacros().size() == 0 && !switcher->disableHints) {
addPulse = PulseWidget(ui->macroAdd, QColor(Qt::green));
}
ui->macros->Reset(switcher->macros,
switcher->macroProperties._highlightExecuted);
ui->macros->Reset(GetMacros(),
GetGlobalMacroProperties()._highlightExecuted);
connect(ui->macros, SIGNAL(MacroSelectionAboutToChange()), this,
SLOT(MacroSelectionAboutToChange()));
connect(ui->macros, SIGNAL(MacroSelectionChanged()), this,
@@ -803,21 +802,21 @@ void AdvSceneSwitcher::SetupMacroTab()
ui->conditionsList->SetHelpMsg(
obs_module_text("AdvSceneSwitcher.macroTab.editConditionHelp"));
connect(ui->conditionsList, &MacroSegmentList::SelectionChagned, this,
connect(ui->conditionsList, &MacroSegmentList::SelectionChanged, this,
&AdvSceneSwitcher::MacroConditionSelectionChanged);
connect(ui->conditionsList, &MacroSegmentList::Reorder, this,
&AdvSceneSwitcher::MacroConditionReorder);
ui->actionsList->SetHelpMsg(
obs_module_text("AdvSceneSwitcher.macroTab.editActionHelp"));
connect(ui->actionsList, &MacroSegmentList::SelectionChagned, this,
connect(ui->actionsList, &MacroSegmentList::SelectionChanged, this,
&AdvSceneSwitcher::MacroActionSelectionChanged);
connect(ui->actionsList, &MacroSegmentList::Reorder, this,
&AdvSceneSwitcher::MacroActionReorder);
ui->elseActionsList->SetHelpMsg(obs_module_text(
"AdvSceneSwitcher.macroTab.editElseActionHelp"));
connect(ui->elseActionsList, &MacroSegmentList::SelectionChagned, this,
connect(ui->elseActionsList, &MacroSegmentList::SelectionChanged, this,
&AdvSceneSwitcher::MacroElseActionSelectionChanged);
connect(ui->elseActionsList, &MacroSegmentList::Reorder, this,
&AdvSceneSwitcher::MacroElseActionReorder);
@@ -862,6 +861,8 @@ void AdvSceneSwitcher::SetupMacroTab()
SetButtonIcon(ui->conditionTop, (pathPrefix + "DoubleUp.svg").c_str());
SetButtonIcon(ui->conditionBottom,
(pathPrefix + "DoubleDown.svg").c_str());
SetButtonIcon(ui->toggleElseActions,
(pathPrefix + "NotEqual.svg").c_str());
// Reserve more space for macro edit area than for the macro list
ui->macroListMacroEditSplitter->setStretchFactor(0, 1);
@@ -988,13 +989,12 @@ void AdvSceneSwitcher::CopyMacro()
return;
}
obs_data_t *data = obs_data_create();
OBSDataAutoRelease data = obs_data_create();
macro->Save(data);
newMacro->Load(data);
newMacro->PostLoad();
newMacro->SetName(name);
Macro::PrepareMoveToGroup(macro->Parent(), newMacro);
obs_data_release(data);
ui->macros->Add(newMacro, macro);
ui->macroAdd->disconnect(addPulse);
@@ -1100,6 +1100,31 @@ void AdvSceneSwitcher::MaximizeConditions()
MinimizeActions();
}
void AdvSceneSwitcher::on_toggleElseActions_clicked()
{
auto elsePosition = ui->macroElseActionSplitter->sizes();
if (elsePosition[1] == 0) {
centerSplitterPosition(ui->macroElseActionSplitter);
return;
}
maximizeFirstSplitterEntry(ui->macroElseActionSplitter);
}
void AdvSceneSwitcher::SetElseActionsStateToHidden()
{
ui->toggleElseActions->setToolTip(obs_module_text(
"AdvSceneSwitcher.macroTab.toggleElseActions.show.tooltip"));
ui->toggleElseActions->setChecked(false);
}
void AdvSceneSwitcher::SetElseActionsStateToVisible()
{
ui->toggleElseActions->setToolTip(obs_module_text(
"AdvSceneSwitcher.macroTab.toggleElseActions.hide.tooltip"));
ui->toggleElseActions->setChecked(true);
}
bool AdvSceneSwitcher::MacroTabIsInFocus()
{
return isActiveWindow() && isAncestorOf(focusWidget()) &&

View File

@@ -1,7 +1,7 @@
#include "macro-tree.hpp"
#include "macro.hpp"
#include "sync-helpers.hpp"
#include "utility.hpp"
#include "switcher-data.hpp"
#include <obs.h>
#include <string>
@@ -395,7 +395,7 @@ CountItemsVisibleInModel(const std::deque<std::shared_ptr<Macro>> &macros)
void MacroTreeModel::Add(std::shared_ptr<Macro> item)
{
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
auto idx = CountItemsVisibleInModel(_macros);
beginInsertRows(QModelIndex(), idx, idx);
_macros.emplace_back(item);
@@ -408,7 +408,7 @@ void MacroTreeModel::Add(std::shared_ptr<Macro> item)
void MacroTreeModel::Remove(std::shared_ptr<Macro> item)
{
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
auto uiStartIdx = GetItemModelIndex(item);
if (uiStartIdx == -1) {
return;
@@ -643,7 +643,7 @@ void MacroTreeModel::GroupSelectedItems(QModelIndexList &indices)
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
QString name = GetNewGroupName();
std::vector<std::shared_ptr<Macro>> itemsToGroup;
itemsToGroup.reserve(indices.size());
@@ -691,7 +691,7 @@ void MacroTreeModel::UngroupSelectedGroups(QModelIndexList &indices)
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
for (int i = indices.count() - 1; i >= 0; i--) {
std::shared_ptr<Macro> item = _macros[ModelIndexToMacroIndex(
indices[i].row(), _macros)];
@@ -1055,7 +1055,7 @@ void MacroTree::dropEvent(QDropEvent *event)
}
// Move items in backend
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
int to = row;
try {
to = ModelIndexToMacroIndex(row, items);
@@ -1181,7 +1181,7 @@ void MacroTree::Remove(std::shared_ptr<Macro> item) const
void MacroTree::Up(std::shared_ptr<Macro> item) const
{
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
auto above = GetModel()->Neighbor(item, true);
if (!above) {
return;
@@ -1207,7 +1207,7 @@ void MacroTree::Up(std::shared_ptr<Macro> item) const
void MacroTree::Down(std::shared_ptr<Macro> item) const
{
std::lock_guard<std::mutex> lock(switcher->m);
auto lock = LockContext();
auto below = GetModel()->Neighbor(item, false);
if (!below) {
return;

View File

@@ -1,20 +1,24 @@
#include "macro.hpp"
#include "macro-action-edit.hpp"
#include "macro-condition-edit.hpp"
#include "macro-action-factory.hpp"
#include "macro-condition-factory.hpp"
#include "macro-dock.hpp"
#include "macro-action-scene-switch.hpp"
#include "switcher-data.hpp"
#include "macro-helpers.hpp"
#include "plugin-state-helpers.hpp"
#include "hotkey.hpp"
#include "sync-helpers.hpp"
#include "utility.hpp"
#include <limits>
#undef max
#include <chrono>
#include <unordered_map>
#include <QAction>
#include <QMainWindow>
namespace advss {
constexpr int perfLogThreshold = 300;
static constexpr int perfLogThreshold = 300;
static std::deque<std::shared_ptr<Macro>> macros;
Macro::Macro(const std::string &name, const bool addHotkey)
{
@@ -33,7 +37,7 @@ Macro::~Macro()
// Keep the dock widgets in case of shutdown so they can be restored by
// OBS on startup
if (!switcher->obsIsShuttingDown) {
if (!OBSIsShuttingDown()) {
RemoveDock();
}
}
@@ -53,9 +57,8 @@ Macro::CreateGroup(const std::string &name,
void Macro::RemoveGroup(std::shared_ptr<Macro> group)
{
auto it = std::find(switcher->macros.begin(), switcher->macros.end(),
group);
if (it == switcher->macros.end()) {
auto it = std::find(macros.begin(), macros.end(), group);
if (it == macros.end()) {
return;
}
@@ -65,12 +68,12 @@ void Macro::RemoveGroup(std::shared_ptr<Macro> group)
(*m)->SetParent(nullptr);
}
switcher->macros.erase(it);
macros.erase(it);
}
void Macro::PrepareMoveToGroup(Macro *group, std::shared_ptr<Macro> item)
{
for (auto &m : switcher->macros) {
for (const auto &m : macros) {
if (m.get() == group) {
PrepareMoveToGroup(m, item);
return;
@@ -149,6 +152,7 @@ bool Macro::CeckMatch()
}
break;
case LogicType::OR_NOT:
_matched = _matched || !cond;
if (!cond) {
c->SetHighlight();
}
@@ -350,7 +354,7 @@ void Macro::AddHelperThread(std::thread &&newThread)
void Macro::Stop()
{
_stop = true;
switcher->macroWaitCv.notify_all();
GetMacroWaitCV().notify_all();
for (auto &t : _helperThreads) {
if (t.joinable()) {
t.join();
@@ -742,15 +746,13 @@ bool Macro::PostLoad()
bool Macro::SwitchesScene() const
{
MacroActionSwitchScene temp(nullptr);
auto sceneSwitchId = temp.GetId();
for (const auto &a : _actions) {
if (a->GetId() == sceneSwitchId) {
if (a->GetId() == GetSceneSwitchActionId()) {
return true;
}
}
for (const auto &a : _elseActions) {
if (a->GetId() == sceneSwitchId) {
if (a->GetId() == GetSceneSwitchActionId()) {
return true;
}
}
@@ -946,7 +948,7 @@ void Macro::EnableDock(bool value)
// geometry set here.
// The function calls here are only intended to attempt to restore the
// dock status when switching scene collections.
if (switcher->startupLoadDone) {
if (InitialLoadIsComplete()) {
_dock->setVisible(_dockIsVisible);
if (window->dockWidgetArea(_dock) != _dockArea) {
window->addDockWidget(_dockArea, _dock);
@@ -1162,12 +1164,10 @@ void Macro::SetHotkeysDesc() const
_name, _togglePauseHotkey);
}
void SwitcherData::SaveMacros(obs_data_t *obj)
void SaveMacros(obs_data_t *obj)
{
switcher->macroProperties.Save(obj);
obs_data_array_t *macroArray = obs_data_array_create();
for (auto &m : macros) {
for (const auto &m : macros) {
obs_data_t *array_obj = obs_data_create();
m->Save(array_obj);
@@ -1179,10 +1179,9 @@ void SwitcherData::SaveMacros(obs_data_t *obj)
obs_data_array_release(macroArray);
}
void SwitcherData::LoadMacros(obs_data_t *obj)
void LoadMacros(obs_data_t *obj)
{
Hotkey::ClearAllHotkeys();
switcher->macroProperties.Load(obj);
macros.clear();
obs_data_array_t *macroArray = obs_data_get_array(obj, "macros");
@@ -1199,7 +1198,7 @@ void SwitcherData::LoadMacros(obs_data_t *obj)
int groupCount = 0;
std::shared_ptr<Macro> group;
std::vector<std::shared_ptr<Macro>> invalidGroups;
for (auto &m : macros) {
for (const auto &m : macros) {
if (groupCount && m->IsGroup()) {
blog(LOG_ERROR,
"nested group detected - will delete \"%s\"",
@@ -1234,23 +1233,28 @@ void SwitcherData::LoadMacros(obs_data_t *obj)
}
}
bool SwitcherData::CheckMacros()
std::deque<std::shared_ptr<Macro>> &GetMacros()
{
bool ret = false;
for (auto &m : macros) {
return macros;
}
bool CheckMacros()
{
bool matchFound = false;
for (const auto &m : macros) {
if (m->CeckMatch() || m->ElseActions().size() > 0) {
ret = true;
matchFound = true;
// This has to be performed here for now as actions are
// not performed immediately after checking conditions.
if (m->SwitchesScene()) {
switcher->macroSceneSwitched = true;
SetMacroSwitchedScene(true);
}
}
}
return ret;
return matchFound;
}
bool SwitcherData::RunMacros()
bool RunMacros()
{
// Create copy of macro list as elements might be removed, inserted, or
// reordered while macros are currently being executed.
@@ -1268,15 +1272,16 @@ bool SwitcherData::RunMacros()
// the constructor of AdvSceneSwitcher() to complete.
// The constructor of AdvSceneSwitcher() cannot continue however as it
// cannot lock the main switcher mutex.
if (GetLock()) {
GetLock()->unlock();
auto lock = GetLoopLock();
if (lock) {
lock->unlock();
}
for (auto &m : runPhaseMacros) {
if (!m || !m->ShouldRunActions()) {
continue;
}
if (firstInterval && m->SkipExecOnStart()) {
if (IsFirstInterval() && m->SkipExecOnStart()) {
blog(LOG_INFO,
"skip execution of macro \"%s\" at startup",
m->Name().c_str());
@@ -1287,15 +1292,15 @@ bool SwitcherData::RunMacros()
blog(LOG_WARNING, "abort macro: %s", m->Name().c_str());
}
}
if (GetLock()) {
GetLock()->lock();
if (lock) {
lock->lock();
}
return true;
}
Macro *GetMacroByName(const char *name)
{
for (auto &m : switcher->macros) {
for (const auto &m : macros) {
if (m->Name() == name) {
return m.get();
}
@@ -1311,7 +1316,7 @@ Macro *GetMacroByQString(const QString &name)
std::weak_ptr<Macro> GetWeakMacroByName(const char *name)
{
for (auto &m : switcher->macros) {
for (const auto &m : macros) {
if (m->Name() == name) {
return m;
}
@@ -1322,7 +1327,7 @@ std::weak_ptr<Macro> GetWeakMacroByName(const char *name)
void InvalidateMacroTempVarValues()
{
for (auto &m : switcher->macros) {
for (const auto &m : macros) {
m->InvalidateTempVarValues();
}
}

View File

@@ -202,6 +202,11 @@ private:
QAction *_dockAction = nullptr;
};
void LoadMacros(obs_data_t *obj);
void SaveMacros(obs_data_t *obj);
std::deque<std::shared_ptr<Macro>> &GetMacros();
bool CheckMacros();
bool RunMacros();
Macro *GetMacroByName(const char *name);
Macro *GetMacroByQString(const QString &name);
std::weak_ptr<Macro> GetWeakMacroByName(const char *name);

View File

@@ -19,8 +19,7 @@ bool MacroConditionMidi::CheckCondition()
for (const auto &m : *messages) {
if (m.Matches(_message)) {
SetVariableValue(std::to_string(m.Note()) + " " +
std::to_string(m.Value()));
SetVariableValues(m);
return true;
}
}
@@ -49,6 +48,36 @@ std::string MacroConditionMidi::GetShortDesc() const
return _device.Name();
}
void MacroConditionMidi::SetupTempVars()
{
MacroCondition::SetupTempVars();
AddTempvar("type",
obs_module_text("AdvSceneSwitcher.tempVar.midi.type"));
AddTempvar("channel",
obs_module_text("AdvSceneSwitcher.tempVar.midi.channel"));
AddTempvar("note",
obs_module_text("AdvSceneSwitcher.tempVar.midi.note"));
AddTempvar("value1",
obs_module_text("AdvSceneSwitcher.tempVar.midi.value1"));
AddTempvar("value2",
obs_module_text("AdvSceneSwitcher.tempVar.midi.value2"));
}
void MacroConditionMidi::SetVariableValues(const MidiMessage &m)
{
SetVariableValue(std::to_string(m.Note()) + " " +
std::to_string(m.Value()));
SetTempVarValue("type", m.MidiTypeToString(m.Type()));
SetTempVarValue("channel", std::to_string(m.Channel()));
try {
SetTempVarValue("note",
GetAllNotes().at(m.Note()).toStdString());
} catch (...) {
}
SetTempVarValue("value1", std::to_string(m.Note()));
SetTempVarValue("value2", std::to_string(m.Value()));
}
MacroConditionMidiEdit::MacroConditionMidiEdit(
QWidget *parent, std::shared_ptr<MacroConditionMidi> entryData)
: QWidget(parent),

View File

@@ -24,6 +24,9 @@ public:
MidiMessage _message;
private:
void SetupTempVars();
void SetVariableValues(const MidiMessage &);
static bool _registered;
static const std::string id;
};

View File

@@ -1,15 +1,17 @@
#include "midi-helpers.hpp"
#include <utility.hpp>
#include <log-helper.hpp>
#include <obs-module-helper.hpp>
#include <switcher-data.hpp>
#include <plugin-state-helpers.hpp>
#include <sync-helpers.hpp>
namespace advss {
static std::map<std::pair<MidiDeviceType, int>, MidiDeviceInstance *>
SetupMidiMessageVector()
{
GetSwitcher()->AddIntervalResetStep(
AddIntervalResetStep(
MidiDeviceInstance::ClearMessageBuffersOfAllDevices);
return {};
}

View File

@@ -190,7 +190,7 @@ void CategoryGrabber::Search(const std::string &)
std::string cursor;
httplib::Params params = {
{"first", "100"}, {"after", cursor}, {"query", _searchString}};
auto response = SendGetRequest(uri, path, *_token, params);
auto response = SendGetRequest(*_token, uri, path, params);
while (response.status == 200 && !_stop) {
cursor = ParseReply(response.data);
@@ -200,7 +200,7 @@ void CategoryGrabber::Search(const std::string &)
params = {{"first", "100"},
{"after", cursor},
{"query", _searchString}};
response = SendGetRequest(uri, path, *_token, params);
response = SendGetRequest(*_token, uri, path, params);
emit CategoryCountUpdated(_categoryMap.size() - startCount);
}
}
@@ -214,7 +214,7 @@ void CategoryGrabber::GetAll()
static std::string cursor;
httplib::Params params = {{"first", "100"}, {"after", cursor}};
auto response = SendGetRequest(uri, path, *_token, params);
auto response = SendGetRequest(*_token, uri, path, params);
while (response.status == 200 && !_stop) {
cursor = ParseReply(response.data);
@@ -222,7 +222,7 @@ void CategoryGrabber::GetAll()
break; // End of category list
}
params = {{"first", "100"}, {"after", cursor}};
response = SendGetRequest(uri, path, *_token, params);
response = SendGetRequest(*_token, uri, path, params);
emit CategoryCountUpdated(_categoryMap.size());
}
}

View File

@@ -35,8 +35,8 @@ std::string TwitchChannel::GetUserID(const TwitchToken &token) const
return it->second;
}
auto res = SendGetRequest("https://api.twitch.tv", "/helix/users",
token, {{"login", _name}});
auto res = SendGetRequest(token, "https://api.twitch.tv",
"/helix/users", {{"login", _name}});
if (res.status == 400) {
userIDCache[_name] = "invalid";
@@ -112,8 +112,8 @@ TwitchChannel::GetLiveInfo(const TwitchToken &token)
httplib::Params params = {
{"first", "1"}, {"after", ""}, {"user_id", id}};
auto result = SendGetRequest("https://api.twitch.tv", "/helix/streams",
token, params, true);
auto result = SendGetRequest(token, "https://api.twitch.tv",
"/helix/streams", params, true);
if (result.status != 200) {
return {};
}
@@ -153,8 +153,8 @@ std::optional<ChannelInfo> TwitchChannel::GetInfo(const TwitchToken &token)
httplib::Params params = {
{"first", "1"}, {"after", ""}, {"broadcaster_id", id}};
auto result = SendGetRequest("https://api.twitch.tv", "/helix/channels",
token, params, true);
auto result = SendGetRequest(token, "https://api.twitch.tv",
"/helix/channels", params, true);
if (result.status != 200) {
return {};
}

View File

@@ -3,7 +3,7 @@
#include "twitch-helpers.hpp"
#include <log-helper.hpp>
#include <switcher-data.hpp>
#include <plugin-state-helpers.hpp>
namespace advss {
@@ -364,8 +364,7 @@ TwitchChatConnection::~TwitchChatConnection()
static bool setupChatMessageClear()
{
GetSwitcher()->AddIntervalResetStep(
&TwitchChatConnection::ClearAllMessages);
AddIntervalResetStep(&TwitchChatConnection::ClearAllMessages);
return true;
}
@@ -432,8 +431,8 @@ void TwitchChatConnection::ConnectThread()
_client.reset();
_connected = true;
websocketpp::lib::error_code ec;
EventSubWSClient::connection_ptr con =
_client.get_connection(_url, ec);
websocketpp::client<websocketpp::config::asio_tls_client>::
connection_ptr con = _client.get_connection(_url, ec);
if (ec) {
blog(LOG_INFO, "Twitch TwitchChatConnection failed: %s",
ec.message().c_str());
@@ -634,8 +633,10 @@ void TwitchChatConnection::OnOpen(connection_hdl)
Authenticate();
}
void TwitchChatConnection::OnMessage(connection_hdl,
EventSubWSClient::message_ptr message)
void TwitchChatConnection::OnMessage(
connection_hdl,
websocketpp::client<websocketpp::config::asio_tls_client>::message_ptr
message)
{
constexpr std::string_view authOKCommand = "001";
constexpr std::string_view pingCommand = "PING";
@@ -679,7 +680,8 @@ void TwitchChatConnection::OnMessage(connection_hdl,
void TwitchChatConnection::OnClose(connection_hdl hdl)
{
EventSubWSClient::connection_ptr con = _client.get_con_from_hdl(hdl);
websocketpp::client<websocketpp::config::asio_tls_client>::connection_ptr
con = _client.get_con_from_hdl(hdl);
auto msg = con->get_ec().message();
blog(LOG_INFO, "Twitch chat connection closed: %s", msg.c_str());
_connected = false;
@@ -687,7 +689,8 @@ void TwitchChatConnection::OnClose(connection_hdl hdl)
void TwitchChatConnection::OnFail(connection_hdl hdl)
{
EventSubWSClient::connection_ptr con = _client.get_con_from_hdl(hdl);
websocketpp::client<websocketpp::config::asio_tls_client>::connection_ptr
con = _client.get_con_from_hdl(hdl);
auto msg = con->get_ec().message();
blog(LOG_INFO, "Twitch chat connection failed: %s", msg.c_str());
_connected = false;

View File

@@ -3,7 +3,7 @@
#include "twitch-helpers.hpp"
#include <log-helper.hpp>
#include <switcher-data.hpp>
#include <plugin-state-helpers.hpp>
namespace advss {
@@ -58,7 +58,7 @@ EventSub::~EventSub()
static bool setupEventSubMessageClear()
{
GetSwitcher()->AddIntervalResetStep(&EventSub::ClearAllEvents);
AddIntervalResetStep(&EventSub::ClearAllEvents);
return true;
}
@@ -230,14 +230,16 @@ std::string EventSub::AddEventSubscribtion(std::shared_ptr<TwitchToken> token,
OBSDataAutoRelease postData = copyData(subscription.data);
setTransportData(postData.Get(), eventSub->_sessionID);
auto result = SendPostRequest(registerSubscriptionURL.data(),
registerSubscriptionPath.data(), *token,
auto result = SendPostRequest(*token, registerSubscriptionURL.data(),
registerSubscriptionPath.data(), {},
postData.Get());
if (result.status != 202) {
vblog(LOG_INFO, "failed to register Twitch EventSub (%d)",
result.status);
return "";
}
OBSDataArrayAutoRelease replyArray =
obs_data_get_array(result.data, "data");
OBSDataAutoRelease replyData = obs_data_array_item(replyArray, 0);

View File

@@ -19,24 +19,24 @@ std::string MacroActionTwitch::GetShortDesc() const
}
const static std::map<MacroActionTwitch::Action, std::string> actionTypes = {
{MacroActionTwitch::Action::TITLE,
"AdvSceneSwitcher.action.twitch.type.title"},
{MacroActionTwitch::Action::CATEGORY,
"AdvSceneSwitcher.action.twitch.type.category"},
{MacroActionTwitch::Action::MARKER,
"AdvSceneSwitcher.action.twitch.type.marker"},
{MacroActionTwitch::Action::CLIP,
"AdvSceneSwitcher.action.twitch.type.clip"},
{MacroActionTwitch::Action::COMMERCIAL,
"AdvSceneSwitcher.action.twitch.type.commercial"},
{MacroActionTwitch::Action::ANNOUNCEMENT,
"AdvSceneSwitcher.action.twitch.type.announcement"},
{MacroActionTwitch::Action::ENABLE_EMOTE_ONLY,
"AdvSceneSwitcher.action.twitch.type.emoteOnlyEnable"},
{MacroActionTwitch::Action::DISABLE_EMOTE_ONLY,
"AdvSceneSwitcher.action.twitch.type.emoteOnlyDisable"},
{MacroActionTwitch::Action::RAID,
"AdvSceneSwitcher.action.twitch.type.raid"},
{MacroActionTwitch::Action::CHANNEL_INFO_TITLE_SET,
"AdvSceneSwitcher.action.twitch.type.channel.info.title.set"},
{MacroActionTwitch::Action::CHANNEL_INFO_CATEGORY_SET,
"AdvSceneSwitcher.action.twitch.type.channel.info.category.set"},
{MacroActionTwitch::Action::RAID_START,
"AdvSceneSwitcher.action.twitch.type.raid.start"},
{MacroActionTwitch::Action::COMMERCIAL_START,
"AdvSceneSwitcher.action.twitch.type.commercial.start"},
{MacroActionTwitch::Action::MARKER_CREATE,
"AdvSceneSwitcher.action.twitch.type.marker.create"},
{MacroActionTwitch::Action::CLIP_CREATE,
"AdvSceneSwitcher.action.twitch.type.clip.create"},
{MacroActionTwitch::Action::CHAT_ANNOUNCEMENT_SEND,
"AdvSceneSwitcher.action.twitch.type.chat.announcement.send"},
{MacroActionTwitch::Action::CHAT_EMOTE_ONLY_ENABLE,
"AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable"},
{MacroActionTwitch::Action::CHAT_EMOTE_ONLY_DISABLE,
"AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable"},
{MacroActionTwitch::Action::SEND_CHAT_MESSAGE,
"AdvSceneSwitcher.action.twitch.type.sendChatMessage"},
};
@@ -64,6 +64,12 @@ const static std::map<MacroActionTwitch::AnnouncementColor, std::string>
{MacroActionTwitch::AnnouncementColor::PURPLE, "purple"},
};
const static std::map<MacroActionTwitch::RedemptionStatus, std::string>
redemptionStatusesTwitch = {
{MacroActionTwitch::RedemptionStatus::CANCELED, "CANCELED"},
{MacroActionTwitch::RedemptionStatus::FULFILLED, "FULFILLED"},
};
void MacroActionTwitch::SetStreamTitle(
const std::shared_ptr<TwitchToken> &token) const
{
@@ -73,11 +79,10 @@ void MacroActionTwitch::SetStreamTitle(
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "title", _streamTitle.c_str());
auto result = SendPatchRequest(
"https://api.twitch.tv",
std::string("/helix/channels?broadcaster_id=") +
token->GetUserID(),
*token, data.Get());
auto result = SendPatchRequest(*token, "https://api.twitch.tv",
"/helix/channels",
{{"broadcaster_id", token->GetUserID()}},
data.Get());
if (result.status != 204) {
blog(LOG_INFO, "Failed to set stream title! (%d)",
@@ -95,11 +100,11 @@ void MacroActionTwitch::SetStreamCategory(
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "game_id",
std::to_string(_category.id).c_str());
auto result = SendPatchRequest(
"https://api.twitch.tv",
std::string("/helix/channels?broadcaster_id=") +
token->GetUserID(),
*token, data.Get());
auto result = SendPatchRequest(*token, "https://api.twitch.tv",
"/helix/channels",
{{"broadcaster_id", token->GetUserID()}},
data.Get());
if (result.status != 204) {
blog(LOG_INFO, "Failed to set stream category! (%d)",
result.status);
@@ -117,9 +122,8 @@ void MacroActionTwitch::CreateStreamMarker(
_markerDescription.c_str());
}
auto result = SendPostRequest("https://api.twitch.tv",
"/helix/streams/markers", *token,
data.Get());
auto result = SendPostRequest(*token, "https://api.twitch.tv",
"/helix/streams/markers", {}, data.Get());
if (result.status != 200) {
blog(LOG_INFO, "Failed to create marker! (%d)", result.status);
@@ -129,14 +133,12 @@ void MacroActionTwitch::CreateStreamMarker(
void MacroActionTwitch::CreateStreamClip(
const std::shared_ptr<TwitchToken> &token) const
{
OBSDataAutoRelease data = obs_data_create();
auto hasDelay = _clipHasDelay ? "true" : "false";
auto result = SendPostRequest(
"https://api.twitch.tv",
"/helix/clips?broadcaster_id=" + token->GetUserID() +
"&has_delay=" + hasDelay,
*token, data.Get());
auto result = SendPostRequest(*token, "https://api.twitch.tv",
"/helix/clips",
{{"broadcaster_id", token->GetUserID()},
{"has_delay", hasDelay}});
if (result.status != 202) {
blog(LOG_INFO, "Failed to create clip! (%d)", result.status);
@@ -149,9 +151,10 @@ void MacroActionTwitch::StartCommercial(
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "broadcaster_id", token->GetUserID().c_str());
obs_data_set_int(data, "length", _duration.Seconds());
auto result = SendPostRequest("https://api.twitch.tv",
"/helix/channels/commercial", *token,
auto result = SendPostRequest(*token, "https://api.twitch.tv",
"/helix/channels/commercial", {},
data.Get());
if (result.status != 200) {
OBSDataArrayAutoRelease replyArray =
obs_data_get_array(result.data, "data");
@@ -178,11 +181,10 @@ void MacroActionTwitch::SendChatAnnouncement(
announcementColorsTwitch.at(_announcementColor).c_str());
auto userId = token->GetUserID();
auto result =
SendPostRequest("https://api.twitch.tv",
"/helix/chat/announcements?broadcaster_id=" +
userId + "&moderator_id=" + userId,
*token, data.Get());
auto result = SendPostRequest(
*token, "https://api.twitch.tv", "/helix/chat/announcements",
{{"broadcaster_id", userId}, {"moderator_id", userId}},
data.Get());
if (result.status != 204) {
blog(LOG_INFO, "Failed to send chat announcement! (%d)",
@@ -197,11 +199,10 @@ void MacroActionTwitch::SetChatEmoteOnlyMode(
obs_data_set_bool(data, "emote_mode", enable);
auto userId = token->GetUserID();
auto result =
SendPatchRequest("https://api.twitch.tv",
"/helix/chat/settings?broadcaster_id=" +
userId + "&moderator_id=" + userId,
*token, data.Get());
auto result = SendPatchRequest(
*token, "https://api.twitch.tv", "/helix/chat/settings",
{{"broadcaster_id", userId}, {"moderator_id", userId}},
data.Get());
if (result.status != 200) {
blog(LOG_INFO, "Failed to %s chat's emote-only mode! (%d)",
@@ -216,8 +217,9 @@ void MacroActionTwitch::StartRaid(const std::shared_ptr<TwitchToken> &token)
token->GetUserID().c_str());
obs_data_set_string(data, "to_broadcaster_id",
_channel.GetUserID(*token).c_str());
auto result = SendPostRequest("https://api.twitch.tv", "/helix/raids",
*token, data.Get());
auto result = SendPostRequest(*token, "https://api.twitch.tv",
"/helix/raids", {}, data.Get());
if (result.status != 200) {
blog(LOG_INFO, "Failed to start raid! (%d)\n", result.status);
}
@@ -243,33 +245,32 @@ bool MacroActionTwitch::PerformAction()
}
switch (_action) {
case MacroActionTwitch::Action::TITLE:
case Action::CHANNEL_INFO_TITLE_SET:
SetStreamTitle(token);
break;
case MacroActionTwitch::Action::CATEGORY:
case Action::CHANNEL_INFO_CATEGORY_SET:
SetStreamCategory(token);
break;
case MacroActionTwitch::Action::MARKER:
CreateStreamMarker(token);
break;
case MacroActionTwitch::Action::CLIP:
CreateStreamClip(token);
break;
case MacroActionTwitch::Action::COMMERCIAL:
StartCommercial(token);
break;
case MacroActionTwitch::Action::ANNOUNCEMENT:
SendChatAnnouncement(token);
break;
case MacroActionTwitch::Action::ENABLE_EMOTE_ONLY:
SetChatEmoteOnlyMode(token, true);
break;
case MacroActionTwitch::Action::DISABLE_EMOTE_ONLY:
SetChatEmoteOnlyMode(token, false);
break;
case MacroActionTwitch::Action::RAID:
case Action::RAID_START:
StartRaid(token);
break;
case Action::COMMERCIAL_START:
StartCommercial(token);
break;
case Action::MARKER_CREATE:
CreateStreamMarker(token);
break;
case Action::CLIP_CREATE:
CreateStreamClip(token);
break;
case Action::CHAT_ANNOUNCEMENT_SEND:
SendChatAnnouncement(token);
break;
case Action::CHAT_EMOTE_ONLY_ENABLE:
SetChatEmoteOnlyMode(token, true);
break;
case Action::CHAT_EMOTE_ONLY_DISABLE:
SetChatEmoteOnlyMode(token, false);
case MacroActionTwitch::Action::SEND_CHAT_MESSAGE:
SendChatMessage(token);
break;
@@ -338,27 +339,111 @@ bool MacroActionTwitch::Load(obs_data_t *obj)
bool MacroActionTwitch::ActionIsSupportedByToken()
{
static const std::unordered_map<Action, TokenOption> requiredOption = {
{Action::TITLE, {"channel:manage:broadcast"}},
{Action::CATEGORY, {"channel:manage:broadcast"}},
{Action::MARKER, {"channel:manage:broadcast"}},
{Action::CLIP, {"clips:edit"}},
{Action::COMMERCIAL, {"channel:edit:commercial"}},
{Action::ANNOUNCEMENT, {"moderator:manage:announcements"}},
{Action::ENABLE_EMOTE_ONLY, {"moderator:manage:chat_settings"}},
{Action::DISABLE_EMOTE_ONLY,
{Action::CHANNEL_INFO_TITLE_SET, {"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_CATEGORY_SET,
{"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_LANGUAGE_SET,
{"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_DELAY_SET, {"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_BRANDED_CONTENT_ENABLE,
{"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_BRANDED_CONTENT_DISABLE,
{"channel:manage:broadcast"}},
{Action::RAID_START, {"channel:manage:raids"}},
{Action::RAID_END, {"channel:manage:raids"}},
{Action::SHOUTOUT_SEND, {"moderator:manage:shoutouts"}},
{Action::POLL_END, {"channel:manage:polls"}},
{Action::PREDICTION_END, {"channel:manage:predictions"}},
{Action::SHIELD_MODE_START, {"moderator:manage:shield_mode"}},
{Action::SHIELD_MODE_END, {"moderator:manage:shield_mode"}},
{Action::POINTS_REWARD_ENABLE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_DISABLE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_PAUSE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_UNPAUSE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_TITLE_SET,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_PROMPT_SET,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_COST_SET,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_USER_INPUT_REQUIRE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_USER_INPUT_UNREQUIRE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_COOLDOWN_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_COOLDOWN_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_QUEUE_SKIP_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_QUEUE_SKIP_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_STREAM_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_STREAM_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_USER_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_USER_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_DELETE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_REDEMPTION_CANCEL,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_REDEMPTION_FULFILL,
{"channel:manage:redemptions"}},
{Action::USER_BAN, {"moderator:manage:banned_users"}},
{Action::USER_UNBAN, {"moderator:manage:banned_users"}},
{Action::USER_BLOCK, {"user:manage:blocked_users"}},
{Action::USER_UNBLOCK, {"user:manage:blocked_users"}},
{Action::USER_MODERATOR_ADD, {"channel:manage:moderators"}},
{Action::USER_MODERATOR_DELETE, {"channel:manage:moderators"}},
{Action::USER_VIP_ADD, {"channel:manage:vips"}},
{Action::USER_VIP_DELETE, {"channel:manage:vips"}},
{Action::COMMERCIAL_START, {"channel:edit:commercial"}},
{Action::COMMERCIAL_SNOOZE, {"channel:manage:ads"}},
{Action::MARKER_CREATE, {"channel:manage:broadcast"}},
{Action::CLIP_CREATE, {"clips:edit"}},
{Action::CHAT_ANNOUNCEMENT_SEND,
{"moderator:manage:announcements"}},
{Action::CHAT_EMOTE_ONLY_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::RAID, {"channel:manage:raids"}},
{Action::CHAT_EMOTE_ONLY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_FOLLOWER_ONLY_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_FOLLOWER_ONLY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SUBSCRIBER_ONLY_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SUBSCRIBER_ONLY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SLOW_MODE_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SLOW_MODE_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_NON_MODERATOR_DELAY_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_NON_MODERATOR_DELAY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_UNIQUE_MODE_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_UNIQUE_MODE_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::WHISPER_SEND, {"user:manage:whispers"}},
{Action::SEND_CHAT_MESSAGE, {"chat:edit"}},
};
auto token = _token.lock();
if (!token) {
return false;
}
auto option = requiredOption.find(_action);
assert(option != requiredOption.end());
if (option == requiredOption.end()) {
return false;
}
return token->OptionIsEnabled(option->second);
}
@@ -401,49 +486,8 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
_channel(new TwitchChannelSelection(this)),
_chatMessage(new VariableTextEdit(this))
{
_streamTitle->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_streamTitle->setMaxLength(140);
_markerDescription->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_markerDescription->setMaxLength(140);
_announcementMessage->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_announcementMessage->setMaxLength(500);
auto spinBox = _duration->SpinBox();
spinBox->setSuffix("s");
spinBox->setMaximum(180);
populateActionSelection(_actions);
populateAnnouncementColorSelection(_announcementColor);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_tokens, SIGNAL(SelectionChanged(const QString &)),
this, SLOT(TwitchTokenChanged(const QString &)));
QWidget::connect(&_tokenPermissionCheckTimer, SIGNAL(timeout()), this,
SLOT(CheckTokenPermissions()));
QWidget::connect(_streamTitle, SIGNAL(editingFinished()), this,
SLOT(StreamTitleChanged()));
QWidget::connect(_category,
SIGNAL(CategoreyChanged(const TwitchCategory &)), this,
SLOT(CategoreyChanged(const TwitchCategory &)));
QWidget::connect(_markerDescription, SIGNAL(editingFinished()), this,
SLOT(MarkerDescriptionChanged()));
QObject::connect(_clipHasDelay, SIGNAL(stateChanged(int)), this,
SLOT(ClipHasDelayChanged(int)));
QObject::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(DurationChanged(const Duration &)));
QWidget::connect(_announcementMessage, SIGNAL(textChanged()), this,
SLOT(AnnouncementMessageChanged()));
QWidget::connect(_announcementColor, SIGNAL(currentIndexChanged(int)),
this, SLOT(AnnouncementColorChanged(int)));
QWidget::connect(_channel,
SIGNAL(ChannelChanged(const TwitchChannel &)), this,
SLOT(ChannelChanged(const TwitchChannel &)));
QWidget::connect(_chatMessage, SIGNAL(textChanged()), this,
SLOT(ChatMessageChanged()));
SetWidgetProperties();
SetWidgetSignalConnections();
_entryData = entryData;
SetWidgetLayout();
@@ -477,8 +521,8 @@ void MacroActionTwitchEdit::ActionChanged(int idx)
auto lock = LockContext();
_entryData->_action = static_cast<MacroActionTwitch::Action>(
_actions->itemData(idx).toInt());
SetupWidgetVisibility();
SetWidgetLayout();
SetWidgetVisibility();
}
void MacroActionTwitchEdit::TwitchTokenChanged(const QString &token)
@@ -492,7 +536,7 @@ void MacroActionTwitchEdit::TwitchTokenChanged(const QString &token)
_category->SetToken(_entryData->_token);
_channel->SetToken(_entryData->_token);
SetupWidgetVisibility();
SetWidgetVisibility();
emit(HeaderInfoChanged(token));
}
@@ -580,30 +624,86 @@ void MacroActionTwitchEdit::AnnouncementColorChanged(int index)
static_cast<MacroActionTwitch::AnnouncementColor>(index);
}
void MacroActionTwitchEdit::SetupWidgetVisibility()
void MacroActionTwitchEdit::SetWidgetProperties()
{
_streamTitle->setVisible(_entryData->_action ==
MacroActionTwitch::Action::TITLE);
_category->setVisible(_entryData->_action ==
MacroActionTwitch::Action::CATEGORY);
_markerDescription->setVisible(_entryData->_action ==
MacroActionTwitch::Action::MARKER);
_clipHasDelay->setVisible(_entryData->_action ==
MacroActionTwitch::Action::CLIP);
_duration->setVisible(_entryData->_action ==
MacroActionTwitch::Action::COMMERCIAL);
_announcementMessage->setVisible(
_entryData->_action == MacroActionTwitch::Action::ANNOUNCEMENT);
_announcementColor->setVisible(_entryData->_action ==
MacroActionTwitch::Action::ANNOUNCEMENT);
_streamTitle->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_streamTitle->setMaxLength(140);
_markerDescription->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_markerDescription->setMaxLength(140);
_announcementMessage->setMaxLength(500);
auto durationSpinBox = _duration->SpinBox();
durationSpinBox->setMaximum(180);
durationSpinBox->setSuffix("s");
populateActionSelection(_actions);
populateAnnouncementColorSelection(_announcementColor);
}
void MacroActionTwitchEdit::SetWidgetSignalConnections()
{
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_tokens, SIGNAL(SelectionChanged(const QString &)),
this, SLOT(TwitchTokenChanged(const QString &)));
QWidget::connect(&_tokenPermissionCheckTimer, SIGNAL(timeout()), this,
SLOT(CheckTokenPermissions()));
QWidget::connect(_streamTitle, SIGNAL(editingFinished()), this,
SLOT(StreamTitleChanged()));
QWidget::connect(_category,
SIGNAL(CategoreyChanged(const TwitchCategory &)), this,
SLOT(CategoreyChanged(const TwitchCategory &)));
QWidget::connect(_markerDescription, SIGNAL(editingFinished()), this,
SLOT(MarkerDescriptionChanged()));
QObject::connect(_clipHasDelay, SIGNAL(stateChanged(int)), this,
SLOT(ClipHasDelayChanged(int)));
QObject::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(DurationChanged(const Duration &)));
QWidget::connect(_announcementMessage, SIGNAL(textChanged()), this,
SLOT(AnnouncementMessageChanged()));
QWidget::connect(_announcementColor, SIGNAL(currentIndexChanged(int)),
this, SLOT(AnnouncementColorChanged(int)));
QWidget::connect(_channel,
SIGNAL(ChannelChanged(const TwitchChannel &)), this,
SLOT(ChannelChanged(const TwitchChannel &)));
QWidget::connect(_chatMessage, SIGNAL(textChanged()), this,
SLOT(ChatMessageChanged()));
}
void MacroActionTwitchEdit::SetWidgetVisibility()
{
_streamTitle->setVisible(
_entryData->_action ==
MacroActionTwitch::Action::CHANNEL_INFO_TITLE_SET);
_category->setVisible(
_entryData->_action ==
MacroActionTwitch::Action::CHANNEL_INFO_CATEGORY_SET);
_channel->setVisible(
_entryData->_action == MacroActionTwitch::Action::RAID ||
_entryData->_action == MacroActionTwitch::Action::RAID_START ||
_entryData->_action == MacroActionTwitch::Action::RAID_END ||
_entryData->_action ==
MacroActionTwitch::Action::SEND_CHAT_MESSAGE);
_duration->setVisible(_entryData->_action ==
MacroActionTwitch::Action::COMMERCIAL_START);
_markerDescription->setVisible(
_entryData->_action ==
MacroActionTwitch::Action::MARKER_CREATE);
_clipHasDelay->setVisible(_entryData->_action ==
MacroActionTwitch::Action::CLIP_CREATE);
_announcementMessage->setVisible(
_entryData->_action ==
MacroActionTwitch::Action::CHAT_ANNOUNCEMENT_SEND);
_announcementColor->setVisible(
_entryData->_action ==
MacroActionTwitch::Action::CHAT_ANNOUNCEMENT_SEND);
_chatMessage->setVisible(_entryData->_action ==
MacroActionTwitch::Action::SEND_CHAT_MESSAGE);
if (_entryData->_action == MacroActionTwitch::Action::TITLE ||
_entryData->_action == MacroActionTwitch::Action::MARKER) {
if (_entryData->_action ==
MacroActionTwitch::Action::CHANNEL_INFO_TITLE_SET ||
_entryData->_action == MacroActionTwitch::Action::MARKER_CREATE) {
RemoveStretchIfPresent(_layout);
} else {
AddStretchIfNecessary(_layout);
@@ -641,7 +741,7 @@ void MacroActionTwitchEdit::ChatMessageChanged()
void MacroActionTwitchEdit::SetWidgetLayout()
{
static const std::vector<QWidget *> widgets{
const std::vector<QWidget *> widgets{
_tokens, _actions, _streamTitle,
_category, _markerDescription, _clipHasDelay,
_duration, _announcementColor, _channel};
@@ -692,7 +792,7 @@ void MacroActionTwitchEdit::UpdateEntryData()
_channel->SetChannel(_entryData->_channel);
_chatMessage->setPlainText(_entryData->_chatMessage);
SetupWidgetVisibility();
SetWidgetVisibility();
}
} // namespace advss

View File

@@ -4,6 +4,7 @@
#include "category-selection.hpp"
#include "channel-selection.hpp"
#include "chat-connection.hpp"
#include "points-reward-selection.hpp"
#include <variable-line-edit.hpp>
#include <variable-text-edit.hpp>
@@ -22,16 +23,118 @@ public:
std::string GetShortDesc() const;
enum class Action {
TITLE,
CATEGORY = 10,
MARKER = 20,
CLIP = 30,
COMMERCIAL = 40,
ANNOUNCEMENT = 50,
ENABLE_EMOTE_ONLY = 60,
DISABLE_EMOTE_ONLY = 70,
RAID = 80,
SEND_CHAT_MESSAGE = 90,
// Channel info update
CHANNEL_INFO_TITLE_SET = 10,
CHANNEL_INFO_CATEGORY_SET = 20,
CHANNEL_INFO_LANGUAGE_SET = 30,
CHANNEL_INFO_DELAY_SET = 40,
CHANNEL_INFO_TAGS_SET = 50, // TODO
CHANNEL_INFO_CONTENT_LABELS_SET = 60, // TODO
CHANNEL_INFO_BRANDED_CONTENT_ENABLE = 70,
CHANNEL_INFO_BRANDED_CONTENT_DISABLE = 71,
// Raid
RAID_START = 110,
RAID_END = 120,
// Shoutout
SHOUTOUT_SEND = 200,
// Poll
POLL_START = 300, // TODO
POLL_END = 310,
// Prediction
PREDICTION_START = 400, // TODO
PREDICTION_END = 410,
// Shield mode
SHIELD_MODE_START = 500,
SHIELD_MODE_END = 501,
// Reward add
POINTS_REWARD_ADD = 600, // TODO
// Reward update
POINTS_REWARD_ENABLE = 710,
POINTS_REWARD_DISABLE = 711,
POINTS_REWARD_PAUSE = 712,
POINTS_REWARD_UNPAUSE = 713,
POINTS_REWARD_TITLE_SET = 720,
POINTS_REWARD_PROMPT_SET = 730,
POINTS_REWARD_COST_SET = 740,
POINTS_REWARD_BACKGROUND_COLOR_SET = 750, // TODO
POINTS_REWARD_USER_INPUT_REQUIRE = 760,
POINTS_REWARD_USER_INPUT_UNREQUIRE = 761,
POINTS_REWARD_COOLDOWN_ENABLE = 770,
POINTS_REWARD_COOLDOWN_DISABLE = 771,
POINTS_REWARD_QUEUE_SKIP_ENABLE = 780,
POINTS_REWARD_QUEUE_SKIP_DISABLE = 781,
POINTS_REWARD_MAX_PER_STREAM_ENABLE = 790,
POINTS_REWARD_MAX_PER_STREAM_DISABLE = 791,
POINTS_REWARD_MAX_PER_USER_ENABLE = 792,
POINTS_REWARD_MAX_PER_USER_DISABLE = 793,
// Reward delete
POINTS_REWARD_DELETE = 800,
// Reward redemption update
POINTS_REWARD_REDEMPTION_FULFILL = 900,
POINTS_REWARD_REDEMPTION_CANCEL = 910,
// User state
USER_BAN = 1000,
USER_UNBAN = 1100,
USER_BLOCK = 1200,
USER_UNBLOCK = 1300,
USER_MODERATOR_ADD = 1400,
USER_MODERATOR_DELETE = 1500,
USER_VIP_ADD = 1600,
USER_VIP_DELETE = 1700,
USER_CHAT_COLOR_UPDATE = 1800, // TODO
// Commercial
COMMERCIAL_START = 1900,
COMMERCIAL_SNOOZE = 2000,
// Marker
MARKER_CREATE = 2100,
// Clips
CLIP_CREATE = 2200,
// Videos
VIDEOS_DELETE = 2300, // TODO
// Chat
CHAT_MESSAGES_DELETE = 2400, // TODO
CHAT_ANNOUNCEMENT_SEND = 2500,
CHAT_EMOTE_ONLY_ENABLE = 2600,
CHAT_EMOTE_ONLY_DISABLE = 2601,
CHAT_FOLLOWER_ONLY_ENABLE = 2602,
CHAT_FOLLOWER_ONLY_DISABLE = 2603,
CHAT_SUBSCRIBER_ONLY_ENABLE = 2604,
CHAT_SUBSCRIBER_ONLY_DISABLE = 2605,
CHAT_SLOW_MODE_ENABLE = 2640,
CHAT_SLOW_MODE_DISABLE = 2641,
CHAT_NON_MODERATOR_DELAY_ENABLE = 2650,
CHAT_NON_MODERATOR_DELAY_DISABLE = 2651,
CHAT_UNIQUE_MODE_ENABLE = 2960,
CHAT_UNIQUE_MODE_DISABLE = 2661,
CHAT_AUTOMOD_UPDATE = 2700, // TODO
CHAT_AUTOMOD_MESSAGE_ALLOW = 2800, // TODO
CHAT_AUTOMOD_MESSAGE_DENY = 2801, // TODO
CHAT_BLOCKED_TERM_ADD = 2900, // TODO
CHAT_BLOCKED_TERM_DELETE = 3000, // TODO
WHISPER_SEND = 3100,
// Schedule
CHANNEL_SCHEDULE_UPDATE = 3200, // TODO
CHANNEL_SCHEDULE_SEGMENT_ADD = 3300, // TODO
CHANNEL_SCHEDULE_SEGMENT_UPDATE = 3400, // TODO
CHANNEL_SCHEDULE_SEGMENT_DELETE = 3500, // TODO
SEND_CHAT_MESSAGE = 5000,
};
enum class AnnouncementColor {
@@ -42,6 +145,8 @@ public:
PURPLE,
};
enum class RedemptionStatus { CANCELED, FULFILLED };
bool PerformAction();
void LogAction() const;
bool Save(obs_data_t *obj) const;
@@ -49,7 +154,7 @@ public:
bool ActionIsSupportedByToken();
void ResetChatConnection();
Action _action = Action::TITLE;
Action _action = Action::CHANNEL_INFO_TITLE_SET;
std::weak_ptr<TwitchToken> _token;
StringVariable _streamTitle =
obs_module_text("AdvSceneSwitcher.action.twitch.title.title");
@@ -119,8 +224,10 @@ protected:
std::shared_ptr<MacroActionTwitch> _entryData;
private:
void SetupWidgetVisibility();
void SetWidgetProperties();
void SetWidgetSignalConnections();
void SetWidgetLayout();
void SetWidgetVisibility();
QHBoxLayout *_layout;
FilterComboBox *_actions;

View File

@@ -866,7 +866,9 @@ void MacroConditionTwitch::SetupTempVars()
AddTempvar(id, name.empty() ? id : name, description);
};
if (_condition != Condition::CHAT_MESSAGE_RECEIVED) {
if (_condition != Condition::CHAT_MESSAGE_RECEIVED &&
_condition != Condition::RAID_INBOUND_EVENT &&
_condition != Condition::RAID_OUTBOUND_EVENT) {
setupTempVarHelper("broadcaster_user_id");
setupTempVarHelper("broadcaster_user_login");
setupTempVarHelper("broadcaster_user_name");
@@ -1141,7 +1143,16 @@ void MacroConditionTwitch::SetupTempVars()
setupTempVarHelper("user_name", ".follow");
setupTempVarHelper("followed_at");
break;
case Condition::RAID_INBOUND_EVENT:
case Condition::RAID_OUTBOUND_EVENT:
setupTempVarHelper("from_broadcaster_user_id", ".raid");
setupTempVarHelper("from_broadcaster_user_login", ".raid");
setupTempVarHelper("from_broadcaster_user_name", ".raid");
setupTempVarHelper("to_broadcaster_user_id", ".raid");
setupTempVarHelper("to_broadcaster_user_login", ".raid");
setupTempVarHelper("to_broadcaster_user_name", ".raid");
setupTempVarHelper("viewers", ".raid");
break;
case Condition::LIVE_POLLING:
setupTempVarHelper("id", ".stream.online");
setupTempVarHelper("game_id");
@@ -1280,7 +1291,7 @@ void MacroConditionTwitchEdit::ConditionChanged(int idx)
auto lock = LockContext();
_entryData->SetCondition(static_cast<MacroConditionTwitch::Condition>(
_conditions->itemData(idx).toInt()));
SetupWidgetVisibility();
SetWidgetVisibility();
emit TempVarsChanged();
}
@@ -1297,7 +1308,7 @@ void MacroConditionTwitchEdit::TwitchTokenChanged(const QString &token)
_pointsReward->SetToken(_entryData->_token);
_entryData->ResetChatConnection();
SetupWidgetVisibility();
SetWidgetVisibility();
emit(HeaderInfoChanged(token));
}
@@ -1388,7 +1399,7 @@ void MacroConditionTwitchEdit::CategoreyChanged(const TwitchCategory &category)
_entryData->_category = category;
}
void MacroConditionTwitchEdit::SetupWidgetVisibility()
void MacroConditionTwitchEdit::SetWidgetVisibility()
{
auto condition = _entryData->GetCondition();
@@ -1448,7 +1459,7 @@ void MacroConditionTwitchEdit::UpdateEntryData()
_category->SetToken(_entryData->_token);
_category->SetCategory(_entryData->_category);
SetupWidgetVisibility();
SetWidgetVisibility();
}
} // namespace advss

View File

@@ -23,8 +23,13 @@ public:
std::string GetShortDesc() const;
enum class Condition {
// Generic event based
// Event based
STREAM_ONLINE_EVENT = 0,
STREAM_ONLINE_LIVE_EVENT = 1,
STREAM_ONLINE_PLAYLIST_EVENT = 2,
STREAM_ONLINE_WATCHPARTY_EVENT = 3,
STREAM_ONLINE_PREMIERE_EVENT = 4,
STREAM_ONLINE_RERUN_EVENT = 5,
STREAM_OFFLINE_EVENT = 100,
CHANNEL_INFO_UPDATE_EVENT = 200,
FOLLOW_EVENT = 300,
@@ -66,13 +71,6 @@ public:
USER_MODERATOR_ADDITION_EVENT = 3900,
USER_MODERATOR_DELETION_EVENT = 4000,
// Event based with custom logic
STREAM_ONLINE_LIVE_EVENT = 1,
STREAM_ONLINE_PLAYLIST_EVENT = 2,
STREAM_ONLINE_WATCHPARTY_EVENT = 3,
STREAM_ONLINE_PREMIERE_EVENT = 4,
STREAM_ONLINE_RERUN_EVENT = 5,
// Chat
CHAT_MESSAGE_RECEIVED = 500000,
@@ -170,7 +168,7 @@ protected:
std::shared_ptr<MacroConditionTwitch> _entryData;
private:
void SetupWidgetVisibility();
void SetWidgetVisibility();
QHBoxLayout *_layout;
FilterComboBox *_conditions;

View File

@@ -134,9 +134,9 @@ TwitchPointsRewardSelection::GetPointsRewards(
httplib::Params params = {
{"broadcaster_id", channel.GetUserID(*token)}};
auto response = SendGetRequest("https://api.twitch.tv",
auto response = SendGetRequest(*token, "https://api.twitch.tv",
"/helix/channel_points/custom_rewards",
*token, params);
params);
if (response.status != 200) {
blog(LOG_WARNING,

View File

@@ -1,26 +1,24 @@
#include "token.hpp"
#include "twitch-helpers.hpp"
#include <switcher-data.hpp>
#include <plugin-state-helpers.hpp>
#include <log-helper.hpp>
#include <obs-module-helper.hpp>
#include <utility.hpp>
#include <QScrollArea>
#include <QDesktopServices>
#include <QGroupBox>
#include <QUrl>
namespace advss {
static const int tokenGrabberPort = 8080;
static std::deque<std::shared_ptr<Item>> twitchTokens;
const std::unordered_map<std::string, std::string> TokenOption::_apiIdToLocale{
{"channel:manage:broadcast",
"AdvSceneSwitcher.twitchToken.channel.broadcast.manage"},
{"clips:edit", "AdvSceneSwitcher.twitchToken.clips.edit"},
{"channel:edit:commercial",
"AdvSceneSwitcher.twitchToken.channel.commercial.edit"},
{"moderator:manage:announcements",
"AdvSceneSwitcher.twitchToken.moderator.announcements.manage"},
{"moderator:manage:chat_settings",
"AdvSceneSwitcher.twitchToken.moderator.chat.settings.manage"},
{"channel:manage:raids", "AdvSceneSwitcher.twitchToken.channel.raid"},
{"moderator:read:followers",
"AdvSceneSwitcher.twitchToken.moderator.followers.read"},
{"channel:read:subscriptions",
@@ -28,6 +26,8 @@ const std::unordered_map<std::string, std::string> TokenOption::_apiIdToLocale{
{"bits:read", "AdvSceneSwitcher.twitchToken.bits.read"},
{"moderator:read:shoutouts",
"AdvSceneSwitcher.twitchToken.moderator.shoutouts.read"},
{"channel:manage:raids",
"AdvSceneSwitcher.twitchToken.channel.raids.manage"},
{"moderator:manage:shoutouts",
"AdvSceneSwitcher.twitchToken.moderator.shoutouts.manage"},
{"channel:read:polls",
@@ -53,8 +53,26 @@ const std::unordered_map<std::string, std::string> TokenOption::_apiIdToLocale{
{"channel:manage:redemptions",
"AdvSceneSwitcher.twitchToken.channel.redemptions.manage"},
{"channel:moderate", "AdvSceneSwitcher.twitchToken.channel.moderate"},
{"moderation:read",
"AdvSceneSwitcher.twitchToken.channel.moderation.read"},
{"moderator:manage:banned_users",
"AdvSceneSwitcher.twitchToken.moderator.bannedUsers.manage"},
{"user:manage:blocked_users",
"AdvSceneSwitcher.twitchToken.user.blockedUsers.manage"},
{"moderation:read", "AdvSceneSwitcher.twitchToken.moderation.read"},
{"channel:manage:moderators",
"AdvSceneSwitcher.twitchToken.channel.moderators.manage"},
{"channel:manage:vips",
"AdvSceneSwitcher.twitchToken.channel.vips.manage"},
{"channel:edit:commercial",
"AdvSceneSwitcher.twitchToken.channel.commercial.edit"},
{"channel:manage:ads",
"AdvSceneSwitcher.twitchToken.channel.ads.manage"},
{"clips:edit", "AdvSceneSwitcher.twitchToken.clips.edit"},
{"moderator:manage:announcements",
"AdvSceneSwitcher.twitchToken.moderator.announcements.manage"},
{"moderator:manage:chat_settings",
"AdvSceneSwitcher.twitchToken.moderator.chat.settings.manage"},
{"user:manage:whispers",
"AdvSceneSwitcher.twitchToken.user.whispers.manage"},
{"chat:read", "AdvSceneSwitcher.twitchToken.chat.read"},
{"chat:edit", "AdvSceneSwitcher.twitchToken.chat.edit"},
};
@@ -64,8 +82,8 @@ static void loadConnections(obs_data_t *obj);
bool setupTwitchTokenSupport()
{
GetSwitcher()->AddSaveStep(saveConnections);
GetSwitcher()->AddLoadStep(loadConnections);
AddSaveStep(saveConnections);
AddLoadStep(loadConnections);
return true;
}
@@ -194,7 +212,7 @@ void TwitchToken::SetToken(const std::string &value)
{
_token = value;
auto res =
SendGetRequest("https://api.twitch.tv", "/helix/users", *this);
SendGetRequest(*this, "https://api.twitch.tv", "/helix/users");
if (res.status != 200) {
blog(LOG_WARNING, "failed to get Twitch user id from token!");
_userID = -1;
@@ -593,7 +611,7 @@ static std::string generateScopeString(const std::set<TokenOption> &options)
static std::string getHtml(const QString &redirect)
{
const char *html = R"(
QString html = R"(
<html>
<head>
<title>Advanced scene switcher</title>
@@ -605,17 +623,18 @@ static std::string getHtml(const QString &redirect)
if (document.location.hash && document.location.hash != '') {
var parsedHash = new URLSearchParams(window.location.hash.slice(1));
if (parsedHash.get('access_token')) {
window.location.replace(`http://localhost:8080/token?access_token=${parsedHash.get('access_token')}&state=${parsedHash.get('state')}`);
window.location.replace(`http://localhost:%1/token?access_token=${parsedHash.get('access_token')}&state=${parsedHash.get('state')}`);
output.textContent = 'It is safe to close this window';
}
} else {
window.location.replace('%1');
window.location.replace('%2');
}
</script>
</body>
</html>)";
return QString(html).arg(redirect).toStdString();
return html.arg(QString::number(tokenGrabberPort), redirect)
.toStdString();
}
void TwitchTokenSettingsDialog::RequestToken()
@@ -710,13 +729,16 @@ void TokenGrabberThread::run()
// Generate URI to request token
auto state = generateStateString();
auto getTokenURI = "https://id.twitch.tv/oauth2/authorize"
"?response_type=token"
"&client_id=" +
QString(GetClientID()) +
"&redirect_uri=http://localhost:8080/auth"
"&scope=" +
_scope + "&state=" + QString::fromStdString(state);
QString getTokenURI =
("https://id.twitch.tv/oauth2/authorize"
"?response_type=token"
"&client_id=" +
QString(GetClientID()) +
"&redirect_uri=http://localhost:%1"
"/auth"
"&scope=" +
_scope + "&state=" + QString::fromStdString(state))
.arg(QString::number(tokenGrabberPort));
// Setup server receiving token string
auto html = getHtml(getTokenURI);
@@ -775,8 +797,19 @@ void TokenGrabberThread::run()
// Start the server and wait
std::unique_lock<std::mutex> lock(_mutex);
_serverThread =
std::thread([this]() { _server.listen("localhost", 8080); });
_serverThread = std::thread([this]() {
if (!_server.bind_to_port("localhost", tokenGrabberPort, 0)) {
blog(LOG_WARNING,
"Failed to bind token server to localhost %d!",
tokenGrabberPort);
return;
}
if (!_server.listen_after_bind()) {
blog(LOG_WARNING, "Token server failed to listen()!");
return;
}
vblog(LOG_INFO, "Token server stopped!");
});
auto time = std::chrono::high_resolution_clock::now() +
std::chrono::seconds(_timeout);
while (!_stopWaiting) {

View File

@@ -1,11 +1,18 @@
#include "twitch-helpers.hpp"
#include "token.hpp"
#include <log-helper.hpp>
namespace advss {
static constexpr std::string_view clientID = "ds5tt4ogliifsqc04mz3d3etnck3e5";
static const int cacheTimeoutSeconds = 10;
const char *GetClientID()
{
return clientID.data();
}
class Args {
public:
Args(const std::string &uri, const std::string &path,
@@ -43,7 +50,7 @@ struct CacheEntry {
std::chrono::system_clock::now();
};
static bool chacheIsTooOld(const CacheEntry &cache)
static bool cacheIsTooOld(const CacheEntry &cache)
{
auto currentTime = std::chrono::system_clock::now();
auto diff = currentTime - cache.cacheTime;
@@ -54,7 +61,7 @@ static bool cacheIsValid(const std::map<Args, CacheEntry> &cache,
const Args &args)
{
auto it = cache.find(args);
return it != cache.end() && !chacheIsTooOld(it->second);
return it != cache.end() && !cacheIsTooOld(it->second);
}
static httplib::Headers getTokenRequestHeaders(const std::string &token)
@@ -65,166 +72,262 @@ static httplib::Headers getTokenRequestHeaders(const std::string &token)
};
}
RequestResult SendGetRequest(const std::string &uri, const std::string &path,
const TwitchToken &token,
static std::string getRequestBody(const OBSData &data)
{
auto json = obs_data_get_json(data);
return json ? json : "";
}
static RequestResult processResult(const httplib::Result &response,
const char *funcName)
{
if (!response) {
auto err = response.error();
blog(LOG_WARNING, "Twitch request failed in %s with error: %s",
funcName, httplib::to_string(err).c_str());
return {};
}
RequestResult result;
result.status = response->status;
if (response->body.empty()) {
return result;
}
OBSDataAutoRelease replyData =
obs_data_create_from_json(response->body.c_str());
result.data = replyData;
return result;
}
RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params)
{
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto url = httplib::append_query_params(uri + path, params);
vblog(LOG_INFO, "Twitch GET request to %s began", url.c_str());
auto headers = getTokenRequestHeaders(*tokenStr);
auto response = cli.Get(path, params, headers);
if (!response) {
auto err = response.error();
blog(LOG_INFO, "%s failed - %s", __func__,
httplib::to_string(err).c_str());
return {};
}
RequestResult result;
result.status = response->status;
if (response->body.empty()) {
return result;
}
OBSDataAutoRelease replyData =
obs_data_create_from_json(response->body.c_str());
result.data = replyData;
return result;
return processResult(response, __func__);
}
RequestResult SendGetRequest(const std::string &uri, const std::string &path,
const TwitchToken &token,
RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params, bool useCache)
{
static std::map<Args, CacheEntry> cache;
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto headers = getTokenRequestHeaders(*tokenStr);
Args args(uri, path, "", params, headers);
if (useCache && cacheIsValid(cache, args)) {
auto it = cache.find(args);
return it->second.result;
}
auto result = SendGetRequest(uri, path, token, params);
auto result = SendGetRequest(token, uri, path, params);
cache[args] = {result};
return result;
}
RequestResult SendPostRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data)
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data)
{
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto pathWithParams = httplib::append_query_params(path, params);
auto url = uri + pathWithParams;
vblog(LOG_INFO, "Twitch POST request to %s began", url.c_str());
auto headers = getTokenRequestHeaders(*tokenStr);
auto json = obs_data_get_json(data);
std::string body = json ? json : "";
auto response = cli.Post(path, headers, body, "application/json");
if (!response) {
auto err = response.error();
blog(LOG_INFO, "%s failed - %s", __func__,
httplib::to_string(err).c_str());
return {};
}
RequestResult result;
result.status = response->status;
if (response->body.empty()) {
return result;
}
OBSDataAutoRelease replyData =
obs_data_create_from_json(response->body.c_str());
result.data = replyData;
return result;
auto body = getRequestBody(data);
auto response =
cli.Post(pathWithParams, headers, body, "application/json");
return processResult(response, __func__);
}
RequestResult SendPostRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data,
bool useCache)
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data, bool useCache)
{
static std::map<Args, CacheEntry> cache;
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto headers = getTokenRequestHeaders(*tokenStr);
auto jsonCstr = obs_data_get_json(data);
Args args(uri, path, jsonCstr ? jsonCstr : "", {}, headers);
auto body = getRequestBody(data);
Args args(uri, path, body, params, headers);
if (useCache && cacheIsValid(cache, args)) {
auto it = cache.find(args);
return it->second.result;
}
auto result = SendPostRequest(uri, path, token, data);
auto result = SendPostRequest(token, uri, path, params, data);
cache[args] = {result};
return result;
}
RequestResult SendPatchRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data)
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params, const OBSData &data)
{
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto pathWithParams = httplib::append_query_params(path, params);
auto url = uri + pathWithParams;
vblog(LOG_INFO, "Twitch PUT request to %s began", url.c_str());
auto headers = getTokenRequestHeaders(*tokenStr);
auto json = obs_data_get_json(data);
std::string body = json ? json : "";
auto response = cli.Patch(path, headers, body, "application/json");
if (!response) {
auto err = response.error();
blog(LOG_INFO, "%s failed - %s", __func__,
httplib::to_string(err).c_str());
return {};
}
RequestResult result;
result.status = response->status;
if (response->body.empty()) {
return result;
}
OBSDataAutoRelease replyData =
obs_data_create_from_json(response->body.c_str());
result.data = replyData;
return result;
auto body = getRequestBody(data);
auto response =
cli.Put(pathWithParams, headers, body, "application/json");
return processResult(response, __func__);
}
RequestResult SendPatchRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data,
bool useCache)
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params, const OBSData &data,
bool useCache)
{
static std::map<Args, CacheEntry> cache;
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto headers = getTokenRequestHeaders(*tokenStr);
auto jsonCstr = obs_data_get_json(data);
Args args(uri, path, jsonCstr ? jsonCstr : "", {}, headers);
auto body = getRequestBody(data);
Args args(uri, path, body, params, headers);
if (useCache && cacheIsValid(cache, args)) {
auto it = cache.find(args);
return it->second.result;
}
auto result = SendPatchRequest(uri, path, token, data);
auto result = SendPutRequest(token, uri, path, params, data);
cache[args] = {result};
return result;
}
const char *GetClientID()
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data)
{
return clientID.data();
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto pathWithParams = httplib::append_query_params(path, params);
auto url = uri + pathWithParams;
vblog(LOG_INFO, "Twitch PATCH request to %s began", url.c_str());
auto headers = getTokenRequestHeaders(*tokenStr);
auto body = getRequestBody(data);
auto response =
cli.Patch(pathWithParams, headers, body, "application/json");
return processResult(response, __func__);
}
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data, bool useCache)
{
static std::map<Args, CacheEntry> cache;
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto headers = getTokenRequestHeaders(*tokenStr);
auto body = getRequestBody(data);
Args args(uri, path, body, params, headers);
if (useCache && cacheIsValid(cache, args)) {
auto it = cache.find(args);
return it->second.result;
}
auto result = SendPatchRequest(token, uri, path, params, data);
cache[args] = {result};
return result;
}
RequestResult SendDeleteRequest(const TwitchToken &token,
const std::string &uri, const std::string &path,
const httplib::Params &params)
{
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
if (!tokenStr) {
return {};
}
auto pathWithParams = httplib::append_query_params(path, params);
auto url = uri + pathWithParams;
vblog(LOG_INFO, "Twitch DELETE request to %s began", url.c_str());
auto headers = getTokenRequestHeaders(*tokenStr);
auto response =
cli.Delete(pathWithParams, headers, "", "application/json");
return processResult(response, __func__);
}
} // namespace advss

View File

@@ -13,29 +13,44 @@ struct RequestResult {
OBSData data = nullptr;
};
// These functions do *not* use or create RequestResult cache entries
const char *GetClientID();
RequestResult SendGetRequest(const std::string &uri, const std::string &path,
const TwitchToken &token,
const httplib::Params & = {});
RequestResult SendPostRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data);
RequestResult SendPatchRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data);
// These functions do *not* use or create RequestResult cache entries
RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params = {});
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params = {},
const OBSData &data = nullptr);
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params = {},
const OBSData &data = nullptr);
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params = {},
const OBSData &data = nullptr);
RequestResult SendDeleteRequest(const TwitchToken &token,
const std::string &uri, const std::string &path,
const httplib::Params &params = {});
// These functions will cache the RequestResult for 10s
// Note that the cache will be reported as a "memory leak" on OBS shutdown
RequestResult SendGetRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const httplib::Params &,
RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params, bool useCache);
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data, bool useCache);
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params, const OBSData &data,
bool useCache);
RequestResult SendPostRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data,
bool useCache);
RequestResult SendPatchRequest(const std::string &uri, const std::string &path,
const TwitchToken &token, const OBSData &data,
bool useCache);
const char *GetClientID();
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data, bool useCache);
} // namespace advss

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