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 3954 additions and 1809 deletions

View File

@@ -7,3 +7,8 @@ package 'ninja-build', bin: 'ninja'
package 'pkg-config' package 'pkg-config'
package 'clang' package 'clang'
package 'clang-format-13' 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...' 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=() local -a _qt_packages=()
if (( QT_VERSION == 5 )) { if (( QT_VERSION == 5 )) {

View File

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

View File

@@ -159,7 +159,6 @@ AdvSceneSwitcher.condition.media="Medien"
AdvSceneSwitcher.condition.media.source="Quelle" AdvSceneSwitcher.condition.media.source="Quelle"
AdvSceneSwitcher.condition.media.anyOnScene="Beliebige Medienquelle in" AdvSceneSwitcher.condition.media.anyOnScene="Beliebige Medienquelle in"
AdvSceneSwitcher.condition.media.allOnScene="Alle Medienquellen 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.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.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}Status ist{{states}}und{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Video" AdvSceneSwitcher.condition.video="Video"
@@ -221,9 +220,8 @@ AdvSceneSwitcher.condition.record="Aufnahme"
AdvSceneSwitcher.condition.record.state.start="Aufnahme läuft" AdvSceneSwitcher.condition.record.state.start="Aufnahme läuft"
AdvSceneSwitcher.condition.record.state.pause="Aufnahme pausiert" AdvSceneSwitcher.condition.record.state.pause="Aufnahme pausiert"
AdvSceneSwitcher.condition.record.state.stop="Aufnahme gestoppt" AdvSceneSwitcher.condition.record.state.stop="Aufnahme gestoppt"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.process="Prozess" 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.process.entry.focus="Aktueller Vordergrundprozess: {{focusProcess}}"
AdvSceneSwitcher.condition.idle="Leerlauf" AdvSceneSwitcher.condition.idle="Leerlauf"
AdvSceneSwitcher.condition.idle.entry="Keine Tastatur- oder Mauseingaben für {{duration}}" 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.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.conditionNoVariableSupport="Das Abrufen von Variablenwerten aus %1 Bedingungen wird nicht unterstützt!"
AdvSceneSwitcher.action.variable.currentSegmentValue="Aktueller Wert:" AdvSceneSwitcher.action.variable.currentSegmentValue="Aktueller Wert:"
AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}"
; Transition Tab ; Transition Tab

View File

@@ -79,6 +79,7 @@ AdvSceneSwitcher.macroTab.edit.action="Action type:"
AdvSceneSwitcher.macroTab.add="Add new macro" AdvSceneSwitcher.macroTab.add="Add new macro"
AdvSceneSwitcher.macroTab.name="Name:" AdvSceneSwitcher.macroTab.name="Name:"
AdvSceneSwitcher.macroTab.run="Run macro" 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.runElse="Run macro (else)"
AdvSceneSwitcher.macroTab.runFail="Running \"%1\" failed!\nEither one of the actions failed or the macro is running already." 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" 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.true="Conditions true text:"
AdvSceneSwitcher.macroTab.currentDockStatusText.false="Conditions false text:" AdvSceneSwitcher.macroTab.currentDockStatusText.false="Conditions false text:"
AdvSceneSwitcher.macroTab.currentDockHighlightIfExecuted="Highlight dock if macro actions were recently executed" 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.pause="Pause"
AdvSceneSwitcher.macroDock.unpause="Unpause" AdvSceneSwitcher.macroDock.unpause="Unpause"
@@ -204,7 +207,6 @@ AdvSceneSwitcher.condition.media="Media"
AdvSceneSwitcher.condition.media.source="Source" AdvSceneSwitcher.condition.media.source="Source"
AdvSceneSwitcher.condition.media.anyOnScene="Any media source on" AdvSceneSwitcher.condition.media.anyOnScene="Any media source on"
AdvSceneSwitcher.condition.media.allOnScene="All media sources 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.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.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}state is{{states}}and{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Video" AdvSceneSwitcher.condition.video="Video"
@@ -295,9 +297,10 @@ AdvSceneSwitcher.condition.record="Recording"
AdvSceneSwitcher.condition.record.state.start="Recording running" AdvSceneSwitcher.condition.record.state.start="Recording running"
AdvSceneSwitcher.condition.record.state.pause="Recording paused" AdvSceneSwitcher.condition.record.state.pause="Recording paused"
AdvSceneSwitcher.condition.record.state.stop="Recording stopped" AdvSceneSwitcher.condition.record.state.stop="Recording stopped"
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="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.process.entry.focus="Current foreground process:{{focusProcess}}"
AdvSceneSwitcher.condition.idle="Idle" AdvSceneSwitcher.condition.idle="Idle"
AdvSceneSwitcher.condition.idle.entry="No keyboard or mouse inputs for{{duration}}" AdvSceneSwitcher.condition.idle.entry="No keyboard or mouse inputs for{{duration}}"
@@ -326,6 +329,7 @@ AdvSceneSwitcher.condition.macro.type.state="Macro condition state"
AdvSceneSwitcher.condition.macro.type.multiState="Multiple macro condition state" AdvSceneSwitcher.condition.macro.type.multiState="Multiple macro condition state"
AdvSceneSwitcher.condition.macro.type.actionDisabled="Macro action disabled" AdvSceneSwitcher.condition.macro.type.actionDisabled="Macro action disabled"
AdvSceneSwitcher.condition.macro.type.actionEnabled="Macro action enabled" 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.type.selection="{{types}}"
AdvSceneSwitcher.condition.macro.count.type.below="Less than" AdvSceneSwitcher.condition.macro.count.type.below="Less than"
AdvSceneSwitcher.condition.macro.count.type.above="More than" AdvSceneSwitcher.condition.macro.count.type.above="More than"
@@ -341,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.count.entry.line2="Current count:{{currentCount}}{{resetCount}}"
AdvSceneSwitcher.condition.macro.actionState.disabled.entry="Action{{actionIndex}}of{{macros}}is disabled" 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.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="Source"
AdvSceneSwitcher.condition.source.type.active="Is active" AdvSceneSwitcher.condition.source.type.active="Is active"
AdvSceneSwitcher.condition.source.type.showing="Is showing" AdvSceneSwitcher.condition.source.type.showing="Is showing"
@@ -604,6 +609,7 @@ AdvSceneSwitcher.action.streaming.type.username="Set username"
AdvSceneSwitcher.action.streaming.type.password="Set password" AdvSceneSwitcher.action.streaming.type.password="Set password"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}" AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="Run" 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="Scene item visibility"
AdvSceneSwitcher.action.sceneVisibility.type.show="Show" AdvSceneSwitcher.action.sceneVisibility.type.show="Show"
AdvSceneSwitcher.action.sceneVisibility.type.hide="Hide" AdvSceneSwitcher.action.sceneVisibility.type.hide="Hide"
@@ -814,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.stringLength="Set to length of string"
AdvSceneSwitcher.action.variable.type.extractJson="Extract json field with name" AdvSceneSwitcher.action.variable.type.extractJson="Extract json field with name"
AdvSceneSwitcher.action.variable.type.setToTempvar="Set to macro property" 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.askForValuePromptDefault="Assign value to variable \"%1\":"
AdvSceneSwitcher.action.variable.askForValuePrompt="Assign value to variable:" AdvSceneSwitcher.action.variable.askForValuePrompt="Assign value to variable:"
AdvSceneSwitcher.action.variable.mathExpression.example="( 1 + 2 * 3 ) / 4" AdvSceneSwitcher.action.variable.mathExpression.example="( 1 + 2 * 3 ) / 4"
@@ -825,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.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.conditionNoVariableSupport="Getting variable values from %1 conditions is not supported!"
AdvSceneSwitcher.action.variable.currentSegmentValue="Current value:" 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.substringIndex="Substring start:{{subStringStart}}Substring size:{{subStringSize}}"
AdvSceneSwitcher.action.variable.entry.substringRegex="Assign value of{{regexMatchIdx}}match using regular expression:" AdvSceneSwitcher.action.variable.entry.substringRegex="Assign value of{{regexMatchIdx}}match using regular expression:"
AdvSceneSwitcher.action.variable.entry.findAndReplace="{{findStr}}{{replaceStr}}" AdvSceneSwitcher.action.variable.entry.findAndReplace="{{findStr}}{{replaceStr}}"
@@ -852,15 +859,15 @@ AdvSceneSwitcher.action.sceneLock.type.unlock="unlock"
AdvSceneSwitcher.action.sceneLock.type.toggle="toggle lock of" AdvSceneSwitcher.action.sceneLock.type.toggle="toggle lock of"
AdvSceneSwitcher.action.sceneLock.entry="On{{scenes}}{{actions}}{{sources}}" AdvSceneSwitcher.action.sceneLock.entry="On{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.twitch="Twitch" AdvSceneSwitcher.action.twitch="Twitch"
AdvSceneSwitcher.action.twitch.type.title="Set stream title" AdvSceneSwitcher.action.twitch.type.channel.info.title.set="Set stream title"
AdvSceneSwitcher.action.twitch.type.category="Set stream category" AdvSceneSwitcher.action.twitch.type.channel.info.category.set="Set stream category"
AdvSceneSwitcher.action.twitch.type.marker="Create stream marker" AdvSceneSwitcher.action.twitch.type.raid.start="Start raid"
AdvSceneSwitcher.action.twitch.type.clip="Create stream clip" AdvSceneSwitcher.action.twitch.type.commercial.start="Start commercial"
AdvSceneSwitcher.action.twitch.type.commercial="Start commercial with duration" AdvSceneSwitcher.action.twitch.type.marker.create="Create stream marker"
AdvSceneSwitcher.action.twitch.type.announcement="Send chat announcement" AdvSceneSwitcher.action.twitch.type.clip.create="Create stream clip"
AdvSceneSwitcher.action.twitch.type.emoteOnlyEnable="Enable chat's emote-only mode" AdvSceneSwitcher.action.twitch.type.chat.announcement.send="Send chat announcement"
AdvSceneSwitcher.action.twitch.type.emoteOnlyDisable="Disable chat's emote-only mode" AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable="Enable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.raid="Raid channel" AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="Disable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.sendChatMessage="Send chat message" AdvSceneSwitcher.action.twitch.type.sendChatMessage="Send chat message"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!" AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!"
AdvSceneSwitcher.action.twitch.entry.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}" AdvSceneSwitcher.action.twitch.entry.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}"
@@ -874,6 +881,12 @@ AdvSceneSwitcher.action.twitch.announcement.blue="Blue"
AdvSceneSwitcher.action.twitch.announcement.green="Green" AdvSceneSwitcher.action.twitch.announcement.green="Green"
AdvSceneSwitcher.action.twitch.announcement.orange="Orange" AdvSceneSwitcher.action.twitch.announcement.orange="Orange"
AdvSceneSwitcher.action.twitch.announcement.purple="Purple" 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 ; Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher" AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher"
@@ -1027,14 +1040,10 @@ AdvSceneSwitcher.twitchToken.request.notSet="Account is not connected!"
AdvSceneSwitcher.twitchToken.permissions="Token permissions:" AdvSceneSwitcher.twitchToken.permissions="Token permissions:"
AdvSceneSwitcher.twitchToken.permissionsInsufficient="Permissions of selected token are insufficient to perform selected action!" 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.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.channel.subscriptions.read="Read channel's subscribers list and info."
AdvSceneSwitcher.twitchToken.moderator.followers.read="Read the followers of a broadcaster." AdvSceneSwitcher.twitchToken.moderator.followers.read="Read the followers of a broadcaster."
AdvSceneSwitcher.twitchToken.bits.read="Read channel's Bits information." 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.read="Read channel's shoutouts."
AdvSceneSwitcher.twitchToken.moderator.shoutouts.manage="Manage channel's shoutouts." AdvSceneSwitcher.twitchToken.moderator.shoutouts.manage="Manage channel's shoutouts."
AdvSceneSwitcher.twitchToken.channel.polls.read="Read channel's polls' state." AdvSceneSwitcher.twitchToken.channel.polls.read="Read channel's polls' state."
@@ -1049,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.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.redemptions.manage="Manage channel's point rewards and their redemptions."
AdvSceneSwitcher.twitchToken.channel.moderate="Moderate the channel." 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.read="View live stream chat messages."
AdvSceneSwitcher.twitchToken.chat.edit="Send live stream chat messages." AdvSceneSwitcher.twitchToken.chat.edit="Send live stream chat messages."
@@ -1275,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.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.id.reward="Reward id"
AdvSceneSwitcher.tempVar.twitch.user_id.reward="User id redeeming reward" AdvSceneSwitcher.tempVar.twitch.user_id.reward="User id redeeming the reward"
AdvSceneSwitcher.tempVar.twitch.user_login.reward="User login redeeming reward" AdvSceneSwitcher.tempVar.twitch.user_login.reward="User login redeeming the reward"
AdvSceneSwitcher.tempVar.twitch.user_name.reward="User login redeeming 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.user_input.reward="User input for reward"
AdvSceneSwitcher.tempVar.twitch.reward.reward="Reward Information" 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." AdvSceneSwitcher.tempVar.twitch.reward.reward.description="Basic information about the reward that was redeemed, at the time it was redeemed."
@@ -1412,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.chatter.description="The user login of the person who sent the chat message."
AdvSceneSwitcher.tempVar.twitch.chat_message="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="Output volume"
AdvSceneSwitcher.tempVar.audio.output_volume.description="The volume the audio source is outputting." AdvSceneSwitcher.tempVar.audio.output_volume.description="The volume the audio source is outputting."
@@ -1422,6 +1455,11 @@ AdvSceneSwitcher.tempVar.audio.sync_offset="Source audio sync offset"
AdvSceneSwitcher.tempVar.audio.monitor="Source audio monitor type" AdvSceneSwitcher.tempVar.audio.monitor="Source audio monitor type"
AdvSceneSwitcher.tempVar.audio.balance="Source audio balance" 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.current="Current scene"
AdvSceneSwitcher.tempVar.scene.previous="Previous scene" AdvSceneSwitcher.tempVar.scene.previous="Previous scene"
AdvSceneSwitcher.tempVar.scene.preview="Preview scene" AdvSceneSwitcher.tempVar.scene.preview="Preview scene"
@@ -1442,6 +1480,33 @@ AdvSceneSwitcher.tempVar.date.minute="Minute"
AdvSceneSwitcher.tempVar.date.second="Second" AdvSceneSwitcher.tempVar.date.second="Second"
AdvSceneSwitcher.tempVar.date.dayOfWeek="Day of week" 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.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene" AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current 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="Medios"
AdvSceneSwitcher.condition.media.anyOnScene="Cualquier fuente multimedia activada" AdvSceneSwitcher.condition.media.anyOnScene="Cualquier fuente multimedia activada"
AdvSceneSwitcher.condition.media.allOnScene="Todas las fuentes de medios activadas" 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.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.media.entry="El estado de{{sourceTypes}}{{mediaSources}}{{scenes}}es{{states}}y{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Video" 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.start="Grabación en ejecución"
AdvSceneSwitcher.condition.record.state.pause="Grabación en pausa" AdvSceneSwitcher.condition.record.state.pause="Grabación en pausa"
AdvSceneSwitcher.condition.record.state.stop="Grabación detenida" AdvSceneSwitcher.condition.record.state.stop="Grabación detenida"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.process="Proceso" 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="Inactivo"
AdvSceneSwitcher.condition.idle.entry="No hay entradas de teclado o ratón durante {{duration}}" AdvSceneSwitcher.condition.idle.entry="No hay entradas de teclado o ratón durante {{duration}}"
AdvSceneSwitcher.condition.pluginState="Estado del complemento" 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.source="Source"
AdvSceneSwitcher.condition.media.anyOnScene="Toute source média sur la scène" 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.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.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.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}l'état est{{states}}et{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Vidéo" 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.pause="Enregistrement en pause"
AdvSceneSwitcher.condition.record.state.stop="Arrêt de l'enregistrement" AdvSceneSwitcher.condition.record.state.stop="Arrêt de l'enregistrement"
AdvSceneSwitcher.condition.process="Processus" 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.process.entry.focus="Processus au premier plan actuel :{{focusProcess}}"
AdvSceneSwitcher.condition.idle="Inactif" AdvSceneSwitcher.condition.idle="Inactif"
AdvSceneSwitcher.condition.idle.entry="Aucune entrée de clavier ou de souris pendant{{duration}}" 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.type.toggle="basculer le verrouillage de"
AdvSceneSwitcher.action.sceneLock.entry="Sur{{scenes}}{{actions}}{{sources}}" AdvSceneSwitcher.action.sceneLock.entry="Sur{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.twitch="Twitch" AdvSceneSwitcher.action.twitch="Twitch"
AdvSceneSwitcher.action.twitch.type.title="Définir le titre du flux" AdvSceneSwitcher.action.twitch.type.channel.info.title.set="Définir le titre du flux"
AdvSceneSwitcher.action.twitch.type.category="Définir la catégorie du flux" AdvSceneSwitcher.action.twitch.type.channel.info.category.set="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.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.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 ; Transition Tab
AdvSceneSwitcher.transitionTab.title="Transition" AdvSceneSwitcher.transitionTab.title="Transition"

View File

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

View File

@@ -119,7 +119,6 @@ AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFil
AdvSceneSwitcher.condition.media="Medya" AdvSceneSwitcher.condition.media="Medya"
AdvSceneSwitcher.condition.media.anyOnScene="Herhangi bir medya kaynağı" AdvSceneSwitcher.condition.media.anyOnScene="Herhangi bir medya kaynağı"
AdvSceneSwitcher.condition.media.allOnScene="Tüm medya kaynakları " 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.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}durumu{{states}}ve{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Video" AdvSceneSwitcher.condition.video="Video"
AdvSceneSwitcher.condition.video.condition.match="Tam olarak eşleşir" 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.start="Kayıt Çalışıyor"
AdvSceneSwitcher.condition.record.state.pause="Kayıt durakladı" AdvSceneSwitcher.condition.record.state.pause="Kayıt durakladı"
AdvSceneSwitcher.condition.record.state.stop="Kayıt durdu" AdvSceneSwitcher.condition.record.state.stop="Kayıt durdu"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.process="İşlem" 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="Boşta"
AdvSceneSwitcher.condition.idle.entry="...için klavye veya fare girişi yok {{duration}}" AdvSceneSwitcher.condition.idle.entry="...için klavye veya fare girişi yok {{duration}}"
AdvSceneSwitcher.condition.pluginState="Eklenti durumu" AdvSceneSwitcher.condition.pluginState="Eklenti durumu"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
#include "macro-action-audio.hpp" #include "macro-action-audio.hpp"
#include "switcher-data.hpp" #include "macro.hpp"
#include "utility.hpp" #include "utility.hpp"
namespace advss { namespace advss {
@@ -36,6 +36,16 @@ const static std::map<MacroActionAudio::FadeType, std::string> fadeTypes = {
"AdvSceneSwitcher.action.audio.fade.type.rate"}, "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 auto fadeInterval = std::chrono::milliseconds(100);
constexpr float minFade = 0.000001f; constexpr float minFade = 0.000001f;
@@ -58,10 +68,9 @@ auto set_master_volume = obs_set_master_volume;
void MacroActionAudio::SetFadeActive(bool value) void MacroActionAudio::SetFadeActive(bool value)
{ {
if (_action == Action::SOURCE_VOLUME) { if (_action == Action::SOURCE_VOLUME) {
switcher->activeAudioFades[_audioSource.ToString()].active = audioFades[_audioSource.ToString()].active = value;
value;
} else { } else {
switcher->masterAudioFade.active = value; masterAudioFade.active = value;
} }
} }
@@ -69,14 +78,13 @@ bool MacroActionAudio::FadeActive()
{ {
bool active = true; bool active = true;
if (_action == Action::SOURCE_VOLUME) { if (_action == Action::SOURCE_VOLUME) {
auto it = switcher->activeAudioFades.find( auto it = audioFades.find(_audioSource.ToString());
_audioSource.ToString()); if (it == audioFades.end()) {
if (it == switcher->activeAudioFades.end()) {
return false; return false;
} }
active = it->second.active; active = it->second.active;
} else { } else {
active = switcher->masterAudioFade.active; active = masterAudioFade.active;
} }
return active; return active;
@@ -86,14 +94,13 @@ std::atomic_int *MacroActionAudio::GetFadeIdPtr()
{ {
if (_action == Action::SOURCE_VOLUME) { if (_action == Action::SOURCE_VOLUME) {
auto it = switcher->activeAudioFades.find( auto it = audioFades.find(_audioSource.ToString());
_audioSource.ToString()); if (it == audioFades.end()) {
if (it == switcher->activeAudioFades.end()) {
return nullptr; return nullptr;
} }
return &it->second.id; return &it->second.id;
} }
return &switcher->masterAudioFade.id; return &masterAudioFade.id;
} }
void MacroActionAudio::SetVolume(float vol) 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-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 "macro-action-scene-switch.hpp"
#include "section.hpp" #include "section.hpp"
#include "switch-button.hpp" #include "switch-button.hpp"
@@ -10,61 +12,9 @@
namespace advss { 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) 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())); QString entry(obs_module_text(action._name.c_str()));
if (list->findText(entry) == -1) { if (list->findText(entry) == -1) {
list->addItem(entry); list->addItem(entry);
@@ -80,7 +30,8 @@ static inline void populateActionSelection(QComboBox *list)
MacroActionEdit::MacroActionEdit(QWidget *parent, MacroActionEdit::MacroActionEdit(QWidget *parent,
std::shared_ptr<MacroAction> *entryData, std::shared_ptr<MacroAction> *entryData,
const std::string &id) const std::string &id)
: MacroSegmentEdit(switcher->macroProperties._highlightActions, parent), : MacroSegmentEdit(GetGlobalMacroProperties()._highlightActions,
parent),
_actionSelection(new FilterComboBox()), _actionSelection(new FilterComboBox()),
_enable(new SwitchButton()), _enable(new SwitchButton()),
_entryData(entryData) _entryData(entryData)
@@ -134,7 +85,7 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
auto idx = _entryData->get()->GetIndex(); auto idx = _entryData->get()->GetIndex();
auto macro = _entryData->get()->GetMacro(); auto macro = _entryData->get()->GetMacro();
{ {
std::lock_guard<std::mutex> lock(switcher->m); auto lock = LockContext();
_entryData->reset(); _entryData->reset();
*_entryData = MacroActionFactory::Create(id, macro); *_entryData = MacroActionFactory::Create(id, macro);
(*_entryData)->SetIndex(idx); (*_entryData)->SetIndex(idx);
@@ -185,7 +136,7 @@ void MacroActionEdit::ActionEnableChanged(bool value)
return; return;
} }
std::lock_guard<std::mutex> lock(switcher->m); auto lock = LockContext();
(*_entryData)->SetEnabled(value); (*_entryData)->SetEnabled(value);
SetDisableEffect(!value); SetDisableEffect(!value);
} }
@@ -229,7 +180,7 @@ void AdvSceneSwitcher::AddMacroAction(int idx)
id = temp.GetId(); id = temp.GetId();
} }
{ {
std::lock_guard<std::mutex> lock(switcher->m); auto lock = LockContext();
macro->Actions().emplace( macro->Actions().emplace(
macro->Actions().begin() + idx, macro->Actions().begin() + idx,
MacroActionFactory::Create(id, macro.get())); 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); ui->actionsList->Remove(idx);
macro->Actions().erase(macro->Actions().begin() + idx); macro->Actions().erase(macro->Actions().begin() + idx);
switcher->abortMacroWait = true; SetMacroAbortWait(true);
switcher->macroWaitCv.notify_all(); GetMacroWaitCV().notify_all();
macro->UpdateActionIndices(); macro->UpdateActionIndices();
SetActionData(*macro); SetActionData(*macro);
} }
@@ -425,7 +376,7 @@ void AdvSceneSwitcher::SwapActions(Macro *m, int pos1, int pos2)
std::swap(pos1, 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); iter_swap(m->Actions().begin() + pos1, m->Actions().begin() + pos2);
m->UpdateActionIndices(); m->UpdateActionIndices();
auto widget1 = static_cast<MacroActionEdit *>( auto widget1 = static_cast<MacroActionEdit *>(
@@ -485,7 +436,7 @@ void AdvSceneSwitcher::MacroElseActionReorder(int to, int from)
return; return;
} }
{ {
std::lock_guard<std::mutex> lock(switcher->m); auto lock = LockContext();
auto action = macro->ElseActions().at(from); auto action = macro->ElseActions().at(from);
macro->ElseActions().erase(macro->ElseActions().begin() + from); macro->ElseActions().erase(macro->ElseActions().begin() + from);
macro->ElseActions().insert(macro->ElseActions().begin() + to, macro->ElseActions().insert(macro->ElseActions().begin() + to,
@@ -518,7 +469,7 @@ void AdvSceneSwitcher::AddMacroElseAction(int idx)
id = temp.GetId(); id = temp.GetId();
} }
{ {
std::lock_guard<std::mutex> lock(switcher->m); auto lock = LockContext();
macro->ElseActions().emplace( macro->ElseActions().emplace(
macro->ElseActions().begin() + idx, macro->ElseActions().begin() + idx,
MacroActionFactory::Create(id, macro.get())); 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); ui->elseActionsList->Remove(idx);
macro->ElseActions().erase(macro->ElseActions().begin() + idx); macro->ElseActions().erase(macro->ElseActions().begin() + idx);
switcher->abortMacroWait = true; SetMacroAbortWait(true);
switcher->macroWaitCv.notify_all(); GetMacroWaitCV().notify_all();
macro->UpdateElseActionIndices(); macro->UpdateElseActionIndices();
SetActionData(*macro); SetActionData(*macro);
} }
@@ -572,7 +523,7 @@ void AdvSceneSwitcher::SwapElseActions(Macro *m, int pos1, int pos2)
std::swap(pos1, pos2); std::swap(pos1, pos2);
} }
std::lock_guard<std::mutex> lock(switcher->m); auto lock = LockContext();
iter_swap(m->ElseActions().begin() + pos1, iter_swap(m->ElseActions().begin() + pos1,
m->ElseActions().begin() + pos2); m->ElseActions().begin() + pos2);
m->UpdateElseActionIndices(); m->UpdateElseActionIndices();
@@ -635,7 +586,7 @@ void AdvSceneSwitcher::MacroActionReorder(int to, int from)
return; return;
} }
{ {
std::lock_guard<std::mutex> lock(switcher->m); auto lock = LockContext();
auto action = macro->Actions().at(from); auto action = macro->Actions().at(from);
macro->Actions().erase(macro->Actions().begin() + from); macro->Actions().erase(macro->Actions().begin() + from);
macro->Actions().insert(macro->Actions().begin() + to, action); macro->Actions().insert(macro->Actions().begin() + to, action);

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "macro-action.hpp" #include "macro-action.hpp"
#include "macro-action-factory.hpp"
#include "filter-combo-box.hpp" #include "filter-combo-box.hpp"
#include <memory> #include <memory>
@@ -8,32 +9,6 @@ namespace advss {
class SwitchButton; 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 { class MacroActionEdit : public MacroSegmentEdit {
Q_OBJECT 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 == _entryData->_settingsInputMethod ==
MacroActionFilter::SettingsInputMethod:: MacroActionFilter::SettingsInputMethod::
INDIVIDUAL_TEMPVAR); INDIVIDUAL_TEMPVAR);
_manualSettingValue->setVisible(
_entryData->_action == MacroActionFilter::Action::SETTINGS && if (_entryData->_action == MacroActionFilter::Action::SETTINGS &&
_entryData->_settingsInputMethod == _entryData->_settingsInputMethod ==
MacroActionFilter::SettingsInputMethod:: MacroActionFilter::SettingsInputMethod::INDIVIDUAL_MANUAL) {
INDIVIDUAL_MANUAL); RemoveStretchIfPresent(_settingsLayout);
_manualSettingValue->show();
} else {
AddStretchIfNecessary(_settingsLayout);
_manualSettingValue->hide();
}
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();

View File

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

View File

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

View File

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

View File

@@ -14,9 +14,14 @@ bool MacroActionRun::_registered = MacroActionFactory::Register(
bool MacroActionRun::PerformAction() bool MacroActionRun::PerformAction()
{ {
bool procStarted = QProcess::startDetached( if (_wait) {
QString::fromStdString(_procConfig.Path()), _procConfig.Args(), _procConfig.StartProcessAndWait(_timeout.Milliseconds());
QString::fromStdString(_procConfig.WorkingDir())); return true;
}
bool procStarted = _procConfig.StartProcessDetached();
// Fall back to using default application to open given file
if (!procStarted && _procConfig.Args().empty()) { if (!procStarted && _procConfig.Args().empty()) {
vblog(LOG_INFO, "run \"%s\" using QDesktopServices", vblog(LOG_INFO, "run \"%s\" using QDesktopServices",
_procConfig.Path().c_str()); _procConfig.Path().c_str());
@@ -35,6 +40,9 @@ bool MacroActionRun::Save(obs_data_t *obj) const
{ {
MacroAction::Save(obj); MacroAction::Save(obj);
_procConfig.Save(obj); _procConfig.Save(obj);
_timeout.Save(obj);
obs_data_set_bool(obj, "wait", _wait);
obs_data_set_int(obj, "version", 1);
return true; return true;
} }
@@ -42,6 +50,12 @@ bool MacroActionRun::Load(obs_data_t *obj)
{ {
MacroAction::Load(obj); MacroAction::Load(obj);
_procConfig.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; return true;
} }
@@ -52,14 +66,30 @@ std::string MacroActionRun::GetShortDesc() const
MacroActionRunEdit::MacroActionRunEdit( MacroActionRunEdit::MacroActionRunEdit(
QWidget *parent, std::shared_ptr<MacroActionRun> entryData) 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, QWidget::connect(_procConfig,
SIGNAL(ConfigChanged(const ProcessConfig &)), this, SIGNAL(ConfigChanged(const ProcessConfig &)), this,
SLOT(ProcessConfigChanged(const ProcessConfig &))); 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->addWidget(_procConfig);
layout->addLayout(_waitLayout);
setLayout(layout); setLayout(layout);
_entryData = entryData; _entryData = entryData;
@@ -73,6 +103,31 @@ void MacroActionRunEdit::UpdateEntryData()
return; return;
} }
_procConfig->SetProcessConfig(_entryData->_procConfig); _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) void MacroActionRunEdit::ProcessConfigChanged(const ProcessConfig &conf)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -638,10 +638,24 @@ void MacroConditionDateEdit::AdvancedSettingsToggleClicked()
{ {
auto lock = LockContext(); auto lock = LockContext();
_entryData->_dayOfWeekCheck = !_entryData->_dayOfWeekCheck; _entryData->_dayOfWeekCheck = !_entryData->_dayOfWeekCheck;
_entryData->_condition = MacroConditionDate::Condition::AT;
} }
switch (_entryData->_condition) {
case MacroConditionDate::Condition::BETWEEN:
case MacroConditionDate::Condition::PATTERN:
_condition->setCurrentIndex(0); _condition->setCurrentIndex(0);
_weekCondition->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(); SetWidgetStatus();
emit HeaderInfoChanged( emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc())); 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-condition-edit.hpp"
#include "macro.hpp"
#include "macro-properties.hpp"
#include "advanced-scene-switcher.hpp"
#include "macro-condition-scene.hpp" #include "macro-condition-scene.hpp"
#include "section.hpp" #include "section.hpp"
#include "utility.hpp" #include "utility.hpp"
namespace advss { 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) static inline void populateLogicSelection(QComboBox *list, bool root = false)
{ {
if (root) { if (root) {
for (auto entry : MacroCondition::logicTypes) { for (const auto &entry : MacroCondition::logicTypes) {
if (static_cast<int>(entry.first) < logic_root_offset) { if (static_cast<int>(entry.first) < logic_root_offset) {
list->addItem(obs_module_text( list->addItem(obs_module_text(
entry.second._name.c_str())); entry.second._name.c_str()));
} }
} }
} else { } else {
for (auto entry : MacroCondition::logicTypes) { for (const auto &entry : MacroCondition::logicTypes) {
if (static_cast<int>(entry.first) >= if (static_cast<int>(entry.first) >=
logic_root_offset) { logic_root_offset) {
list->addItem(obs_module_text( list->addItem(obs_module_text(
@@ -90,7 +30,7 @@ static inline void populateLogicSelection(QComboBox *list, bool root = false)
static inline void populateConditionSelection(QComboBox *list) static inline void populateConditionSelection(QComboBox *list)
{ {
for (auto &[_, condition] : for (const auto &[_, condition] :
MacroConditionFactory::GetConditionTypes()) { MacroConditionFactory::GetConditionTypes()) {
QString entry(obs_module_text(condition._name.c_str())); QString entry(obs_module_text(condition._name.c_str()));
if (list->findText(entry) == -1) { if (list->findText(entry) == -1) {
@@ -179,7 +119,7 @@ void DurationModifierEdit::Collapse(bool collapse)
MacroConditionEdit::MacroConditionEdit( MacroConditionEdit::MacroConditionEdit(
QWidget *parent, std::shared_ptr<MacroCondition> *entryData, QWidget *parent, std::shared_ptr<MacroCondition> *entryData,
const std::string &id, bool root) const std::string &id, bool root)
: MacroSegmentEdit(switcher->macroProperties._highlightConditions, : MacroSegmentEdit(GetGlobalMacroProperties()._highlightConditions,
parent), parent),
_logicSelection(new QComboBox()), _logicSelection(new QComboBox()),
_conditionSelection(new FilterComboBox()), _conditionSelection(new FilterComboBox()),

View File

@@ -1,38 +1,12 @@
#pragma once #pragma once
#include "macro-condition.hpp" #include "macro-condition.hpp"
#include "macro-condition-factory.hpp"
#include "filter-combo-box.hpp" #include "filter-combo-box.hpp"
#include <memory> #include <memory>
namespace advss { 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 { class DurationModifierEdit : public QWidget {
Q_OBJECT Q_OBJECT
public: 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 "macro-condition-file.hpp"
#include "utility.hpp" #include "utility.hpp"
#include "switcher-data.hpp"
#include "curl-helper.hpp" #include "curl-helper.hpp"
#include "plugin-state-helpers.hpp"
#include <QTextStream> #include <QTextStream>
#include <QFileDialog> #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) static std::string getRemoteData(std::string &url)
{ {
std::string readBuffer; std::string readBuffer;
switcher->curl.SetOpt(CURLOPT_URL, url.c_str()); CurlHelper::SetOpt(CURLOPT_URL, url.c_str());
switcher->curl.SetOpt(CURLOPT_WRITEFUNCTION, WriteCallback); CurlHelper::SetOpt(CURLOPT_WRITEFUNCTION, WriteCallback);
switcher->curl.SetOpt(CURLOPT_WRITEDATA, &readBuffer); CurlHelper::SetOpt(CURLOPT_WRITEDATA, &readBuffer);
// Set timeout to at least one second // Set timeout to at least one second
int timeout = switcher->interval / 1000; int timeout = GetIntervalValue() / 1000;
if (timeout == 0) { if (timeout == 0) {
timeout = 1; timeout = 1;
} }
switcher->curl.SetOpt(CURLOPT_TIMEOUT, 1); CurlHelper::SetOpt(CURLOPT_TIMEOUT, 1);
switcher->curl.Perform(); CurlHelper::Perform();
return readBuffer; return readBuffer;
} }

View File

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

View File

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

View File

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

View File

@@ -72,7 +72,6 @@ public:
OBSWeakSource _rawSource = nullptr; OBSWeakSource _rawSource = nullptr;
std::vector<MacroConditionMedia> _sourceGroup; std::vector<MacroConditionMedia> _sourceGroup;
Duration _time; Duration _time;
bool _onlyMatchOnChagne = false;
private: private:
bool CheckTime(); bool CheckTime();
@@ -84,9 +83,7 @@ private:
bool _stopped = false; bool _stopped = false;
bool _ended = false; bool _ended = false;
bool _next = 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 // Workaround to enable use of "ended" to specify end of VLC playlist
bool _previousStateEnded = false; bool _previousStateEnded = false;
// Used to keep track of scene changes // Used to keep track of scene changes
@@ -119,7 +116,6 @@ private slots:
void StateChanged(int index); void StateChanged(int index);
void TimeRestrictionChanged(int index); void TimeRestrictionChanged(int index);
void TimeChanged(const Duration &seconds); void TimeChanged(const Duration &seconds);
void OnChangeChanged(int);
signals: signals:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
@@ -130,7 +126,6 @@ protected:
QComboBox *_states; QComboBox *_states;
QComboBox *_timeRestrictions; QComboBox *_timeRestrictions;
DurationSelection *_time; DurationSelection *_time;
QCheckBox *_onChange;
std::shared_ptr<MacroConditionMedia> _entryData; std::shared_ptr<MacroConditionMedia> _entryData;
private: private:

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
#include "macro-condition-recording.hpp" #include "macro-condition-recording.hpp"
#include "utility.hpp" #include "utility.hpp"
#include <thread>
namespace advss { namespace advss {
const std::string MacroConditionRecord::id = "recording"; const std::string MacroConditionRecord::id = "recording";
@@ -10,45 +12,81 @@ bool MacroConditionRecord::_registered = MacroConditionFactory::Register(
{MacroConditionRecord::Create, MacroConditionRecordEdit::Create, {MacroConditionRecord::Create, MacroConditionRecordEdit::Create,
"AdvSceneSwitcher.condition.record"}); "AdvSceneSwitcher.condition.record"});
const static std::map<RecordState, std::string> recordStates = { const static std::map<MacroConditionRecord::Condition, std::string>
{RecordState::STOP, "AdvSceneSwitcher.condition.record.state.stop"}, recordStates = {
{RecordState::PAUSE, "AdvSceneSwitcher.condition.record.state.pause"}, {MacroConditionRecord::Condition::STOP,
{RecordState::START, "AdvSceneSwitcher.condition.record.state.start"}, "AdvSceneSwitcher.condition.record.state.stop"},
{MacroConditionRecord::Condition::PAUSE,
"AdvSceneSwitcher.condition.record.state.pause"},
{MacroConditionRecord::Condition::START,
"AdvSceneSwitcher.condition.record.state.start"},
{MacroConditionRecord::Condition::DURATION,
"AdvSceneSwitcher.condition.record.state.duration"},
}; };
static bool SetupRecordingTimer();
static bool recordingTimerIsSetup = SetupRecordingTimer();
static int currentRecordingDurationInSeconds = 0;
bool MacroConditionRecord::CheckCondition() bool MacroConditionRecord::CheckCondition()
{ {
bool stateMatch = false; switch (_condition) {
switch (_recordState) { case Condition::STOP:
case RecordState::STOP: return !obs_frontend_recording_active();
stateMatch = !obs_frontend_recording_active(); case Condition::PAUSE:
break; return obs_frontend_recording_paused();
case RecordState::PAUSE: case Condition::START:
stateMatch = obs_frontend_recording_paused(); return obs_frontend_recording_active();
break; case Condition::DURATION:
case RecordState::START: SetTempVarValue(
stateMatch = obs_frontend_recording_active(); "durationSeconds",
break; std::to_string(currentRecordingDurationInSeconds));
return currentRecordingDurationInSeconds > _duration.Seconds();
default: default:
break; break;
} }
return stateMatch; return false;
} }
bool MacroConditionRecord::Save(obs_data_t *obj) const bool MacroConditionRecord::Save(obs_data_t *obj) const
{ {
MacroCondition::Save(obj); MacroCondition::Save(obj);
obs_data_set_int(obj, "state", static_cast<int>(_recordState)); obs_data_set_int(obj, "state", static_cast<int>(_condition));
_duration.Save(obj);
return true; return true;
} }
bool MacroConditionRecord::Load(obs_data_t *obj) bool MacroConditionRecord::Load(obs_data_t *obj)
{ {
MacroCondition::Load(obj); MacroCondition::Load(obj);
_recordState = static_cast<RecordState>(obs_data_get_int(obj, "state")); _condition = static_cast<Condition>(obs_data_get_int(obj, "state"));
_duration.Load(obj);
return true; return true;
} }
void MacroConditionRecord::SetCondition(Condition condition)
{
_condition = condition;
SetupTempVars();
}
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;
}
}
static inline void populateStateSelection(QComboBox *list) static inline void populateStateSelection(QComboBox *list)
{ {
for (auto entry : recordStates) { for (auto entry : recordStates) {
@@ -58,38 +96,39 @@ static inline void populateStateSelection(QComboBox *list)
MacroConditionRecordEdit::MacroConditionRecordEdit( MacroConditionRecordEdit::MacroConditionRecordEdit(
QWidget *parent, std::shared_ptr<MacroConditionRecord> entryData) 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, QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(StateChanged(int))); SLOT(ConditionChanged(int)));
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
populateStateSelection(_recordState); this, SLOT(DurationChanged(const Duration &)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{recordState}}", _recordState},
};
auto layout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.record.entry"), PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.record.entry"),
mainLayout, widgetPlaceholders); layout,
setLayout(mainLayout); {{"{{condition}}", _condition},
{"{{duration}}", _duration}});
setLayout(layout);
_entryData = entryData; _entryData = entryData;
UpdateEntryData(); UpdateEntryData();
_loading = false; _loading = false;
} }
void MacroConditionRecordEdit::StateChanged(int value) void MacroConditionRecordEdit::ConditionChanged(int value)
{ {
if (_loading || !_entryData) { if (_loading || !_entryData) {
return; return;
} }
auto lock = LockContext(); auto lock = LockContext();
_entryData->_recordState = static_cast<RecordState>(value); _entryData->SetCondition(
static_cast<MacroConditionRecord::Condition>(value));
SetWidgetVisibility();
} }
void MacroConditionRecordEdit::UpdateEntryData() void MacroConditionRecordEdit::UpdateEntryData()
@@ -98,8 +137,67 @@ void MacroConditionRecordEdit::UpdateEntryData()
return; return;
} }
_recordState->setCurrentIndex( _condition->setCurrentIndex(
static_cast<int>(_entryData->_recordState)); 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 } // namespace advss

View File

@@ -6,12 +6,6 @@
namespace advss { namespace advss {
enum class RecordState {
STOP,
PAUSE,
START,
};
class MacroConditionRecord : public MacroCondition { class MacroConditionRecord : public MacroCondition {
public: public:
MacroConditionRecord(Macro *m) : MacroCondition(m) {} MacroConditionRecord(Macro *m) : MacroCondition(m) {}
@@ -24,9 +18,21 @@ public:
return std::make_shared<MacroConditionRecord>(m); return std::make_shared<MacroConditionRecord>(m);
} }
RecordState _recordState = RecordState::STOP; enum class Condition {
STOP,
PAUSE,
START,
DURATION,
};
void SetCondition(Condition);
Condition GetCondition() const { return _condition; }
Duration _duration;
private: private:
void SetupTempVars();
Condition _condition = Condition::STOP;
static bool _registered; static bool _registered;
static const std::string id; static const std::string id;
}; };
@@ -48,13 +54,15 @@ public:
} }
private slots: private slots:
void StateChanged(int value); void ConditionChanged(int value);
void DurationChanged(const Duration &);
protected:
QComboBox *_recordState;
std::shared_ptr<MacroConditionRecord> _entryData;
private: private:
void SetWidgetVisibility();
QComboBox *_condition;
DurationSelection *_duration;
std::shared_ptr<MacroConditionRecord> _entryData;
bool _loading = true; bool _loading = true;
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
#include "macro-condition-streaming.hpp" #include "macro-condition-streaming.hpp"
#include "switcher-data.hpp"
#include "utility.hpp" #include "utility.hpp"
namespace advss { namespace advss {
@@ -25,6 +24,32 @@ const static std::map<MacroConditionStream::Condition, std::string>
"AdvSceneSwitcher.condition.stream.state.keyFrameInterval"}, "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() int MacroConditionStream::GetKeyFrameInterval()
{ {
const auto configPath = GetPathInProfileDir("streamEncoder.json"); const auto configPath = GetPathInProfileDir("streamEncoder.json");
@@ -42,10 +67,8 @@ bool MacroConditionStream::CheckCondition()
{ {
bool match = false; bool match = false;
bool streamStarting = switcher->lastStreamStartingTime != bool streamStarting = streamStartTime != _lastStreamStartingTime;
_lastStreamStartingTime; bool streamStopping = streamStopTime != _lastStreamStoppingTime;
bool streamStopping = switcher->lastStreamStoppingTime !=
_lastStreamStoppingTime;
switch (_condition) { switch (_condition) {
case Condition::STOP: case Condition::STOP:
@@ -68,10 +91,10 @@ bool MacroConditionStream::CheckCondition()
} }
if (streamStarting) { if (streamStarting) {
_lastStreamStartingTime = switcher->lastStreamStartingTime; _lastStreamStartingTime = streamStartTime;
} }
if (streamStopping) { if (streamStopping) {
_lastStreamStoppingTime = switcher->lastStreamStoppingTime; _lastStreamStoppingTime = streamStopTime;
} }
return match; return match;
} }

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
#include "macro-condition-window.hpp" #include "macro-condition-window.hpp"
#include "switcher-data.hpp" #include "plugin-state-helpers.hpp"
#include "platform-funcs.hpp" #include "platform-funcs.hpp"
#include "utility.hpp" #include "utility.hpp"
@@ -43,7 +43,8 @@ static bool windowContainsText(const std::string &window,
bool MacroConditionWindow::WindowMatchesRequirements( bool MacroConditionWindow::WindowMatchesRequirements(
const std::string &window) const const std::string &window) const
{ {
const bool focusCheckOK = (!_focus || window == switcher->currentTitle); const bool focusCheckOK =
(!_focus || window == ForegroundWindowTitle());
if (!focusCheckOK) { if (!focusCheckOK) {
return false; return false;
} }
@@ -104,13 +105,13 @@ void MacroConditionWindow::SetVariableValueBasedOnMatch(
auto text = GetTextInWindow(matchWindow); auto text = GetTextInWindow(matchWindow);
SetVariableValue(text.value_or("")); SetVariableValue(text.value_or(""));
} else { } else {
SetVariableValue(switcher->currentTitle); SetVariableValue(ForegroundWindowTitle());
} }
} }
static bool foregroundWindowChanged() static bool foregroundWindowChanged()
{ {
return switcher->currentTitle != switcher->lastTitle; return ForegroundWindowTitle() != PreviousForegroundWindowTitle();
} }
bool MacroConditionWindow::CheckCondition() bool MacroConditionWindow::CheckCondition()
@@ -176,6 +177,7 @@ std::string MacroConditionWindow::GetShortDesc() const
void MacroConditionWindow::SetupTempVars() void MacroConditionWindow::SetupTempVars()
{ {
MacroCondition::SetupTempVars();
AddTempvar( AddTempvar(
"window", "window",
obs_module_text("AdvSceneSwitcher.tempVar.window.window"), obs_module_text("AdvSceneSwitcher.tempVar.window.window"),
@@ -431,7 +433,7 @@ void MacroConditionWindowEdit::WindowFocusChanged(int state)
void MacroConditionWindowEdit::UpdateFocusWindow() void MacroConditionWindowEdit::UpdateFocusWindow()
{ {
_focusWindow->setText(QString::fromStdString(switcher->currentTitle)); _focusWindow->setText(QString::fromStdString(ForegroundWindowTitle()));
} }
void MacroConditionWindowEdit::SetWidgetVisibility() 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 { namespace advss {
static MacroProperties macroProperties;
void MacroProperties::Save(obs_data_t *obj) const void MacroProperties::Save(obs_data_t *obj) const
{ {
auto data = obs_data_create(); auto data = obs_data_create();
@@ -325,4 +327,19 @@ bool MacroPropertiesDialog::AskForSettings(QWidget *parent,
return true; 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 } // namespace advss

View File

@@ -68,4 +68,8 @@ private:
int _conditionsFalseTextRow = -1; int _conditionsFalseTextRow = -1;
}; };
MacroProperties &GetGlobalMacroProperties();
void SaveGlobalMacroProperties(obs_data_t *obj);
void LoadGlobalMacroProperties(obs_data_t *obj);
} // namespace advss } // 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) 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) { for (int idx = 0; idx < _contentLayout->count(); ++idx) {
auto item = _contentLayout->itemAt(idx); auto item = _contentLayout->itemAt(idx);
if (!item) { if (!item) {
@@ -161,7 +181,7 @@ void MacroSegmentList::mousePressEvent(QMouseEvent *event)
#else #else
_dragPosition = GetDragIndex(event->globalPosition().toPoint()); _dragPosition = GetDragIndex(event->globalPosition().toPoint());
#endif #endif
emit SelectionChagned(_dragPosition); emit SelectionChanged(_dragPosition);
} else { } else {
_dragPosition = -1; _dragPosition = -1;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -202,6 +202,11 @@ private:
QAction *_dockAction = nullptr; 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 *GetMacroByName(const char *name);
Macro *GetMacroByQString(const QString &name); Macro *GetMacroByQString(const QString &name);
std::weak_ptr<Macro> GetWeakMacroByName(const char *name); std::weak_ptr<Macro> GetWeakMacroByName(const char *name);

View File

@@ -19,8 +19,7 @@ bool MacroConditionMidi::CheckCondition()
for (const auto &m : *messages) { for (const auto &m : *messages) {
if (m.Matches(_message)) { if (m.Matches(_message)) {
SetVariableValue(std::to_string(m.Note()) + " " + SetVariableValues(m);
std::to_string(m.Value()));
return true; return true;
} }
} }
@@ -49,6 +48,36 @@ std::string MacroConditionMidi::GetShortDesc() const
return _device.Name(); 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( MacroConditionMidiEdit::MacroConditionMidiEdit(
QWidget *parent, std::shared_ptr<MacroConditionMidi> entryData) QWidget *parent, std::shared_ptr<MacroConditionMidi> entryData)
: QWidget(parent), : QWidget(parent),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
#include "twitch-helpers.hpp" #include "twitch-helpers.hpp"
#include <log-helper.hpp> #include <log-helper.hpp>
#include <switcher-data.hpp> #include <plugin-state-helpers.hpp>
namespace advss { namespace advss {
@@ -58,7 +58,7 @@ EventSub::~EventSub()
static bool setupEventSubMessageClear() static bool setupEventSubMessageClear()
{ {
GetSwitcher()->AddIntervalResetStep(&EventSub::ClearAllEvents); AddIntervalResetStep(&EventSub::ClearAllEvents);
return true; return true;
} }
@@ -230,14 +230,16 @@ std::string EventSub::AddEventSubscribtion(std::shared_ptr<TwitchToken> token,
OBSDataAutoRelease postData = copyData(subscription.data); OBSDataAutoRelease postData = copyData(subscription.data);
setTransportData(postData.Get(), eventSub->_sessionID); setTransportData(postData.Get(), eventSub->_sessionID);
auto result = SendPostRequest(registerSubscriptionURL.data(), auto result = SendPostRequest(*token, registerSubscriptionURL.data(),
registerSubscriptionPath.data(), *token, registerSubscriptionPath.data(), {},
postData.Get()); postData.Get());
if (result.status != 202) { if (result.status != 202) {
vblog(LOG_INFO, "failed to register Twitch EventSub (%d)", vblog(LOG_INFO, "failed to register Twitch EventSub (%d)",
result.status); result.status);
return ""; return "";
} }
OBSDataArrayAutoRelease replyArray = OBSDataArrayAutoRelease replyArray =
obs_data_get_array(result.data, "data"); obs_data_get_array(result.data, "data");
OBSDataAutoRelease replyData = obs_data_array_item(replyArray, 0); 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 = { const static std::map<MacroActionTwitch::Action, std::string> actionTypes = {
{MacroActionTwitch::Action::TITLE, {MacroActionTwitch::Action::CHANNEL_INFO_TITLE_SET,
"AdvSceneSwitcher.action.twitch.type.title"}, "AdvSceneSwitcher.action.twitch.type.channel.info.title.set"},
{MacroActionTwitch::Action::CATEGORY, {MacroActionTwitch::Action::CHANNEL_INFO_CATEGORY_SET,
"AdvSceneSwitcher.action.twitch.type.category"}, "AdvSceneSwitcher.action.twitch.type.channel.info.category.set"},
{MacroActionTwitch::Action::MARKER, {MacroActionTwitch::Action::RAID_START,
"AdvSceneSwitcher.action.twitch.type.marker"}, "AdvSceneSwitcher.action.twitch.type.raid.start"},
{MacroActionTwitch::Action::CLIP, {MacroActionTwitch::Action::COMMERCIAL_START,
"AdvSceneSwitcher.action.twitch.type.clip"}, "AdvSceneSwitcher.action.twitch.type.commercial.start"},
{MacroActionTwitch::Action::COMMERCIAL, {MacroActionTwitch::Action::MARKER_CREATE,
"AdvSceneSwitcher.action.twitch.type.commercial"}, "AdvSceneSwitcher.action.twitch.type.marker.create"},
{MacroActionTwitch::Action::ANNOUNCEMENT, {MacroActionTwitch::Action::CLIP_CREATE,
"AdvSceneSwitcher.action.twitch.type.announcement"}, "AdvSceneSwitcher.action.twitch.type.clip.create"},
{MacroActionTwitch::Action::ENABLE_EMOTE_ONLY, {MacroActionTwitch::Action::CHAT_ANNOUNCEMENT_SEND,
"AdvSceneSwitcher.action.twitch.type.emoteOnlyEnable"}, "AdvSceneSwitcher.action.twitch.type.chat.announcement.send"},
{MacroActionTwitch::Action::DISABLE_EMOTE_ONLY, {MacroActionTwitch::Action::CHAT_EMOTE_ONLY_ENABLE,
"AdvSceneSwitcher.action.twitch.type.emoteOnlyDisable"}, "AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable"},
{MacroActionTwitch::Action::RAID, {MacroActionTwitch::Action::CHAT_EMOTE_ONLY_DISABLE,
"AdvSceneSwitcher.action.twitch.type.raid"}, "AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable"},
{MacroActionTwitch::Action::SEND_CHAT_MESSAGE, {MacroActionTwitch::Action::SEND_CHAT_MESSAGE,
"AdvSceneSwitcher.action.twitch.type.sendChatMessage"}, "AdvSceneSwitcher.action.twitch.type.sendChatMessage"},
}; };
@@ -64,6 +64,12 @@ const static std::map<MacroActionTwitch::AnnouncementColor, std::string>
{MacroActionTwitch::AnnouncementColor::PURPLE, "purple"}, {MacroActionTwitch::AnnouncementColor::PURPLE, "purple"},
}; };
const static std::map<MacroActionTwitch::RedemptionStatus, std::string>
redemptionStatusesTwitch = {
{MacroActionTwitch::RedemptionStatus::CANCELED, "CANCELED"},
{MacroActionTwitch::RedemptionStatus::FULFILLED, "FULFILLED"},
};
void MacroActionTwitch::SetStreamTitle( void MacroActionTwitch::SetStreamTitle(
const std::shared_ptr<TwitchToken> &token) const const std::shared_ptr<TwitchToken> &token) const
{ {
@@ -73,11 +79,10 @@ void MacroActionTwitch::SetStreamTitle(
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "title", _streamTitle.c_str()); obs_data_set_string(data, "title", _streamTitle.c_str());
auto result = SendPatchRequest( auto result = SendPatchRequest(*token, "https://api.twitch.tv",
"https://api.twitch.tv", "/helix/channels",
std::string("/helix/channels?broadcaster_id=") + {{"broadcaster_id", token->GetUserID()}},
token->GetUserID(), data.Get());
*token, data.Get());
if (result.status != 204) { if (result.status != 204) {
blog(LOG_INFO, "Failed to set stream title! (%d)", blog(LOG_INFO, "Failed to set stream title! (%d)",
@@ -95,11 +100,11 @@ void MacroActionTwitch::SetStreamCategory(
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "game_id", obs_data_set_string(data, "game_id",
std::to_string(_category.id).c_str()); std::to_string(_category.id).c_str());
auto result = SendPatchRequest( auto result = SendPatchRequest(*token, "https://api.twitch.tv",
"https://api.twitch.tv", "/helix/channels",
std::string("/helix/channels?broadcaster_id=") + {{"broadcaster_id", token->GetUserID()}},
token->GetUserID(), data.Get());
*token, data.Get());
if (result.status != 204) { if (result.status != 204) {
blog(LOG_INFO, "Failed to set stream category! (%d)", blog(LOG_INFO, "Failed to set stream category! (%d)",
result.status); result.status);
@@ -117,9 +122,8 @@ void MacroActionTwitch::CreateStreamMarker(
_markerDescription.c_str()); _markerDescription.c_str());
} }
auto result = SendPostRequest("https://api.twitch.tv", auto result = SendPostRequest(*token, "https://api.twitch.tv",
"/helix/streams/markers", *token, "/helix/streams/markers", {}, data.Get());
data.Get());
if (result.status != 200) { if (result.status != 200) {
blog(LOG_INFO, "Failed to create marker! (%d)", result.status); blog(LOG_INFO, "Failed to create marker! (%d)", result.status);
@@ -129,14 +133,12 @@ void MacroActionTwitch::CreateStreamMarker(
void MacroActionTwitch::CreateStreamClip( void MacroActionTwitch::CreateStreamClip(
const std::shared_ptr<TwitchToken> &token) const const std::shared_ptr<TwitchToken> &token) const
{ {
OBSDataAutoRelease data = obs_data_create();
auto hasDelay = _clipHasDelay ? "true" : "false"; auto hasDelay = _clipHasDelay ? "true" : "false";
auto result = SendPostRequest( auto result = SendPostRequest(*token, "https://api.twitch.tv",
"https://api.twitch.tv", "/helix/clips",
"/helix/clips?broadcaster_id=" + token->GetUserID() + {{"broadcaster_id", token->GetUserID()},
"&has_delay=" + hasDelay, {"has_delay", hasDelay}});
*token, data.Get());
if (result.status != 202) { if (result.status != 202) {
blog(LOG_INFO, "Failed to create clip! (%d)", result.status); blog(LOG_INFO, "Failed to create clip! (%d)", result.status);
@@ -149,9 +151,10 @@ void MacroActionTwitch::StartCommercial(
OBSDataAutoRelease data = obs_data_create(); OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "broadcaster_id", token->GetUserID().c_str()); obs_data_set_string(data, "broadcaster_id", token->GetUserID().c_str());
obs_data_set_int(data, "length", _duration.Seconds()); obs_data_set_int(data, "length", _duration.Seconds());
auto result = SendPostRequest("https://api.twitch.tv", auto result = SendPostRequest(*token, "https://api.twitch.tv",
"/helix/channels/commercial", *token, "/helix/channels/commercial", {},
data.Get()); data.Get());
if (result.status != 200) { if (result.status != 200) {
OBSDataArrayAutoRelease replyArray = OBSDataArrayAutoRelease replyArray =
obs_data_get_array(result.data, "data"); obs_data_get_array(result.data, "data");
@@ -178,11 +181,10 @@ void MacroActionTwitch::SendChatAnnouncement(
announcementColorsTwitch.at(_announcementColor).c_str()); announcementColorsTwitch.at(_announcementColor).c_str());
auto userId = token->GetUserID(); auto userId = token->GetUserID();
auto result = auto result = SendPostRequest(
SendPostRequest("https://api.twitch.tv", *token, "https://api.twitch.tv", "/helix/chat/announcements",
"/helix/chat/announcements?broadcaster_id=" + {{"broadcaster_id", userId}, {"moderator_id", userId}},
userId + "&moderator_id=" + userId, data.Get());
*token, data.Get());
if (result.status != 204) { if (result.status != 204) {
blog(LOG_INFO, "Failed to send chat announcement! (%d)", blog(LOG_INFO, "Failed to send chat announcement! (%d)",
@@ -197,11 +199,10 @@ void MacroActionTwitch::SetChatEmoteOnlyMode(
obs_data_set_bool(data, "emote_mode", enable); obs_data_set_bool(data, "emote_mode", enable);
auto userId = token->GetUserID(); auto userId = token->GetUserID();
auto result = auto result = SendPatchRequest(
SendPatchRequest("https://api.twitch.tv", *token, "https://api.twitch.tv", "/helix/chat/settings",
"/helix/chat/settings?broadcaster_id=" + {{"broadcaster_id", userId}, {"moderator_id", userId}},
userId + "&moderator_id=" + userId, data.Get());
*token, data.Get());
if (result.status != 200) { if (result.status != 200) {
blog(LOG_INFO, "Failed to %s chat's emote-only mode! (%d)", 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()); token->GetUserID().c_str());
obs_data_set_string(data, "to_broadcaster_id", obs_data_set_string(data, "to_broadcaster_id",
_channel.GetUserID(*token).c_str()); _channel.GetUserID(*token).c_str());
auto result = SendPostRequest("https://api.twitch.tv", "/helix/raids", auto result = SendPostRequest(*token, "https://api.twitch.tv",
*token, data.Get()); "/helix/raids", {}, data.Get());
if (result.status != 200) { if (result.status != 200) {
blog(LOG_INFO, "Failed to start raid! (%d)\n", result.status); blog(LOG_INFO, "Failed to start raid! (%d)\n", result.status);
} }
@@ -243,33 +245,32 @@ bool MacroActionTwitch::PerformAction()
} }
switch (_action) { switch (_action) {
case MacroActionTwitch::Action::TITLE: case Action::CHANNEL_INFO_TITLE_SET:
SetStreamTitle(token); SetStreamTitle(token);
break; break;
case MacroActionTwitch::Action::CATEGORY: case Action::CHANNEL_INFO_CATEGORY_SET:
SetStreamCategory(token); SetStreamCategory(token);
break; break;
case MacroActionTwitch::Action::MARKER: case Action::RAID_START:
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:
StartRaid(token); StartRaid(token);
break; 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: case MacroActionTwitch::Action::SEND_CHAT_MESSAGE:
SendChatMessage(token); SendChatMessage(token);
break; break;
@@ -338,27 +339,111 @@ bool MacroActionTwitch::Load(obs_data_t *obj)
bool MacroActionTwitch::ActionIsSupportedByToken() bool MacroActionTwitch::ActionIsSupportedByToken()
{ {
static const std::unordered_map<Action, TokenOption> requiredOption = { static const std::unordered_map<Action, TokenOption> requiredOption = {
{Action::TITLE, {"channel:manage:broadcast"}}, {Action::CHANNEL_INFO_TITLE_SET, {"channel:manage:broadcast"}},
{Action::CATEGORY, {"channel:manage:broadcast"}}, {Action::CHANNEL_INFO_CATEGORY_SET,
{Action::MARKER, {"channel:manage:broadcast"}}, {"channel:manage:broadcast"}},
{Action::CLIP, {"clips:edit"}}, {Action::CHANNEL_INFO_LANGUAGE_SET,
{Action::COMMERCIAL, {"channel:edit:commercial"}}, {"channel:manage:broadcast"}},
{Action::ANNOUNCEMENT, {"moderator:manage:announcements"}}, {Action::CHANNEL_INFO_DELAY_SET, {"channel:manage:broadcast"}},
{Action::ENABLE_EMOTE_ONLY, {"moderator:manage:chat_settings"}}, {Action::CHANNEL_INFO_BRANDED_CONTENT_ENABLE,
{Action::DISABLE_EMOTE_ONLY, {"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"}}, {"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"}}, {Action::SEND_CHAT_MESSAGE, {"chat:edit"}},
}; };
auto token = _token.lock(); auto token = _token.lock();
if (!token) { if (!token) {
return false; return false;
} }
auto option = requiredOption.find(_action); auto option = requiredOption.find(_action);
assert(option != requiredOption.end()); assert(option != requiredOption.end());
if (option == requiredOption.end()) { if (option == requiredOption.end()) {
return false; return false;
} }
return token->OptionIsEnabled(option->second); return token->OptionIsEnabled(option->second);
} }
@@ -401,49 +486,8 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
_channel(new TwitchChannelSelection(this)), _channel(new TwitchChannelSelection(this)),
_chatMessage(new VariableTextEdit(this)) _chatMessage(new VariableTextEdit(this))
{ {
_streamTitle->setSizePolicy(QSizePolicy::MinimumExpanding, SetWidgetProperties();
QSizePolicy::Preferred); SetWidgetSignalConnections();
_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()));
_entryData = entryData; _entryData = entryData;
SetWidgetLayout(); SetWidgetLayout();
@@ -477,8 +521,8 @@ void MacroActionTwitchEdit::ActionChanged(int idx)
auto lock = LockContext(); auto lock = LockContext();
_entryData->_action = static_cast<MacroActionTwitch::Action>( _entryData->_action = static_cast<MacroActionTwitch::Action>(
_actions->itemData(idx).toInt()); _actions->itemData(idx).toInt());
SetupWidgetVisibility();
SetWidgetLayout(); SetWidgetLayout();
SetWidgetVisibility();
} }
void MacroActionTwitchEdit::TwitchTokenChanged(const QString &token) void MacroActionTwitchEdit::TwitchTokenChanged(const QString &token)
@@ -492,7 +536,7 @@ void MacroActionTwitchEdit::TwitchTokenChanged(const QString &token)
_category->SetToken(_entryData->_token); _category->SetToken(_entryData->_token);
_channel->SetToken(_entryData->_token); _channel->SetToken(_entryData->_token);
SetupWidgetVisibility(); SetWidgetVisibility();
emit(HeaderInfoChanged(token)); emit(HeaderInfoChanged(token));
} }
@@ -580,30 +624,86 @@ void MacroActionTwitchEdit::AnnouncementColorChanged(int index)
static_cast<MacroActionTwitch::AnnouncementColor>(index); static_cast<MacroActionTwitch::AnnouncementColor>(index);
} }
void MacroActionTwitchEdit::SetupWidgetVisibility() void MacroActionTwitchEdit::SetWidgetProperties()
{ {
_streamTitle->setVisible(_entryData->_action == _streamTitle->setSizePolicy(QSizePolicy::MinimumExpanding,
MacroActionTwitch::Action::TITLE); QSizePolicy::Preferred);
_category->setVisible(_entryData->_action == _streamTitle->setMaxLength(140);
MacroActionTwitch::Action::CATEGORY); _markerDescription->setSizePolicy(QSizePolicy::MinimumExpanding,
_markerDescription->setVisible(_entryData->_action == QSizePolicy::Preferred);
MacroActionTwitch::Action::MARKER); _markerDescription->setMaxLength(140);
_clipHasDelay->setVisible(_entryData->_action == _announcementMessage->setMaxLength(500);
MacroActionTwitch::Action::CLIP);
_duration->setVisible(_entryData->_action == auto durationSpinBox = _duration->SpinBox();
MacroActionTwitch::Action::COMMERCIAL); durationSpinBox->setMaximum(180);
_announcementMessage->setVisible( durationSpinBox->setSuffix("s");
_entryData->_action == MacroActionTwitch::Action::ANNOUNCEMENT);
_announcementColor->setVisible(_entryData->_action == populateActionSelection(_actions);
MacroActionTwitch::Action::ANNOUNCEMENT); 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( _channel->setVisible(
_entryData->_action == MacroActionTwitch::Action::RAID || _entryData->_action == MacroActionTwitch::Action::RAID_START ||
_entryData->_action == MacroActionTwitch::Action::RAID_END ||
_entryData->_action == _entryData->_action ==
MacroActionTwitch::Action::SEND_CHAT_MESSAGE); 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 == _chatMessage->setVisible(_entryData->_action ==
MacroActionTwitch::Action::SEND_CHAT_MESSAGE); 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); RemoveStretchIfPresent(_layout);
} else { } else {
AddStretchIfNecessary(_layout); AddStretchIfNecessary(_layout);
@@ -641,7 +741,7 @@ void MacroActionTwitchEdit::ChatMessageChanged()
void MacroActionTwitchEdit::SetWidgetLayout() void MacroActionTwitchEdit::SetWidgetLayout()
{ {
static const std::vector<QWidget *> widgets{ const std::vector<QWidget *> widgets{
_tokens, _actions, _streamTitle, _tokens, _actions, _streamTitle,
_category, _markerDescription, _clipHasDelay, _category, _markerDescription, _clipHasDelay,
_duration, _announcementColor, _channel}; _duration, _announcementColor, _channel};
@@ -692,7 +792,7 @@ void MacroActionTwitchEdit::UpdateEntryData()
_channel->SetChannel(_entryData->_channel); _channel->SetChannel(_entryData->_channel);
_chatMessage->setPlainText(_entryData->_chatMessage); _chatMessage->setPlainText(_entryData->_chatMessage);
SetupWidgetVisibility(); SetWidgetVisibility();
} }
} // namespace advss } // namespace advss

View File

@@ -4,6 +4,7 @@
#include "category-selection.hpp" #include "category-selection.hpp"
#include "channel-selection.hpp" #include "channel-selection.hpp"
#include "chat-connection.hpp" #include "chat-connection.hpp"
#include "points-reward-selection.hpp"
#include <variable-line-edit.hpp> #include <variable-line-edit.hpp>
#include <variable-text-edit.hpp> #include <variable-text-edit.hpp>
@@ -22,16 +23,118 @@ public:
std::string GetShortDesc() const; std::string GetShortDesc() const;
enum class Action { enum class Action {
TITLE, // Channel info update
CATEGORY = 10, CHANNEL_INFO_TITLE_SET = 10,
MARKER = 20, CHANNEL_INFO_CATEGORY_SET = 20,
CLIP = 30, CHANNEL_INFO_LANGUAGE_SET = 30,
COMMERCIAL = 40, CHANNEL_INFO_DELAY_SET = 40,
ANNOUNCEMENT = 50, CHANNEL_INFO_TAGS_SET = 50, // TODO
ENABLE_EMOTE_ONLY = 60, CHANNEL_INFO_CONTENT_LABELS_SET = 60, // TODO
DISABLE_EMOTE_ONLY = 70, CHANNEL_INFO_BRANDED_CONTENT_ENABLE = 70,
RAID = 80, CHANNEL_INFO_BRANDED_CONTENT_DISABLE = 71,
SEND_CHAT_MESSAGE = 90,
// 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 { enum class AnnouncementColor {
@@ -42,6 +145,8 @@ public:
PURPLE, PURPLE,
}; };
enum class RedemptionStatus { CANCELED, FULFILLED };
bool PerformAction(); bool PerformAction();
void LogAction() const; void LogAction() const;
bool Save(obs_data_t *obj) const; bool Save(obs_data_t *obj) const;
@@ -49,7 +154,7 @@ public:
bool ActionIsSupportedByToken(); bool ActionIsSupportedByToken();
void ResetChatConnection(); void ResetChatConnection();
Action _action = Action::TITLE; Action _action = Action::CHANNEL_INFO_TITLE_SET;
std::weak_ptr<TwitchToken> _token; std::weak_ptr<TwitchToken> _token;
StringVariable _streamTitle = StringVariable _streamTitle =
obs_module_text("AdvSceneSwitcher.action.twitch.title.title"); obs_module_text("AdvSceneSwitcher.action.twitch.title.title");
@@ -119,8 +224,10 @@ protected:
std::shared_ptr<MacroActionTwitch> _entryData; std::shared_ptr<MacroActionTwitch> _entryData;
private: private:
void SetupWidgetVisibility(); void SetWidgetProperties();
void SetWidgetSignalConnections();
void SetWidgetLayout(); void SetWidgetLayout();
void SetWidgetVisibility();
QHBoxLayout *_layout; QHBoxLayout *_layout;
FilterComboBox *_actions; FilterComboBox *_actions;

View File

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

View File

@@ -23,8 +23,13 @@ public:
std::string GetShortDesc() const; std::string GetShortDesc() const;
enum class Condition { enum class Condition {
// Generic event based // Event based
STREAM_ONLINE_EVENT = 0, 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, STREAM_OFFLINE_EVENT = 100,
CHANNEL_INFO_UPDATE_EVENT = 200, CHANNEL_INFO_UPDATE_EVENT = 200,
FOLLOW_EVENT = 300, FOLLOW_EVENT = 300,
@@ -66,13 +71,6 @@ public:
USER_MODERATOR_ADDITION_EVENT = 3900, USER_MODERATOR_ADDITION_EVENT = 3900,
USER_MODERATOR_DELETION_EVENT = 4000, 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
CHAT_MESSAGE_RECEIVED = 500000, CHAT_MESSAGE_RECEIVED = 500000,
@@ -170,7 +168,7 @@ protected:
std::shared_ptr<MacroConditionTwitch> _entryData; std::shared_ptr<MacroConditionTwitch> _entryData;
private: private:
void SetupWidgetVisibility(); void SetWidgetVisibility();
QHBoxLayout *_layout; QHBoxLayout *_layout;
FilterComboBox *_conditions; FilterComboBox *_conditions;

View File

@@ -134,9 +134,9 @@ TwitchPointsRewardSelection::GetPointsRewards(
httplib::Params params = { httplib::Params params = {
{"broadcaster_id", channel.GetUserID(*token)}}; {"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", "/helix/channel_points/custom_rewards",
*token, params); params);
if (response.status != 200) { if (response.status != 200) {
blog(LOG_WARNING, blog(LOG_WARNING,

View File

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

View File

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

View File

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

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