mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-30 05:06:14 -05:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ada858046 | ||
|
|
51d121bda2 | ||
|
|
a7cf5769dd | ||
|
|
63ee36ddfe | ||
|
|
43967f5ded | ||
|
|
c043053b2b | ||
|
|
220f27bd0a | ||
|
|
83914d5378 | ||
|
|
57e4b1a3f1 | ||
|
|
c85e556924 | ||
|
|
8c0be8011d | ||
|
|
7914f75876 | ||
|
|
7d1253e14e | ||
|
|
d7c58b3eb3 | ||
|
|
fb664b6f08 | ||
|
|
258a6d076e | ||
|
|
32ab060153 | ||
|
|
91d3784f9b | ||
|
|
8a5aac3d31 | ||
|
|
06c6effb7d | ||
|
|
f3789c05bd | ||
|
|
c64e1f5e51 | ||
|
|
5d8e24c19a | ||
|
|
f756de05c3 | ||
|
|
574fdb496f | ||
|
|
5580da69eb | ||
|
|
35274863c9 | ||
|
|
1f31b81f0d | ||
|
|
cde111ddeb | ||
|
|
845c12c01c | ||
|
|
99a0118d31 | ||
|
|
bb7ee17362 | ||
|
|
70854ef012 | ||
|
|
053c14056d | ||
|
|
9397d50764 | ||
|
|
473e24cc42 | ||
|
|
d9394605f4 | ||
|
|
d2b9074852 | ||
|
|
b525eb7a0e |
5
.github/scripts/.Aptfile
vendored
5
.github/scripts/.Aptfile
vendored
@@ -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'
|
||||||
|
|||||||
7
.github/scripts/utils.zsh/setup_linux
vendored
7
.github/scripts/utils.zsh/setup_linux
vendored
@@ -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 )) {
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ 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-file.cpp
|
src/macro-core/macro-action-file.cpp
|
||||||
@@ -254,6 +256,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 +291,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-helper.cpp
|
||||||
|
src/utils/plugin-state-helper.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
|
||||||
@@ -458,27 +464,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)
|
||||||
|
|||||||
@@ -152,9 +152,6 @@ AdvSceneSwitcher.condition.file.type.contentChange="Inhalt geändert"
|
|||||||
AdvSceneSwitcher.condition.file.type.dateChange="Änderungsdatum geändert"
|
AdvSceneSwitcher.condition.file.type.dateChange="Änderungsdatum geändert"
|
||||||
AdvSceneSwitcher.condition.file.remote="Entfernte Datei"
|
AdvSceneSwitcher.condition.file.remote="Entfernte Datei"
|
||||||
AdvSceneSwitcher.condition.file.local="Lokale Datei"
|
AdvSceneSwitcher.condition.file.local="Lokale Datei"
|
||||||
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
|
||||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
|
||||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
|
||||||
AdvSceneSwitcher.condition.media="Medien"
|
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"
|
||||||
@@ -221,9 +218,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}}"
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -192,14 +195,17 @@ AdvSceneSwitcher.condition.window.entry.text="{{checkText}}Window contains text{
|
|||||||
AdvSceneSwitcher.condition.window.entry.text.note="This option might not work on every text being displayed in a window.\nIf that is the case, you can consider using the Video conditions OCR check instead."
|
AdvSceneSwitcher.condition.window.entry.text.note="This option might not work on every text being displayed in a window.\nIf that is the case, you can consider using the Video conditions OCR check instead."
|
||||||
AdvSceneSwitcher.condition.window.entry.currentFocus="Current focus window:{{focusWindow}}"
|
AdvSceneSwitcher.condition.window.entry.currentFocus="Current focus window:{{focusWindow}}"
|
||||||
AdvSceneSwitcher.condition.file="File"
|
AdvSceneSwitcher.condition.file="File"
|
||||||
AdvSceneSwitcher.condition.file.type.match="matches"
|
AdvSceneSwitcher.condition.file.type.match="Matches"
|
||||||
AdvSceneSwitcher.condition.file.type.contentChange="content changed"
|
AdvSceneSwitcher.condition.file.type.contentChange="Content changed"
|
||||||
AdvSceneSwitcher.condition.file.type.dateChange="modification date changed"
|
AdvSceneSwitcher.condition.file.type.dateChange="Modification date changed"
|
||||||
|
AdvSceneSwitcher.condition.file.type.changesMatch="Changed lines match"
|
||||||
AdvSceneSwitcher.condition.file.remote="Remote file"
|
AdvSceneSwitcher.condition.file.remote="Remote file"
|
||||||
AdvSceneSwitcher.condition.file.local="Local file"
|
AdvSceneSwitcher.condition.file.local="Local file"
|
||||||
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
AdvSceneSwitcher.condition.file.changeMatchType.any="Any lines"
|
||||||
|
AdvSceneSwitcher.condition.file.changeMatchType.added="Added lines"
|
||||||
|
AdvSceneSwitcher.condition.file.changeMatchType.removed="Removed lines"
|
||||||
|
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{changeMatchType}}{{useRegex}}"
|
||||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
|
||||||
AdvSceneSwitcher.condition.media="Media"
|
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"
|
||||||
@@ -295,9 +301,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 +333,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 +349,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"
|
||||||
@@ -852,15 +861,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 +883,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 +1042,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 channel’s broadcast configuration, including updating channel configuration and managing stream markers and stream tags."
|
AdvSceneSwitcher.twitchToken.channel.broadcast.manage="Manage a channel’s 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 +1060,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."
|
||||||
|
|
||||||
@@ -1412,6 +1433,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 +1457,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 +1482,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"
|
||||||
|
|||||||
@@ -123,9 +123,6 @@ AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}
|
|||||||
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
||||||
AdvSceneSwitcher.condition.window="Ventana"
|
AdvSceneSwitcher.condition.window="Ventana"
|
||||||
AdvSceneSwitcher.condition.file="Archivo"
|
AdvSceneSwitcher.condition.file="Archivo"
|
||||||
AdvSceneSwitcher.condition.file.entry.line1="Contenido de{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
|
||||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
|
||||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
|
||||||
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"
|
||||||
@@ -182,9 +179,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"
|
||||||
|
|||||||
@@ -286,7 +286,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,9 +736,9 @@ 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}}"
|
AdvSceneSwitcher.action.twitch.entry.line1="Sur{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}"
|
||||||
|
|
||||||
|
|||||||
@@ -87,8 +87,6 @@ AdvSceneSwitcher.condition.scene.type.current="Текущий"
|
|||||||
AdvSceneSwitcher.condition.scene.type.previous="Предыдущий"
|
AdvSceneSwitcher.condition.scene.type.previous="Предыдущий"
|
||||||
AdvSceneSwitcher.condition.window="Окно"
|
AdvSceneSwitcher.condition.window="Окно"
|
||||||
AdvSceneSwitcher.condition.file="Файл"
|
AdvSceneSwitcher.condition.file="Файл"
|
||||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
|
||||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
|
||||||
AdvSceneSwitcher.condition.media="Медиа"
|
AdvSceneSwitcher.condition.media="Медиа"
|
||||||
AdvSceneSwitcher.condition.video="Видео"
|
AdvSceneSwitcher.condition.video="Видео"
|
||||||
AdvSceneSwitcher.condition.video.condition.match="точно соответствует"
|
AdvSceneSwitcher.condition.video.condition.match="точно соответствует"
|
||||||
@@ -107,7 +105,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="Состояние плагина"
|
||||||
|
|||||||
@@ -113,9 +113,6 @@ AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}
|
|||||||
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
||||||
AdvSceneSwitcher.condition.window="Pencere"
|
AdvSceneSwitcher.condition.window="Pencere"
|
||||||
AdvSceneSwitcher.condition.file="Dosya"
|
AdvSceneSwitcher.condition.file="Dosya"
|
||||||
AdvSceneSwitcher.condition.file.entry.line1="İçerik{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
|
||||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
|
||||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
|
||||||
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ı "
|
||||||
@@ -159,9 +156,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"
|
||||||
|
|||||||
@@ -178,9 +178,6 @@ AdvSceneSwitcher.condition.file.type.contentChange="内容已更改"
|
|||||||
AdvSceneSwitcher.condition.file.type.dateChange="修改日期已更改"
|
AdvSceneSwitcher.condition.file.type.dateChange="修改日期已更改"
|
||||||
AdvSceneSwitcher.condition.file.remote="远程文件"
|
AdvSceneSwitcher.condition.file.remote="远程文件"
|
||||||
AdvSceneSwitcher.condition.file.local="本地文件"
|
AdvSceneSwitcher.condition.file.local="本地文件"
|
||||||
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
|
|
||||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
|
||||||
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
|
|
||||||
AdvSceneSwitcher.condition.media="媒体"
|
AdvSceneSwitcher.condition.media="媒体"
|
||||||
AdvSceneSwitcher.condition.media.source="源"
|
AdvSceneSwitcher.condition.media.source="源"
|
||||||
AdvSceneSwitcher.condition.media.anyOnScene="任何媒体源"
|
AdvSceneSwitcher.condition.media.anyOnScene="任何媒体源"
|
||||||
@@ -267,9 +264,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}}"
|
||||||
|
|||||||
11
data/res/images/DarkNotEqual.svg
Normal file
11
data/res/images/DarkNotEqual.svg
Normal 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 |
11
data/res/images/LightNotEqual.svg
Normal file
11
data/res/images/LightNotEqual.svg
Normal 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 |
@@ -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>
|
||||||
|
|||||||
@@ -115,6 +115,23 @@ 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
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
@@ -330,12 +347,8 @@ void SwitcherData::SetPreconditions()
|
|||||||
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();
|
||||||
@@ -759,6 +772,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 +785,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);
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -33,7 +33,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 +45,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 +58,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,7 +506,6 @@ 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);
|
||||||
@@ -549,7 +548,6 @@ void SwitcherData::SaveSettings(obs_data_t *obj)
|
|||||||
|
|
||||||
saveSceneGroups(obj);
|
saveSceneGroups(obj);
|
||||||
SaveMacros(obj);
|
SaveMacros(obj);
|
||||||
SaveConnections(obj);
|
|
||||||
SaveVariables(obj);
|
SaveVariables(obj);
|
||||||
saveWindowTitleSwitches(obj);
|
saveWindowTitleSwitches(obj);
|
||||||
saveScreenRegionSwitches(obj);
|
saveScreenRegionSwitches(obj);
|
||||||
@@ -624,9 +622,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 +798,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 +955,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 {
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
@@ -633,6 +671,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 +730,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
257
src/macro-core/macro-action-clipboard.cpp
Normal file
257
src/macro-core/macro-action-clipboard.cpp
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
#include "macro-action-clipboard.hpp"
|
||||||
|
#include "curl-helper.hpp"
|
||||||
|
#include "switcher-data.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;
|
||||||
|
|
||||||
|
switcher->curl.SetOpt(CURLOPT_URL, url);
|
||||||
|
switcher->curl.SetOpt(CURLOPT_HTTPGET, 1L);
|
||||||
|
switcher->curl.SetOpt(CURLOPT_TIMEOUT_MS, 30000);
|
||||||
|
switcher->curl.SetOpt(CURLOPT_WRITEFUNCTION, writeCallback);
|
||||||
|
switcher->curl.SetOpt(CURLOPT_WRITEDATA, &response);
|
||||||
|
auto code = switcher->curl.Perform();
|
||||||
|
|
||||||
|
if (code != CURLE_OK) {
|
||||||
|
blog(LOG_WARNING,
|
||||||
|
"Retrieving image failed in %s with error: %s", __func__,
|
||||||
|
switcher->curl.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, ¶ms, true);
|
||||||
|
SetTempVarValues(params);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Action::COPY_IMAGE: {
|
||||||
|
ClipboardImageQueueParams params{"", "", _url};
|
||||||
|
obs_queue_task(OBS_TASK_UI, copyImageFromUrl, ¶ms, 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 ¶ms)
|
||||||
|
{
|
||||||
|
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
|
||||||
89
src/macro-core/macro-action-clipboard.hpp
Normal file
89
src/macro-core/macro-action-clipboard.hpp
Normal 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 ¶ms);
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#include "macro-action-plugin-state.hpp"
|
#include "macro-action-plugin-state.hpp"
|
||||||
#include "switcher-data.hpp"
|
#include "plugin-state-helper.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;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "macro-action-scene-collection.hpp"
|
#include "macro-action-scene-collection.hpp"
|
||||||
#include "switcher-data.hpp"
|
#include "plugin-state-helper.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());
|
||||||
|
|||||||
@@ -145,7 +145,8 @@ 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 != switcher->currentScene) {
|
||||||
return WaitForTransition(scene, transition);
|
return WaitForTransition(scene, transition);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
|
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <regex>
|
#include <array>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
@@ -41,86 +42,63 @@ static std::string getRemoteData(std::string &url)
|
|||||||
return readBuffer;
|
return readBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroConditionFile::MatchFileContent(QString &filedata)
|
static bool stringsMatch(const QString &text, const std::string &pattern,
|
||||||
|
const RegexConfig ®ex)
|
||||||
{
|
{
|
||||||
if (_onlyMatchIfChanged) {
|
auto qpattern = QString::fromStdString(pattern);
|
||||||
size_t newHash = strHash(filedata.toUtf8().constData());
|
if (regex.Enabled()) {
|
||||||
if (newHash == _lastHash) {
|
return regex.Matches(text, qpattern);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_lastHash = newHash;
|
|
||||||
}
|
}
|
||||||
|
auto textCopy = text;
|
||||||
if (_regex.Enabled()) {
|
return CompareIgnoringLineEnding(qpattern, textCopy);
|
||||||
auto expr = _regex.GetRegularExpression(_text);
|
|
||||||
if (!expr.isValid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
auto match = expr.match(filedata);
|
|
||||||
return match.hasMatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString text = QString::fromStdString(_text);
|
|
||||||
return CompareIgnoringLineEnding(text, filedata);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroConditionFile::CheckRemoteFileContent()
|
static std::optional<QString> getFileContent(MacroConditionFile::FileType type,
|
||||||
|
const std::string &filePath)
|
||||||
{
|
{
|
||||||
std::string path = _file;
|
QString filedata;
|
||||||
std::string data = getRemoteData(path);
|
switch (type) {
|
||||||
SetVariableValue(data);
|
case MacroConditionFile::FileType::LOCAL: {
|
||||||
QString qdata = QString::fromStdString(data);
|
std::string path = filePath;
|
||||||
return MatchFileContent(qdata);
|
QFile file(QString::fromStdString(path));
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
filedata = QTextStream(&file).readAll();
|
||||||
|
file.close();
|
||||||
|
} break;
|
||||||
|
case MacroConditionFile::FileType::REMOTE: {
|
||||||
|
std::string path = filePath;
|
||||||
|
std::string data = getRemoteData(path);
|
||||||
|
filedata = QString::fromStdString(data);
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return filedata;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroConditionFile::CheckLocalFileContent()
|
bool MacroConditionFile::CheckFileContentMatch()
|
||||||
{
|
{
|
||||||
QFile file(QString::fromStdString(_file));
|
auto fileData = getFileContent(_fileType, _file);
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (!fileData) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const bool match = stringsMatch(*fileData, _text, _regex);
|
||||||
if (_useTime) {
|
SetVariableValue(fileData->toStdString());
|
||||||
QDateTime newLastMod = QFileInfo(file).lastModified();
|
|
||||||
if (_lastMod == newLastMod) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_lastMod = newLastMod;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString filedata = QTextStream(&file).readAll();
|
|
||||||
SetVariableValue(filedata.toStdString());
|
|
||||||
bool match = MatchFileContent(filedata);
|
|
||||||
|
|
||||||
file.close();
|
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroConditionFile::CheckChangeContent()
|
bool MacroConditionFile::CheckChangeContent()
|
||||||
{
|
{
|
||||||
QString filedata;
|
auto filedata = getFileContent(_fileType, _file);
|
||||||
switch (_fileType) {
|
if (!filedata) {
|
||||||
case FileType::LOCAL: {
|
return false;
|
||||||
std::string path = _file;
|
|
||||||
QFile file(QString::fromStdString(path));
|
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
filedata = QTextStream(&file).readAll();
|
|
||||||
file.close();
|
|
||||||
} break;
|
|
||||||
case FileType::REMOTE: {
|
|
||||||
std::string path = _file;
|
|
||||||
std::string data = getRemoteData(path);
|
|
||||||
QString filedata = QString::fromStdString(data);
|
|
||||||
} break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
size_t newHash = strHash(filedata->toUtf8().constData());
|
||||||
size_t newHash = strHash(filedata.toUtf8().constData());
|
|
||||||
const bool contentChanged = newHash != _lastHash;
|
const bool contentChanged = newHash != _lastHash;
|
||||||
_lastHash = newHash;
|
_lastHash = newHash;
|
||||||
|
SetVariableValue(contentChanged ? "true" : "false");
|
||||||
return contentChanged;
|
return contentChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,35 +113,94 @@ bool MacroConditionFile::CheckChangeDate()
|
|||||||
SetVariableValue(newLastMod.toString().toStdString());
|
SetVariableValue(newLastMod.toString().toStdString());
|
||||||
const bool dateChanged = _lastMod != newLastMod;
|
const bool dateChanged = _lastMod != newLastMod;
|
||||||
_lastMod = newLastMod;
|
_lastMod = newLastMod;
|
||||||
|
SetVariableValue(dateChanged ? "true" : "false");
|
||||||
return dateChanged;
|
return dateChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static QStringList splitLines(const QString &string)
|
||||||
|
{
|
||||||
|
static auto regex = QRegularExpression("\n|\r\n|\r");
|
||||||
|
return string.split(regex);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MacroConditionFile::CheckChangedMatch()
|
||||||
|
{
|
||||||
|
static bool setupDone = false;
|
||||||
|
if (!setupDone) {
|
||||||
|
auto content = getFileContent(_fileType, _file);
|
||||||
|
if (!content) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_previousContent = splitLines(*content);
|
||||||
|
setupDone = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto currentFileContent = getFileContent(_fileType, _file);
|
||||||
|
if (!currentFileContent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto currentContent = splitLines(*currentFileContent);
|
||||||
|
auto newContent = currentContent;
|
||||||
|
|
||||||
|
QStringList addedLines;
|
||||||
|
QStringList removedLines;
|
||||||
|
|
||||||
|
for (const auto &line : newContent) {
|
||||||
|
if (auto it = std::find(_previousContent.begin(),
|
||||||
|
_previousContent.end(),
|
||||||
|
line) == _previousContent.end()) {
|
||||||
|
addedLines.push_back(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &line : _previousContent) {
|
||||||
|
if (std::find(newContent.begin(), newContent.end(), line) ==
|
||||||
|
newContent.end()) {
|
||||||
|
removedLines.push_back(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_previousContent = currentContent;
|
||||||
|
|
||||||
|
if (_changeMatchType == ChangeMatchType::ADDED ||
|
||||||
|
_changeMatchType == ChangeMatchType::ANY) {
|
||||||
|
for (const auto &line : addedLines) {
|
||||||
|
if (stringsMatch(line, _text, _regex)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_changeMatchType == ChangeMatchType::REMOVED ||
|
||||||
|
_changeMatchType == ChangeMatchType::ANY) {
|
||||||
|
for (const auto &line : removedLines) {
|
||||||
|
if (stringsMatch(line, _text, _regex)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool MacroConditionFile::CheckCondition()
|
bool MacroConditionFile::CheckCondition()
|
||||||
{
|
{
|
||||||
bool ret = false;
|
|
||||||
switch (_condition) {
|
switch (_condition) {
|
||||||
case MacroConditionFile::ConditionType::MATCH:
|
case MacroConditionFile::ConditionType::MATCH:
|
||||||
if (_fileType == FileType::REMOTE) {
|
return CheckFileContentMatch();
|
||||||
ret = CheckRemoteFileContent();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ret = CheckLocalFileContent();
|
|
||||||
break;
|
|
||||||
case MacroConditionFile::ConditionType::CONTENT_CHANGE:
|
case MacroConditionFile::ConditionType::CONTENT_CHANGE:
|
||||||
ret = CheckChangeContent();
|
return CheckChangeContent();
|
||||||
break;
|
|
||||||
case MacroConditionFile::ConditionType::DATE_CHANGE:
|
case MacroConditionFile::ConditionType::DATE_CHANGE:
|
||||||
ret = CheckChangeDate();
|
return CheckChangeDate();
|
||||||
break;
|
case MacroConditionFile::ConditionType::CHANGES_MATCH:
|
||||||
|
return CheckChangedMatch();
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetVariableValue().empty()) {
|
return false;
|
||||||
SetVariableValue(ret ? "true" : "false");
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroConditionFile::Save(obs_data_t *obj) const
|
bool MacroConditionFile::Save(obs_data_t *obj) const
|
||||||
@@ -173,9 +210,9 @@ bool MacroConditionFile::Save(obs_data_t *obj) const
|
|||||||
_file.Save(obj, "file");
|
_file.Save(obj, "file");
|
||||||
_text.Save(obj, "text");
|
_text.Save(obj, "text");
|
||||||
obs_data_set_int(obj, "fileType", static_cast<int>(_fileType));
|
obs_data_set_int(obj, "fileType", static_cast<int>(_fileType));
|
||||||
|
obs_data_set_int(obj, "changeMatchType",
|
||||||
|
static_cast<int>(_changeMatchType));
|
||||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||||
obs_data_set_bool(obj, "useTime", _useTime);
|
|
||||||
obs_data_set_bool(obj, "onlyMatchIfChanged", _onlyMatchIfChanged);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,10 +228,10 @@ bool MacroConditionFile::Load(obs_data_t *obj)
|
|||||||
_file.Load(obj, "file");
|
_file.Load(obj, "file");
|
||||||
_text.Load(obj, "text");
|
_text.Load(obj, "text");
|
||||||
_fileType = static_cast<FileType>(obs_data_get_int(obj, "fileType"));
|
_fileType = static_cast<FileType>(obs_data_get_int(obj, "fileType"));
|
||||||
|
_changeMatchType = static_cast<ChangeMatchType>(
|
||||||
|
obs_data_get_int(obj, "changeMatchType"));
|
||||||
_condition =
|
_condition =
|
||||||
static_cast<ConditionType>(obs_data_get_int(obj, "condition"));
|
static_cast<ConditionType>(obs_data_get_int(obj, "condition"));
|
||||||
_useTime = obs_data_get_bool(obj, "useTime");
|
|
||||||
_onlyMatchIfChanged = obs_data_get_bool(obj, "onlyMatchIfChanged");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,39 +242,79 @@ std::string MacroConditionFile::GetShortDesc() const
|
|||||||
|
|
||||||
static void populateFileTypes(QComboBox *list)
|
static void populateFileTypes(QComboBox *list)
|
||||||
{
|
{
|
||||||
list->addItem(obs_module_text("AdvSceneSwitcher.condition.file.local"));
|
static constexpr std::array<
|
||||||
list->addItem(
|
std::tuple<MacroConditionFile::FileType, std::string_view>, 2>
|
||||||
obs_module_text("AdvSceneSwitcher.condition.file.remote"));
|
fileTypes = {{{MacroConditionFile::FileType::LOCAL,
|
||||||
|
"AdvSceneSwitcher.condition.file.local"},
|
||||||
|
{MacroConditionFile::FileType::REMOTE,
|
||||||
|
"AdvSceneSwitcher.condition.file.remote"}}};
|
||||||
|
|
||||||
|
for (const auto &[type, name] : fileTypes) {
|
||||||
|
list->addItem(obs_module_text(name.data()),
|
||||||
|
static_cast<int>(type));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void populateConditions(QComboBox *list)
|
static void populateConditions(QComboBox *list)
|
||||||
{
|
{
|
||||||
list->addItem(
|
static constexpr std::array<
|
||||||
obs_module_text("AdvSceneSwitcher.condition.file.type.match"));
|
std::tuple<MacroConditionFile::ConditionType, std::string_view>,
|
||||||
list->addItem(obs_module_text(
|
4>
|
||||||
"AdvSceneSwitcher.condition.file.type.contentChange"));
|
conditionTypes = {{
|
||||||
list->addItem(obs_module_text(
|
{MacroConditionFile::ConditionType::MATCH,
|
||||||
"AdvSceneSwitcher.condition.file.type.dateChange"));
|
"AdvSceneSwitcher.condition.file.type.match"},
|
||||||
|
{MacroConditionFile::ConditionType::CONTENT_CHANGE,
|
||||||
|
"AdvSceneSwitcher.condition.file.type.contentChange"},
|
||||||
|
{MacroConditionFile::ConditionType::DATE_CHANGE,
|
||||||
|
"AdvSceneSwitcher.condition.file.type.dateChange"},
|
||||||
|
{MacroConditionFile::ConditionType::CHANGES_MATCH,
|
||||||
|
"AdvSceneSwitcher.condition.file.type.changesMatch"},
|
||||||
|
}};
|
||||||
|
|
||||||
|
for (const auto &[type, name] : conditionTypes) {
|
||||||
|
list->addItem(obs_module_text(name.data()),
|
||||||
|
static_cast<int>(type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void populateChangeMatchTypes(QComboBox *list)
|
||||||
|
{
|
||||||
|
static constexpr std::array<
|
||||||
|
std::tuple<MacroConditionFile::ChangeMatchType, std::string_view>,
|
||||||
|
3>
|
||||||
|
matchTypes = {{
|
||||||
|
{MacroConditionFile::ChangeMatchType::ANY,
|
||||||
|
"AdvSceneSwitcher.condition.file.changeMatchType.any"},
|
||||||
|
{MacroConditionFile::ChangeMatchType::ADDED,
|
||||||
|
"AdvSceneSwitcher.condition.file.changeMatchType.added"},
|
||||||
|
{MacroConditionFile::ChangeMatchType::REMOVED,
|
||||||
|
"AdvSceneSwitcher.condition.file.changeMatchType.removed"},
|
||||||
|
}};
|
||||||
|
|
||||||
|
for (const auto &[type, name] : matchTypes) {
|
||||||
|
list->addItem(obs_module_text(name.data()),
|
||||||
|
static_cast<int>(type));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MacroConditionFileEdit::MacroConditionFileEdit(
|
MacroConditionFileEdit::MacroConditionFileEdit(
|
||||||
QWidget *parent, std::shared_ptr<MacroConditionFile> entryData)
|
QWidget *parent, std::shared_ptr<MacroConditionFile> entryData)
|
||||||
: QWidget(parent),
|
: QWidget(parent),
|
||||||
_fileTypes(new QComboBox()),
|
_fileTypes(new QComboBox()),
|
||||||
|
_changeMatchTypes(new QComboBox()),
|
||||||
_conditions(new QComboBox()),
|
_conditions(new QComboBox()),
|
||||||
_filePath(new FileSelection()),
|
_filePath(new FileSelection()),
|
||||||
_matchText(new VariableTextEdit(this)),
|
_matchText(new VariableTextEdit(this)),
|
||||||
_regex(new RegexConfigWidget(parent)),
|
_regex(new RegexConfigWidget(parent))
|
||||||
_checkModificationDate(new QCheckBox(obs_module_text(
|
|
||||||
"AdvSceneSwitcher.fileTab.checkfileContentTime"))),
|
|
||||||
_checkFileContent(new QCheckBox(
|
|
||||||
obs_module_text("AdvSceneSwitcher.fileTab.checkfileContent")))
|
|
||||||
{
|
{
|
||||||
populateFileTypes(_fileTypes);
|
populateFileTypes(_fileTypes);
|
||||||
populateConditions(_conditions);
|
populateConditions(_conditions);
|
||||||
|
populateChangeMatchTypes(_changeMatchTypes);
|
||||||
|
|
||||||
QWidget::connect(_fileTypes, SIGNAL(currentIndexChanged(int)), this,
|
QWidget::connect(_fileTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||||
SLOT(FileTypeChanged(int)));
|
SLOT(FileTypeChanged(int)));
|
||||||
|
QWidget::connect(_changeMatchTypes, SIGNAL(currentIndexChanged(int)),
|
||||||
|
this, SLOT(ChangeMatchTypeChanged(int)));
|
||||||
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
|
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
|
||||||
SLOT(ConditionChanged(int)));
|
SLOT(ConditionChanged(int)));
|
||||||
QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this,
|
QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this,
|
||||||
@@ -246,41 +323,30 @@ MacroConditionFileEdit::MacroConditionFileEdit(
|
|||||||
SLOT(MatchTextChanged()));
|
SLOT(MatchTextChanged()));
|
||||||
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
|
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
|
||||||
SLOT(RegexChanged(RegexConfig)));
|
SLOT(RegexChanged(RegexConfig)));
|
||||||
QWidget::connect(_checkModificationDate, SIGNAL(stateChanged(int)),
|
|
||||||
this, SLOT(CheckModificationDateChanged(int)));
|
|
||||||
QWidget::connect(_checkFileContent, SIGNAL(stateChanged(int)), this,
|
|
||||||
SLOT(OnlyMatchIfChangedChanged(int)));
|
|
||||||
|
|
||||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||||
{"{{fileType}}", _fileTypes},
|
{"{{fileType}}", _fileTypes},
|
||||||
|
{"{{changeMatchType}}", _changeMatchTypes},
|
||||||
{"{{conditions}}", _conditions},
|
{"{{conditions}}", _conditions},
|
||||||
{"{{filePath}}", _filePath},
|
{"{{filePath}}", _filePath},
|
||||||
{"{{matchText}}", _matchText},
|
{"{{matchText}}", _matchText},
|
||||||
{"{{useRegex}}", _regex},
|
{"{{useRegex}}", _regex},
|
||||||
{"{{checkModificationDate}}", _checkModificationDate},
|
|
||||||
{"{{checkFileContent}}", _checkFileContent},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
auto line1Layout = new QHBoxLayout;
|
||||||
QHBoxLayout *line1Layout = new QHBoxLayout;
|
|
||||||
QHBoxLayout *line2Layout = new QHBoxLayout;
|
|
||||||
QHBoxLayout *line3Layout = new QHBoxLayout;
|
|
||||||
line1Layout->setContentsMargins(0, 0, 0, 0);
|
line1Layout->setContentsMargins(0, 0, 0, 0);
|
||||||
line2Layout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
line3Layout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
PlaceWidgets(
|
PlaceWidgets(
|
||||||
obs_module_text("AdvSceneSwitcher.condition.file.entry.line1"),
|
obs_module_text("AdvSceneSwitcher.condition.file.entry.line1"),
|
||||||
line1Layout, widgetPlaceholders);
|
line1Layout, widgetPlaceholders);
|
||||||
|
auto line2Layout = new QHBoxLayout;
|
||||||
|
line2Layout->setContentsMargins(0, 0, 0, 0);
|
||||||
PlaceWidgets(
|
PlaceWidgets(
|
||||||
obs_module_text("AdvSceneSwitcher.condition.file.entry.line2"),
|
obs_module_text("AdvSceneSwitcher.condition.file.entry.line2"),
|
||||||
line2Layout, widgetPlaceholders, false);
|
line2Layout, widgetPlaceholders, false);
|
||||||
PlaceWidgets(
|
|
||||||
obs_module_text("AdvSceneSwitcher.condition.file.entry.line3"),
|
auto mainLayout = new QVBoxLayout;
|
||||||
line3Layout, widgetPlaceholders);
|
|
||||||
mainLayout->addLayout(line1Layout);
|
mainLayout->addLayout(line1Layout);
|
||||||
mainLayout->addLayout(line2Layout);
|
mainLayout->addLayout(line2Layout);
|
||||||
mainLayout->addLayout(line3Layout);
|
|
||||||
|
|
||||||
setLayout(mainLayout);
|
setLayout(mainLayout);
|
||||||
|
|
||||||
_entryData = entryData;
|
_entryData = entryData;
|
||||||
@@ -294,21 +360,15 @@ void MacroConditionFileEdit::UpdateEntryData()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_fileTypes->setCurrentIndex(static_cast<int>(_entryData->_fileType));
|
_fileTypes->setCurrentIndex(
|
||||||
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
_fileTypes->findData(static_cast<int>(_entryData->_fileType)));
|
||||||
|
_conditions->setCurrentIndex(_conditions->findData(
|
||||||
|
static_cast<int>(_entryData->_condition)));
|
||||||
|
_changeMatchTypes->setCurrentIndex(_changeMatchTypes->findData(
|
||||||
|
static_cast<int>(_entryData->_changeMatchType)));
|
||||||
_filePath->SetPath(_entryData->_file);
|
_filePath->SetPath(_entryData->_file);
|
||||||
_matchText->setPlainText(_entryData->_text);
|
_matchText->setPlainText(_entryData->_text);
|
||||||
_regex->SetRegexConfig(_entryData->_regex);
|
_regex->SetRegexConfig(_entryData->_regex);
|
||||||
_checkModificationDate->setChecked(_entryData->_useTime);
|
|
||||||
_checkFileContent->setChecked(_entryData->_onlyMatchIfChanged);
|
|
||||||
|
|
||||||
// TODO: Remove in future version
|
|
||||||
if (!_entryData->_useTime) {
|
|
||||||
_checkModificationDate->hide();
|
|
||||||
}
|
|
||||||
if (!_entryData->_onlyMatchIfChanged) {
|
|
||||||
_checkFileContent->hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
SetWidgetVisibility();
|
SetWidgetVisibility();
|
||||||
}
|
}
|
||||||
@@ -319,19 +379,25 @@ void MacroConditionFileEdit::FileTypeChanged(int index)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MacroConditionFile::FileType type =
|
auto type = static_cast<MacroConditionFile::FileType>(
|
||||||
static_cast<MacroConditionFile::FileType>(index);
|
_fileTypes->itemData(index).toInt());
|
||||||
|
_filePath->Button()->setEnabled(type ==
|
||||||
|
MacroConditionFile::FileType::LOCAL);
|
||||||
|
auto lock = LockContext();
|
||||||
|
_entryData->_fileType = type;
|
||||||
|
}
|
||||||
|
|
||||||
if (type == MacroConditionFile::FileType::LOCAL) {
|
void MacroConditionFileEdit::ChangeMatchTypeChanged(int index)
|
||||||
_filePath->Button()->setDisabled(false);
|
{
|
||||||
_checkModificationDate->setDisabled(false);
|
if (_loading || !_entryData) {
|
||||||
} else {
|
return;
|
||||||
_filePath->Button()->setDisabled(true);
|
|
||||||
_checkModificationDate->setDisabled(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto lock = LockContext();
|
auto lock = LockContext();
|
||||||
_entryData->_fileType = type;
|
_entryData->_changeMatchType =
|
||||||
|
static_cast<MacroConditionFile::ChangeMatchType>(
|
||||||
|
_changeMatchTypes->itemData(index).toInt());
|
||||||
|
SetWidgetVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroConditionFileEdit::ConditionChanged(int index)
|
void MacroConditionFileEdit::ConditionChanged(int index)
|
||||||
@@ -341,8 +407,8 @@ void MacroConditionFileEdit::ConditionChanged(int index)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto lock = LockContext();
|
auto lock = LockContext();
|
||||||
_entryData->_condition =
|
_entryData->_condition = static_cast<MacroConditionFile::ConditionType>(
|
||||||
static_cast<MacroConditionFile::ConditionType>(index);
|
_conditions->itemData(index).toInt());
|
||||||
SetWidgetVisibility();
|
SetWidgetVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,44 +449,25 @@ void MacroConditionFileEdit::RegexChanged(RegexConfig conf)
|
|||||||
updateGeometry();
|
updateGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroConditionFileEdit::CheckModificationDateChanged(int state)
|
|
||||||
{
|
|
||||||
if (_loading || !_entryData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto lock = LockContext();
|
|
||||||
_entryData->_useTime = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MacroConditionFileEdit::OnlyMatchIfChangedChanged(int state)
|
|
||||||
{
|
|
||||||
if (_loading || !_entryData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto lock = LockContext();
|
|
||||||
_entryData->_onlyMatchIfChanged = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MacroConditionFileEdit::SetWidgetVisibility()
|
void MacroConditionFileEdit::SetWidgetVisibility()
|
||||||
{
|
{
|
||||||
if (!_entryData) {
|
if (!_entryData) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_matchText->setVisible(_entryData->_condition ==
|
_changeMatchTypes->setVisible(
|
||||||
MacroConditionFile::ConditionType::MATCH);
|
|
||||||
_regex->setVisible(_entryData->_condition ==
|
|
||||||
MacroConditionFile::ConditionType::MATCH);
|
|
||||||
_checkModificationDate->setVisible(
|
|
||||||
_entryData->_useTime &&
|
|
||||||
_entryData->_condition ==
|
_entryData->_condition ==
|
||||||
MacroConditionFile::ConditionType::MATCH);
|
MacroConditionFile::ConditionType::CHANGES_MATCH);
|
||||||
_checkFileContent->setVisible(
|
_matchText->setVisible(
|
||||||
_entryData->_onlyMatchIfChanged &&
|
|
||||||
_entryData->_condition ==
|
_entryData->_condition ==
|
||||||
MacroConditionFile::ConditionType::MATCH);
|
MacroConditionFile::ConditionType::MATCH ||
|
||||||
|
_entryData->_condition ==
|
||||||
|
MacroConditionFile::ConditionType::CHANGES_MATCH);
|
||||||
|
_regex->setVisible(
|
||||||
|
_entryData->_condition ==
|
||||||
|
MacroConditionFile::ConditionType::MATCH ||
|
||||||
|
_entryData->_condition ==
|
||||||
|
MacroConditionFile::ConditionType::CHANGES_MATCH);
|
||||||
adjustSize();
|
adjustSize();
|
||||||
updateGeometry();
|
updateGeometry();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
@@ -35,27 +36,31 @@ public:
|
|||||||
MATCH,
|
MATCH,
|
||||||
CONTENT_CHANGE,
|
CONTENT_CHANGE,
|
||||||
DATE_CHANGE,
|
DATE_CHANGE,
|
||||||
|
CHANGES_MATCH,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ChangeMatchType {
|
||||||
|
ANY,
|
||||||
|
ADDED,
|
||||||
|
REMOVED,
|
||||||
};
|
};
|
||||||
|
|
||||||
StringVariable _file = obs_module_text("AdvSceneSwitcher.enterPath");
|
StringVariable _file = obs_module_text("AdvSceneSwitcher.enterPath");
|
||||||
StringVariable _text = obs_module_text("AdvSceneSwitcher.enterText");
|
StringVariable _text = obs_module_text("AdvSceneSwitcher.enterText");
|
||||||
FileType _fileType = FileType::LOCAL;
|
FileType _fileType = FileType::LOCAL;
|
||||||
|
ChangeMatchType _changeMatchType = ChangeMatchType::ANY;
|
||||||
ConditionType _condition = ConditionType::MATCH;
|
ConditionType _condition = ConditionType::MATCH;
|
||||||
RegexConfig _regex;
|
RegexConfig _regex;
|
||||||
|
|
||||||
// TODO: Remove in future version
|
|
||||||
bool _useTime = false;
|
|
||||||
bool _onlyMatchIfChanged = false;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool MatchFileContent(QString &filedata);
|
bool CheckFileContentMatch();
|
||||||
bool CheckRemoteFileContent();
|
|
||||||
bool CheckLocalFileContent();
|
|
||||||
bool CheckChangeContent();
|
bool CheckChangeContent();
|
||||||
bool CheckChangeDate();
|
bool CheckChangeDate();
|
||||||
|
bool CheckChangedMatch();
|
||||||
|
|
||||||
QDateTime _lastMod;
|
QDateTime _lastMod;
|
||||||
size_t _lastHash = 0;
|
size_t _lastHash = 0;
|
||||||
|
QStringList _previousContent;
|
||||||
static bool _registered;
|
static bool _registered;
|
||||||
static const std::string id;
|
static const std::string id;
|
||||||
};
|
};
|
||||||
@@ -78,23 +83,21 @@ public:
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void FileTypeChanged(int index);
|
void FileTypeChanged(int index);
|
||||||
|
void ChangeMatchTypeChanged(int index);
|
||||||
void ConditionChanged(int index);
|
void ConditionChanged(int index);
|
||||||
void PathChanged(const QString &text);
|
void PathChanged(const QString &text);
|
||||||
void MatchTextChanged();
|
void MatchTextChanged();
|
||||||
void RegexChanged(RegexConfig);
|
void RegexChanged(RegexConfig);
|
||||||
void CheckModificationDateChanged(int state);
|
|
||||||
void OnlyMatchIfChangedChanged(int state);
|
|
||||||
signals:
|
signals:
|
||||||
void HeaderInfoChanged(const QString &);
|
void HeaderInfoChanged(const QString &);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QComboBox *_fileTypes;
|
QComboBox *_fileTypes;
|
||||||
|
QComboBox *_changeMatchTypes;
|
||||||
QComboBox *_conditions;
|
QComboBox *_conditions;
|
||||||
FileSelection *_filePath;
|
FileSelection *_filePath;
|
||||||
VariableTextEdit *_matchText;
|
VariableTextEdit *_matchText;
|
||||||
RegexConfigWidget *_regex;
|
RegexConfigWidget *_regex;
|
||||||
QCheckBox *_checkModificationDate;
|
|
||||||
QCheckBox *_checkFileContent;
|
|
||||||
std::shared_ptr<MacroConditionFile> _entryData;
|
std::shared_ptr<MacroConditionFile> _entryData;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,25 +19,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 +63,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 +78,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 +100,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 +111,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 +147,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,7 +166,7 @@ void MacroConditionProcessEdit::FocusChanged(int state)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto lock = LockContext();
|
auto lock = LockContext();
|
||||||
_entryData->_focus = state;
|
_entryData->_checkFocus = state;
|
||||||
SetWidgetVisibility();
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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,65 @@ 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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
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
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -34,28 +34,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);
|
||||||
|
|||||||
@@ -176,6 +176,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"),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 ¯o : macros) {
|
for (const auto ¯o : 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);
|
switcher->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) {
|
||||||
@@ -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,27 +409,26 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
importedMacros.emplace_back(macro);
|
||||||
switcher->macros.emplace_back(macro);
|
switcher->macros.emplace_back(macro);
|
||||||
if (groupSize > 0 && !macro->IsGroup()) {
|
if (groupSize > 0 && !macro->IsGroup()) {
|
||||||
Macro::PrepareMoveToGroup(group, macro);
|
Macro::PrepareMoveToGroup(group, macro);
|
||||||
@@ -445,11 +443,11 @@ 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);
|
for (const auto ¯o : importedMacros) {
|
||||||
|
macro->PostLoad();
|
||||||
|
}
|
||||||
|
|
||||||
ui->macros->Reset(switcher->macros,
|
ui->macros->Reset(switcher->macros,
|
||||||
switcher->macroProperties._highlightExecuted);
|
switcher->macroProperties._highlightExecuted);
|
||||||
@@ -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(
|
||||||
@@ -790,6 +787,8 @@ bool shouldRestoreSplitter(const QList<int> &pos)
|
|||||||
|
|
||||||
void AdvSceneSwitcher::SetupMacroTab()
|
void AdvSceneSwitcher::SetupMacroTab()
|
||||||
{
|
{
|
||||||
|
ui->macroElseActions->installEventFilter(this);
|
||||||
|
|
||||||
if (switcher->macros.size() == 0 && !switcher->disableHints) {
|
if (switcher->macros.size() == 0 && !switcher->disableHints) {
|
||||||
addPulse = PulseWidget(ui->macroAdd, QColor(Qt::green));
|
addPulse = PulseWidget(ui->macroAdd, QColor(Qt::green));
|
||||||
}
|
}
|
||||||
@@ -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()) &&
|
||||||
|
|||||||
@@ -149,6 +149,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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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-helper.hpp>
|
||||||
|
#include <sync-helper.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 {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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-helper.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;
|
||||||
|
|||||||
@@ -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-helper.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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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-helper.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 =
|
||||||
"?response_type=token"
|
("https://id.twitch.tv/oauth2/authorize"
|
||||||
"&client_id=" +
|
"?response_type=token"
|
||||||
QString(GetClientID()) +
|
"&client_id=" +
|
||||||
"&redirect_uri=http://localhost:8080/auth"
|
QString(GetClientID()) +
|
||||||
"&scope=" +
|
"&redirect_uri=http://localhost:%1"
|
||||||
_scope + "&state=" + QString::fromStdString(state);
|
"/auth"
|
||||||
|
"&scope=" +
|
||||||
|
_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) {
|
||||||
|
|||||||
@@ -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 ¶ms)
|
const httplib::Params ¶ms)
|
||||||
{
|
{
|
||||||
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 ¶ms, bool useCache)
|
const httplib::Params ¶ms, 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 ¶ms,
|
||||||
|
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());
|
|
||||||
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 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,
|
||||||
bool useCache)
|
const httplib::Params ¶ms,
|
||||||
|
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 = SendPostRequest(uri, path, token, data);
|
auto result = SendPostRequest(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 SendPutRequest(const TwitchToken &token, const std::string &uri,
|
||||||
const TwitchToken &token, const OBSData &data)
|
const std::string &path,
|
||||||
|
const httplib::Params ¶ms, 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 PUT 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.Put(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 SendPutRequest(const TwitchToken &token, const std::string &uri,
|
||||||
const TwitchToken &token, const OBSData &data,
|
const std::string &path,
|
||||||
bool useCache)
|
const httplib::Params ¶ms, 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 = SendPutRequest(token, uri, path, params, data);
|
||||||
cache[args] = {result};
|
cache[args] = {result};
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *GetClientID()
|
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
|
||||||
|
const std::string &path,
|
||||||
|
const httplib::Params ¶ms,
|
||||||
|
const OBSData &data)
|
||||||
{
|
{
|
||||||
return clientID.data();
|
httplib::Client cli(uri);
|
||||||
|
auto tokenStr = token.GetToken();
|
||||||
|
|
||||||
|
if (!tokenStr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pathWithParams = httplib::append_query_params(path, params);
|
||||||
|
auto url = uri + pathWithParams;
|
||||||
|
vblog(LOG_INFO, "Twitch PATCH request to %s began", url.c_str());
|
||||||
|
|
||||||
|
auto headers = getTokenRequestHeaders(*tokenStr);
|
||||||
|
auto body = getRequestBody(data);
|
||||||
|
auto response =
|
||||||
|
cli.Patch(pathWithParams, headers, body, "application/json");
|
||||||
|
|
||||||
|
return processResult(response, __func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
|
||||||
|
const std::string &path,
|
||||||
|
const httplib::Params ¶ms,
|
||||||
|
const OBSData &data, bool useCache)
|
||||||
|
{
|
||||||
|
static std::map<Args, CacheEntry> cache;
|
||||||
|
static std::mutex mtx;
|
||||||
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
auto tokenStr = token.GetToken();
|
||||||
|
|
||||||
|
if (!tokenStr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto headers = getTokenRequestHeaders(*tokenStr);
|
||||||
|
auto body = getRequestBody(data);
|
||||||
|
Args args(uri, path, body, params, headers);
|
||||||
|
|
||||||
|
if (useCache && cacheIsValid(cache, args)) {
|
||||||
|
auto it = cache.find(args);
|
||||||
|
return it->second.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = SendPatchRequest(token, uri, path, params, data);
|
||||||
|
cache[args] = {result};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestResult SendDeleteRequest(const TwitchToken &token,
|
||||||
|
const std::string &uri, const std::string &path,
|
||||||
|
const httplib::Params ¶ms)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
|||||||
@@ -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 ¶ms = {});
|
||||||
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 ¶ms = {},
|
||||||
|
const OBSData &data = nullptr);
|
||||||
|
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
|
||||||
|
const std::string &path,
|
||||||
|
const httplib::Params ¶ms = {},
|
||||||
|
const OBSData &data = nullptr);
|
||||||
|
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
|
||||||
|
const std::string &path,
|
||||||
|
const httplib::Params ¶ms = {},
|
||||||
|
const OBSData &data = nullptr);
|
||||||
|
RequestResult SendDeleteRequest(const TwitchToken &token,
|
||||||
|
const std::string &uri, const std::string &path,
|
||||||
|
const httplib::Params ¶ms = {});
|
||||||
|
|
||||||
// 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 ¶ms, bool useCache);
|
||||||
|
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
|
||||||
|
const std::string &path,
|
||||||
|
const httplib::Params ¶ms,
|
||||||
|
const OBSData &data, bool useCache);
|
||||||
|
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
|
||||||
|
const std::string &path,
|
||||||
|
const httplib::Params ¶ms, 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 ¶ms,
|
||||||
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
|
||||||
|
|||||||
@@ -177,8 +177,8 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
|
|||||||
{
|
{
|
||||||
MacroCondition::Load(obj);
|
MacroCondition::Load(obj);
|
||||||
_video.Load(obj);
|
_video.Load(obj);
|
||||||
_condition =
|
SetCondition(static_cast<VideoCondition>(
|
||||||
static_cast<VideoCondition>(obs_data_get_int(obj, "condition"));
|
obs_data_get_int(obj, "condition")));
|
||||||
_file = obs_data_get_string(obj, "filePath");
|
_file = obs_data_get_string(obj, "filePath");
|
||||||
_blockUntilScreenshotDone =
|
_blockUntilScreenshotDone =
|
||||||
obs_data_get_bool(obj, "blockUntilScreenshotDone");
|
obs_data_get_bool(obj, "blockUntilScreenshotDone");
|
||||||
@@ -215,8 +215,15 @@ void MacroConditionVideo::GetScreenshot(bool blocking)
|
|||||||
{
|
{
|
||||||
auto source = obs_weak_source_get_source(_video.GetVideo());
|
auto source = obs_weak_source_get_source(_video.GetVideo());
|
||||||
_screenshotData.~ScreenshotHelper();
|
_screenshotData.~ScreenshotHelper();
|
||||||
new (&_screenshotData)
|
QRect screenshotArea;
|
||||||
ScreenshotHelper(source, blocking, GetSwitcher()->interval);
|
if (_areaParameters.enable && _condition != VideoCondition::NO_IMAGE) {
|
||||||
|
screenshotArea.setRect(_areaParameters.area.x,
|
||||||
|
_areaParameters.area.y,
|
||||||
|
_areaParameters.area.width,
|
||||||
|
_areaParameters.area.height);
|
||||||
|
}
|
||||||
|
new (&_screenshotData) ScreenshotHelper(
|
||||||
|
source, screenshotArea, blocking, GetSwitcher()->interval);
|
||||||
obs_source_release(source);
|
obs_source_release(source);
|
||||||
_getNextScreenshot = false;
|
_getNextScreenshot = false;
|
||||||
}
|
}
|
||||||
@@ -261,6 +268,12 @@ bool MacroConditionVideo::SetLanguage(const std::string &language)
|
|||||||
return _ocrParameters.SetLanguageCode(language);
|
return _ocrParameters.SetLanguageCode(language);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroConditionVideo::SetCondition(VideoCondition condition)
|
||||||
|
{
|
||||||
|
_condition = condition;
|
||||||
|
SetupTempVars();
|
||||||
|
}
|
||||||
|
|
||||||
bool MacroConditionVideo::ScreenshotContainsPattern()
|
bool MacroConditionVideo::ScreenshotContainsPattern()
|
||||||
{
|
{
|
||||||
cv::Mat result;
|
cv::Mat result;
|
||||||
@@ -269,9 +282,12 @@ bool MacroConditionVideo::ScreenshotContainsPattern()
|
|||||||
_patternMatchParameters.useAlphaAsMask,
|
_patternMatchParameters.useAlphaAsMask,
|
||||||
_patternMatchParameters.matchMode);
|
_patternMatchParameters.matchMode);
|
||||||
if (result.total() == 0) {
|
if (result.total() == 0) {
|
||||||
|
SetTempVarValue("patternCount", "0");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return countNonZero(result) > 0;
|
const auto count = countNonZero(result);
|
||||||
|
SetTempVarValue("patternCount", std::to_string(count));
|
||||||
|
return count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroConditionVideo::OutputChanged()
|
bool MacroConditionVideo::OutputChanged()
|
||||||
@@ -300,12 +316,15 @@ bool MacroConditionVideo::ScreenshotContainsObject()
|
|||||||
_objMatchParameters.minNeighbors,
|
_objMatchParameters.minNeighbors,
|
||||||
_objMatchParameters.minSize.CV(),
|
_objMatchParameters.minSize.CV(),
|
||||||
_objMatchParameters.maxSize.CV());
|
_objMatchParameters.maxSize.CV());
|
||||||
return objects.size() > 0;
|
const auto count = objects.size();
|
||||||
|
SetTempVarValue("objectCount", std::to_string(count));
|
||||||
|
return count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroConditionVideo::CheckBrightnessThreshold()
|
bool MacroConditionVideo::CheckBrightnessThreshold()
|
||||||
{
|
{
|
||||||
_currentBrightness = GetAvgBrightness(_screenshotData.image) / 255.;
|
_currentBrightness = GetAvgBrightness(_screenshotData.image) / 255.;
|
||||||
|
SetTempVarValue("brightness", std::to_string(_currentBrightness));
|
||||||
return _currentBrightness > _brightnessThreshold;
|
return _currentBrightness > _brightnessThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,6 +337,7 @@ bool MacroConditionVideo::CheckOCR()
|
|||||||
auto text = RunOCR(_ocrParameters.GetOCR(), _screenshotData.image,
|
auto text = RunOCR(_ocrParameters.GetOCR(), _screenshotData.image,
|
||||||
_ocrParameters.color, _ocrParameters.colorThreshold);
|
_ocrParameters.color, _ocrParameters.colorThreshold);
|
||||||
SetVariableValue(text);
|
SetVariableValue(text);
|
||||||
|
SetTempVarValue("text", text);
|
||||||
if (!_ocrParameters.regex.Enabled()) {
|
if (!_ocrParameters.regex.Enabled()) {
|
||||||
return text == std::string(_ocrParameters.text);
|
return text == std::string(_ocrParameters.text);
|
||||||
}
|
}
|
||||||
@@ -332,21 +352,22 @@ bool MacroConditionVideo::CheckOCR()
|
|||||||
|
|
||||||
bool MacroConditionVideo::CheckColor()
|
bool MacroConditionVideo::CheckColor()
|
||||||
{
|
{
|
||||||
return ContainsPixelsInColorRange(_screenshotData.image,
|
const bool ret = ContainsPixelsInColorRange(
|
||||||
_colorParameters.color,
|
_screenshotData.image, _colorParameters.color,
|
||||||
_colorParameters.colorThreshold,
|
_colorParameters.colorThreshold,
|
||||||
_colorParameters.matchThreshold);
|
_colorParameters.matchThreshold);
|
||||||
|
// Way too slow for now
|
||||||
|
//SetTempVarValue("dominantColor", GetDominantColor(_screenshotData.image, 3)
|
||||||
|
// .name(QColor::HexArgb)
|
||||||
|
// .toStdString());
|
||||||
|
SetTempVarValue("color", GetAverageColor(_screenshotData.image)
|
||||||
|
.name(QColor::HexArgb)
|
||||||
|
.toStdString());
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroConditionVideo::Compare()
|
bool MacroConditionVideo::Compare()
|
||||||
{
|
{
|
||||||
if (_areaParameters.enable && _condition != VideoCondition::NO_IMAGE) {
|
|
||||||
_screenshotData.image = _screenshotData.image.copy(
|
|
||||||
_areaParameters.area.x, _areaParameters.area.y,
|
|
||||||
_areaParameters.area.width,
|
|
||||||
_areaParameters.area.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_condition != VideoCondition::OCR) {
|
if (_condition != VideoCondition::OCR) {
|
||||||
SetVariableValue("");
|
SetVariableValue("");
|
||||||
}
|
}
|
||||||
@@ -378,6 +399,58 @@ bool MacroConditionVideo::Compare()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroConditionVideo::SetupTempVars()
|
||||||
|
{
|
||||||
|
MacroCondition::SetupTempVars();
|
||||||
|
switch (_condition) {
|
||||||
|
case VideoCondition::PATTERN:
|
||||||
|
AddTempvar(
|
||||||
|
"patternCount",
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.tempVar.video.patternCount"),
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.tempVar.video.patternCount.description"));
|
||||||
|
break;
|
||||||
|
case VideoCondition::OBJECT:
|
||||||
|
AddTempvar(
|
||||||
|
"objectCount",
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.tempVar.video.objectCount"),
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.tempVar.video.objectCount.description"));
|
||||||
|
break;
|
||||||
|
case VideoCondition::BRIGHTNESS:
|
||||||
|
AddTempvar(
|
||||||
|
"brightness",
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.tempVar.video.brightness"),
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.tempVar.video.brightness.description"));
|
||||||
|
break;
|
||||||
|
case VideoCondition::OCR:
|
||||||
|
AddTempvar(
|
||||||
|
"text",
|
||||||
|
obs_module_text("AdvSceneSwitcher.tempVar.video.text"),
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.tempVar.video.text.description"));
|
||||||
|
break;
|
||||||
|
case VideoCondition::COLOR:
|
||||||
|
AddTempvar(
|
||||||
|
"color",
|
||||||
|
obs_module_text("AdvSceneSwitcher.tempVar.video.color"),
|
||||||
|
obs_module_text(
|
||||||
|
"AdvSceneSwitcher.tempVar.video.color.description"));
|
||||||
|
break;
|
||||||
|
case VideoCondition::MATCH:
|
||||||
|
case VideoCondition::DIFFER:
|
||||||
|
case VideoCondition::HAS_NOT_CHANGED:
|
||||||
|
case VideoCondition::HAS_CHANGED:
|
||||||
|
case VideoCondition::NO_IMAGE:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static inline void populateVideoInputSelection(QComboBox *list)
|
static inline void populateVideoInputSelection(QComboBox *list)
|
||||||
{
|
{
|
||||||
for (const auto &[_, name] : videoInputTypes) {
|
for (const auto &[_, name] : videoInputTypes) {
|
||||||
@@ -1155,7 +1228,7 @@ void MacroConditionVideoEdit::UpdatePreviewTooltip()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!requiresFileInput(_entryData->_condition)) {
|
if (!requiresFileInput(_entryData->GetCondition())) {
|
||||||
this->setToolTip("");
|
this->setToolTip("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1224,7 +1297,7 @@ void MacroConditionVideoEdit::ConditionChanged(int cond)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto lock = LockContext();
|
auto lock = LockContext();
|
||||||
_entryData->_condition = static_cast<VideoCondition>(cond);
|
_entryData->SetCondition(static_cast<VideoCondition>(cond));
|
||||||
_entryData->ResetLastMatch();
|
_entryData->ResetLastMatch();
|
||||||
SetWidgetVisibility();
|
SetWidgetVisibility();
|
||||||
|
|
||||||
@@ -1239,7 +1312,7 @@ void MacroConditionVideoEdit::ConditionChanged(int cond)
|
|||||||
_previewDialog.PatternMatchParametersChanged(
|
_previewDialog.PatternMatchParametersChanged(
|
||||||
_entryData->_patternMatchParameters);
|
_entryData->_patternMatchParameters);
|
||||||
|
|
||||||
if (_entryData->_condition == VideoCondition::OBJECT) {
|
if (_entryData->GetCondition() == VideoCondition::OBJECT) {
|
||||||
auto path = _entryData->GetModelDataPath();
|
auto path = _entryData->GetModelDataPath();
|
||||||
_entryData->_objMatchParameters.cascade =
|
_entryData->_objMatchParameters.cascade =
|
||||||
initObjectCascade(path);
|
initObjectCascade(path);
|
||||||
@@ -1482,27 +1555,28 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
|
|||||||
_sources->setVisible(_entryData->_video.type ==
|
_sources->setVisible(_entryData->_video.type ==
|
||||||
VideoInput::Type::SOURCE);
|
VideoInput::Type::SOURCE);
|
||||||
_scenes->setVisible(_entryData->_video.type == VideoInput::Type::SCENE);
|
_scenes->setVisible(_entryData->_video.type == VideoInput::Type::SCENE);
|
||||||
_imagePath->setVisible(requiresFileInput(_entryData->_condition));
|
_imagePath->setVisible(requiresFileInput(_entryData->GetCondition()));
|
||||||
_usePatternForChangedCheck->setVisible(
|
_usePatternForChangedCheck->setVisible(
|
||||||
patternControlIsOptional(_entryData->_condition));
|
patternControlIsOptional(_entryData->GetCondition()));
|
||||||
_patternThreshold->setVisible(needsThreshold(_entryData->_condition));
|
_patternThreshold->setVisible(
|
||||||
_useAlphaAsMask->setVisible(_entryData->_condition ==
|
needsThreshold(_entryData->GetCondition()));
|
||||||
|
_useAlphaAsMask->setVisible(_entryData->GetCondition() ==
|
||||||
VideoCondition::PATTERN);
|
VideoCondition::PATTERN);
|
||||||
SetLayoutVisible(_patternMatchModeLayout,
|
SetLayoutVisible(_patternMatchModeLayout,
|
||||||
_entryData->_condition == VideoCondition::PATTERN);
|
_entryData->GetCondition() == VideoCondition::PATTERN);
|
||||||
_brightness->setVisible(_entryData->_condition ==
|
_brightness->setVisible(_entryData->GetCondition() ==
|
||||||
VideoCondition::BRIGHTNESS);
|
VideoCondition::BRIGHTNESS);
|
||||||
_showMatch->setVisible(needsShowMatch(_entryData->_condition));
|
_showMatch->setVisible(needsShowMatch(_entryData->GetCondition()));
|
||||||
_ocr->setVisible(_entryData->_condition == VideoCondition::OCR);
|
_ocr->setVisible(_entryData->GetCondition() == VideoCondition::OCR);
|
||||||
_objectDetect->setVisible(_entryData->_condition ==
|
_objectDetect->setVisible(_entryData->GetCondition() ==
|
||||||
VideoCondition::OBJECT);
|
VideoCondition::OBJECT);
|
||||||
_color->setVisible(_entryData->_condition == VideoCondition::COLOR);
|
_color->setVisible(_entryData->GetCondition() == VideoCondition::COLOR);
|
||||||
SetLayoutVisible(_throttleControlLayout,
|
SetLayoutVisible(_throttleControlLayout,
|
||||||
needsThrottleControls(_entryData->_condition));
|
needsThrottleControls(_entryData->GetCondition()));
|
||||||
_area->setVisible(needsAreaControls(_entryData->_condition));
|
_area->setVisible(needsAreaControls(_entryData->GetCondition()));
|
||||||
|
|
||||||
if (_entryData->_condition == VideoCondition::HAS_CHANGED ||
|
if (_entryData->GetCondition() == VideoCondition::HAS_CHANGED ||
|
||||||
_entryData->_condition == VideoCondition::HAS_NOT_CHANGED) {
|
_entryData->GetCondition() == VideoCondition::HAS_NOT_CHANGED) {
|
||||||
_patternThreshold->setVisible(
|
_patternThreshold->setVisible(
|
||||||
_entryData->_patternMatchParameters.useForChangedCheck);
|
_entryData->_patternMatchParameters.useForChangedCheck);
|
||||||
SetLayoutVisible(
|
SetLayoutVisible(
|
||||||
@@ -1528,7 +1602,7 @@ void MacroConditionVideoEdit::SetupPreviewDialogParams()
|
|||||||
_previewDialog.VideoSelectionChanged(_entryData->_video);
|
_previewDialog.VideoSelectionChanged(_entryData->_video);
|
||||||
_previewDialog.AreaParametersChanged(_entryData->_areaParameters);
|
_previewDialog.AreaParametersChanged(_entryData->_areaParameters);
|
||||||
_previewDialog.ConditionChanged(
|
_previewDialog.ConditionChanged(
|
||||||
static_cast<int>(_entryData->_condition));
|
static_cast<int>(_entryData->GetCondition()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroConditionVideoEdit::UpdateEntryData()
|
void MacroConditionVideoEdit::UpdateEntryData()
|
||||||
@@ -1541,7 +1615,8 @@ void MacroConditionVideoEdit::UpdateEntryData()
|
|||||||
static_cast<int>(_entryData->_video.type));
|
static_cast<int>(_entryData->_video.type));
|
||||||
_scenes->SetScene(_entryData->_video.scene);
|
_scenes->SetScene(_entryData->_video.scene);
|
||||||
_sources->SetSource(_entryData->_video.source);
|
_sources->SetSource(_entryData->_video.source);
|
||||||
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
_condition->setCurrentIndex(
|
||||||
|
static_cast<int>(_entryData->GetCondition()));
|
||||||
_reduceLatency->setChecked(_entryData->_blockUntilScreenshotDone);
|
_reduceLatency->setChecked(_entryData->_blockUntilScreenshotDone);
|
||||||
_imagePath->SetPath(QString::fromStdString(_entryData->_file));
|
_imagePath->SetPath(QString::fromStdString(_entryData->_file));
|
||||||
_usePatternForChangedCheck->setChecked(
|
_usePatternForChangedCheck->setChecked(
|
||||||
|
|||||||
@@ -45,8 +45,10 @@ public:
|
|||||||
void SetPageSegMode(tesseract::PageSegMode);
|
void SetPageSegMode(tesseract::PageSegMode);
|
||||||
bool SetLanguage(const std::string &);
|
bool SetLanguage(const std::string &);
|
||||||
|
|
||||||
|
void SetCondition(VideoCondition);
|
||||||
|
VideoCondition GetCondition() const { return _condition; }
|
||||||
|
|
||||||
VideoInput _video;
|
VideoInput _video;
|
||||||
VideoCondition _condition = VideoCondition::MATCH;
|
|
||||||
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
|
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
|
||||||
// Enabling this will reduce matching latency, but slow down the
|
// Enabling this will reduce matching latency, but slow down the
|
||||||
// the condition checks of all macros overall.
|
// the condition checks of all macros overall.
|
||||||
@@ -74,6 +76,10 @@ private:
|
|||||||
bool Compare();
|
bool Compare();
|
||||||
bool CheckShouldBeSkipped();
|
bool CheckShouldBeSkipped();
|
||||||
|
|
||||||
|
void SetupTempVars();
|
||||||
|
|
||||||
|
VideoCondition _condition = VideoCondition::MATCH;
|
||||||
|
|
||||||
bool _getNextScreenshot = true;
|
bool _getNextScreenshot = true;
|
||||||
ScreenshotHelper _screenshotData;
|
ScreenshotHelper _screenshotData;
|
||||||
QImage _matchImage;
|
QImage _matchImage;
|
||||||
|
|||||||
@@ -225,6 +225,61 @@ bool ContainsPixelsInColorRange(const QImage &image, const QColor &color,
|
|||||||
return matchPercentage >= totalPixelMatchThreshold;
|
return matchPercentage >= totalPixelMatchThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QColor GetAverageColor(const QImage &img)
|
||||||
|
{
|
||||||
|
if (img.isNull()) {
|
||||||
|
return QColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto image = QImageToMat(img);
|
||||||
|
cv::Scalar meanColor = cv::mean(image);
|
||||||
|
int averageBlue = cvRound(meanColor[0]);
|
||||||
|
int averageGreen = cvRound(meanColor[1]);
|
||||||
|
int averageRed = cvRound(meanColor[2]);
|
||||||
|
|
||||||
|
return QColor(averageRed, averageGreen, averageBlue);
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor GetDominantColor(const QImage &img, int k)
|
||||||
|
{
|
||||||
|
if (img.isNull()) {
|
||||||
|
return QColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto image = QImageToMat(img);
|
||||||
|
cv::Mat reshapedImage = image.reshape(1, image.rows * image.cols);
|
||||||
|
reshapedImage.convertTo(reshapedImage, CV_32F);
|
||||||
|
|
||||||
|
cv::mean(reshapedImage);
|
||||||
|
|
||||||
|
// Apply k-means clustering to group similar colors
|
||||||
|
cv::TermCriteria criteria(
|
||||||
|
cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 100, 0.2);
|
||||||
|
cv::Mat labels, centers;
|
||||||
|
cv::kmeans(reshapedImage, k, labels, criteria, 1,
|
||||||
|
cv::KMEANS_RANDOM_CENTERS, centers);
|
||||||
|
|
||||||
|
// Find the dominant color
|
||||||
|
// Center of the cluster with the largest number of pixels
|
||||||
|
cv::Mat counts = cv::Mat::zeros(1, k, CV_32SC1);
|
||||||
|
for (int i = 0; i < labels.rows; i++) {
|
||||||
|
counts.at<int>(0, labels.at<int>(i))++;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Point max_loc;
|
||||||
|
cv::minMaxLoc(counts, nullptr, nullptr, nullptr, &max_loc);
|
||||||
|
try {
|
||||||
|
cv::Scalar dominantColor = centers.at<cv::Scalar>(max_loc.y);
|
||||||
|
const int blue = cv::saturate_cast<int>(dominantColor.val[0]);
|
||||||
|
const int green = cv::saturate_cast<int>(dominantColor.val[1]);
|
||||||
|
const int red = cv::saturate_cast<int>(dominantColor.val[2]);
|
||||||
|
const int alpha = cv::saturate_cast<int>(dominantColor.val[3]);
|
||||||
|
return QColor(red, green, blue, alpha);
|
||||||
|
} catch (...) {
|
||||||
|
}
|
||||||
|
return QColor();
|
||||||
|
}
|
||||||
|
|
||||||
// Assumption is that QImage uses Format_RGBA8888.
|
// Assumption is that QImage uses Format_RGBA8888.
|
||||||
// Conversion from: https://github.com/dbzhang800/QtOpenCV
|
// Conversion from: https://github.com/dbzhang800/QtOpenCV
|
||||||
cv::Mat QImageToMat(const QImage &img)
|
cv::Mat QImageToMat(const QImage &img)
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ std::string RunOCR(tesseract::TessBaseAPI *, const QImage &, const QColor &,
|
|||||||
bool ContainsPixelsInColorRange(const QImage &image, const QColor &color,
|
bool ContainsPixelsInColorRange(const QImage &image, const QColor &color,
|
||||||
double colorDeviationThreshold,
|
double colorDeviationThreshold,
|
||||||
double totalPixelMatchThreshold);
|
double totalPixelMatchThreshold);
|
||||||
|
QColor GetAverageColor(const QImage &img);
|
||||||
|
QColor GetDominantColor(const QImage &image, int k);
|
||||||
cv::Mat QImageToMat(const QImage &img);
|
cv::Mat QImageToMat(const QImage &img);
|
||||||
QImage MatToQImage(const cv::Mat &mat);
|
QImage MatToQImage(const cv::Mat &mat);
|
||||||
|
|
||||||
|
|||||||
@@ -255,7 +255,13 @@ void PreviewImage::CreateImage(const VideoInput &video, PreviewType type,
|
|||||||
VideoCondition condition)
|
VideoCondition condition)
|
||||||
{
|
{
|
||||||
auto source = obs_weak_source_get_source(video.GetVideo());
|
auto source = obs_weak_source_get_source(video.GetVideo());
|
||||||
ScreenshotHelper screenshot(source, true);
|
QRect screenshotArea;
|
||||||
|
if (areaParams.enable && type == PreviewType::SHOW_MATCH) {
|
||||||
|
screenshotArea.setRect(areaParams.area.x, areaParams.area.y,
|
||||||
|
areaParams.area.width,
|
||||||
|
areaParams.area.height);
|
||||||
|
}
|
||||||
|
ScreenshotHelper screenshot(source, screenshotArea, true);
|
||||||
obs_source_release(source);
|
obs_source_release(source);
|
||||||
|
|
||||||
if (!video.ValidSelection() || !screenshot.done) {
|
if (!video.ValidSelection() || !screenshot.done) {
|
||||||
@@ -274,11 +280,6 @@ void PreviewImage::CreateImage(const VideoInput &video, PreviewType type,
|
|||||||
|
|
||||||
if (type == PreviewType::SHOW_MATCH) {
|
if (type == PreviewType::SHOW_MATCH) {
|
||||||
std::unique_lock<std::mutex> lock(_mtx);
|
std::unique_lock<std::mutex> lock(_mtx);
|
||||||
if (areaParams.enable) {
|
|
||||||
screenshot.image = screenshot.image.copy(
|
|
||||||
areaParams.area.x, areaParams.area.y,
|
|
||||||
areaParams.area.width, areaParams.area.height);
|
|
||||||
}
|
|
||||||
// Will emit status label update
|
// Will emit status label update
|
||||||
MarkMatch(screenshot.image, patternMatchParams,
|
MarkMatch(screenshot.image, patternMatchParams,
|
||||||
patternImageData, objDetectParams, ocrParams,
|
patternImageData, objDetectParams, ocrParams,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ void SwitcherData::Prune()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (nonMatchingScene && !WeakSourceValid(nonMatchingScene)) {
|
if (nonMatchingScene && !WeakSourceValid(nonMatchingScene)) {
|
||||||
switchIfNotMatching = NoMatch::NO_SWITCH;
|
switchIfNotMatching = NoMatchBehavior::NO_SWITCH;
|
||||||
nonMatchingScene = nullptr;
|
nonMatchingScene = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "curl-helper.hpp"
|
#include "curl-helper.hpp"
|
||||||
#include "priority-helper.hpp"
|
#include "priority-helper.hpp"
|
||||||
#include "log-helper.hpp"
|
#include "log-helper.hpp"
|
||||||
|
#include "plugin-state-helper.hpp"
|
||||||
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -80,7 +81,6 @@ public:
|
|||||||
|
|
||||||
void SaveSettings(obs_data_t *obj);
|
void SaveSettings(obs_data_t *obj);
|
||||||
void SaveMacros(obs_data_t *obj);
|
void SaveMacros(obs_data_t *obj);
|
||||||
void SaveConnections(obs_data_t *obj);
|
|
||||||
void SaveVariables(obs_data_t *obj);
|
void SaveVariables(obs_data_t *obj);
|
||||||
void SaveGeneralSettings(obs_data_t *obj);
|
void SaveGeneralSettings(obs_data_t *obj);
|
||||||
void SaveHotkeys(obs_data_t *obj);
|
void SaveHotkeys(obs_data_t *obj);
|
||||||
@@ -89,7 +89,6 @@ public:
|
|||||||
|
|
||||||
void LoadSettings(obs_data_t *obj);
|
void LoadSettings(obs_data_t *obj);
|
||||||
void LoadMacros(obs_data_t *obj);
|
void LoadMacros(obs_data_t *obj);
|
||||||
void LoadConnections(obs_data_t *obj);
|
|
||||||
void LoadVariables(obs_data_t *obj);
|
void LoadVariables(obs_data_t *obj);
|
||||||
void LoadGeneralSettings(obs_data_t *obj);
|
void LoadGeneralSettings(obs_data_t *obj);
|
||||||
void LoadHotkeys(obs_data_t *obj);
|
void LoadHotkeys(obs_data_t *obj);
|
||||||
@@ -143,8 +142,7 @@ public:
|
|||||||
|
|
||||||
int interval = default_interval;
|
int interval = default_interval;
|
||||||
OBSWeakSource nonMatchingScene;
|
OBSWeakSource nonMatchingScene;
|
||||||
enum class NoMatch { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 };
|
NoMatchBehavior switchIfNotMatching = NoMatchBehavior::NO_SWITCH;
|
||||||
NoMatch switchIfNotMatching = NoMatch::NO_SWITCH;
|
|
||||||
Duration noMatchDelay;
|
Duration noMatchDelay;
|
||||||
enum class StartupBehavior { PERSIST = 0, START = 1, STOP = 2 };
|
enum class StartupBehavior { PERSIST = 0, START = 1, STOP = 2 };
|
||||||
StartupBehavior startupBehavior = StartupBehavior::PERSIST;
|
StartupBehavior startupBehavior = StartupBehavior::PERSIST;
|
||||||
@@ -175,8 +173,6 @@ public:
|
|||||||
bool macroSceneSwitched = false;
|
bool macroSceneSwitched = false;
|
||||||
|
|
||||||
Curlhelper curl;
|
Curlhelper curl;
|
||||||
std::deque<std::shared_ptr<Item>> connections;
|
|
||||||
std::vector<std::string> websocketMessages;
|
|
||||||
std::deque<std::shared_ptr<Item>> variables;
|
std::deque<std::shared_ptr<Item>> variables;
|
||||||
|
|
||||||
std::string lastTitle;
|
std::string lastTitle;
|
||||||
|
|||||||
@@ -1,18 +1,33 @@
|
|||||||
#include "connection-manager.hpp"
|
#include "connection-manager.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
#include "obs-module-helper.hpp"
|
||||||
|
#include "plugin-state-helper.hpp"
|
||||||
#include "name-dialog.hpp"
|
#include "name-dialog.hpp"
|
||||||
#include "switcher-data.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <obs-module.h>
|
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(advss::Connection *);
|
Q_DECLARE_METATYPE(advss::Connection *);
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
void SwitcherData::SaveConnections(obs_data_t *obj)
|
static std::deque<std::shared_ptr<Item>> connections;
|
||||||
|
static void saveConnections(obs_data_t *obj);
|
||||||
|
static void loadConnections(obs_data_t *obj);
|
||||||
|
|
||||||
|
void SetupConnectionManager()
|
||||||
|
{
|
||||||
|
static bool done = false;
|
||||||
|
if (done) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AddSaveStep(saveConnections);
|
||||||
|
AddLoadStep(loadConnections);
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void saveConnections(obs_data_t *obj)
|
||||||
{
|
{
|
||||||
obs_data_array_t *connectionArray = obs_data_array_create();
|
obs_data_array_t *connectionArray = obs_data_array_create();
|
||||||
for (const auto &c : connections) {
|
for (const auto &c : connections) {
|
||||||
@@ -25,7 +40,7 @@ void SwitcherData::SaveConnections(obs_data_t *obj)
|
|||||||
obs_data_array_release(connectionArray);
|
obs_data_array_release(connectionArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SwitcherData::LoadConnections(obs_data_t *obj)
|
static void loadConnections(obs_data_t *obj)
|
||||||
{
|
{
|
||||||
connections.clear();
|
connections.clear();
|
||||||
|
|
||||||
@@ -192,7 +207,7 @@ Connection *GetConnectionByName(const QString &name)
|
|||||||
|
|
||||||
Connection *GetConnectionByName(const std::string &name)
|
Connection *GetConnectionByName(const std::string &name)
|
||||||
{
|
{
|
||||||
for (auto &con : switcher->connections) {
|
for (auto &con : connections) {
|
||||||
if (con->Name() == name) {
|
if (con->Name() == name) {
|
||||||
return dynamic_cast<Connection *>(con.get());
|
return dynamic_cast<Connection *>(con.get());
|
||||||
}
|
}
|
||||||
@@ -202,7 +217,7 @@ Connection *GetConnectionByName(const std::string &name)
|
|||||||
|
|
||||||
std::weak_ptr<Connection> GetWeakConnectionByName(const std::string &name)
|
std::weak_ptr<Connection> GetWeakConnectionByName(const std::string &name)
|
||||||
{
|
{
|
||||||
for (const auto &c : switcher->connections) {
|
for (const auto &c : connections) {
|
||||||
if (c->Name() == name) {
|
if (c->Name() == name) {
|
||||||
std::weak_ptr<Connection> wp =
|
std::weak_ptr<Connection> wp =
|
||||||
std::dynamic_pointer_cast<Connection>(c);
|
std::dynamic_pointer_cast<Connection>(c);
|
||||||
@@ -226,6 +241,11 @@ std::string GetWeakConnectionName(std::weak_ptr<Connection> connection)
|
|||||||
return con->Name();
|
return con->Name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::deque<std::shared_ptr<Item>> &GetConnections()
|
||||||
|
{
|
||||||
|
return connections;
|
||||||
|
}
|
||||||
|
|
||||||
static bool ConnectionNameAvailable(const QString &name)
|
static bool ConnectionNameAvailable(const QString &name)
|
||||||
{
|
{
|
||||||
return !GetConnectionByName(name);
|
return !GetConnectionByName(name);
|
||||||
@@ -244,8 +264,7 @@ static bool AskForSettingsWrapper(QWidget *parent, Item &settings)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ConnectionSelection::ConnectionSelection(QWidget *parent)
|
ConnectionSelection::ConnectionSelection(QWidget *parent)
|
||||||
: ItemSelection(switcher->connections, Connection::Create,
|
: ItemSelection(connections, Connection::Create, AskForSettingsWrapper,
|
||||||
AskForSettingsWrapper,
|
|
||||||
"AdvSceneSwitcher.connection.select",
|
"AdvSceneSwitcher.connection.select",
|
||||||
"AdvSceneSwitcher.connection.add",
|
"AdvSceneSwitcher.connection.add",
|
||||||
"AdvSceneSwitcher.item.nameNotAvailable",
|
"AdvSceneSwitcher.item.nameNotAvailable",
|
||||||
@@ -296,7 +315,7 @@ void ConnectionSelection::SetConnection(
|
|||||||
|
|
||||||
ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
|
ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
|
||||||
const Connection &settings)
|
const Connection &settings)
|
||||||
: ItemSettingsDialog(settings, switcher->connections,
|
: ItemSettingsDialog(settings, connections,
|
||||||
"AdvSceneSwitcher.connection.select",
|
"AdvSceneSwitcher.connection.select",
|
||||||
"AdvSceneSwitcher.connection.add",
|
"AdvSceneSwitcher.connection.add",
|
||||||
"AdvSceneSwitcher.item.nameNotAvailable", parent),
|
"AdvSceneSwitcher.item.nameNotAvailable", parent),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "item-selection-helpers.hpp"
|
#include "item-selection-helpers.hpp"
|
||||||
|
#include "websocket-helpers.hpp"
|
||||||
|
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
@@ -14,10 +15,11 @@
|
|||||||
#include <QGridLayout>
|
#include <QGridLayout>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <obs.hpp>
|
#include <obs.hpp>
|
||||||
#include <websocket-helpers.hpp>
|
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
|
void SetupConnectionManager();
|
||||||
|
|
||||||
class ConnectionSelection;
|
class ConnectionSelection;
|
||||||
class ConnectionSettingsDialog;
|
class ConnectionSettingsDialog;
|
||||||
|
|
||||||
@@ -69,6 +71,7 @@ Connection *GetConnectionByName(const std::string &);
|
|||||||
std::weak_ptr<Connection> GetWeakConnectionByName(const std::string &name);
|
std::weak_ptr<Connection> GetWeakConnectionByName(const std::string &name);
|
||||||
std::weak_ptr<Connection> GetWeakConnectionByQString(const QString &name);
|
std::weak_ptr<Connection> GetWeakConnectionByQString(const QString &name);
|
||||||
std::string GetWeakConnectionName(std::weak_ptr<Connection>);
|
std::string GetWeakConnectionName(std::weak_ptr<Connection>);
|
||||||
|
std::deque<std::shared_ptr<Item>> &GetConnections();
|
||||||
|
|
||||||
class ConnectionSettingsDialog : public ItemSettingsDialog {
|
class ConnectionSettingsDialog : public ItemSettingsDialog {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|||||||
@@ -50,6 +50,15 @@ CURLcode Curlhelper::Perform()
|
|||||||
return _perform(_curl);
|
return _perform(_curl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *Curlhelper::GetError(CURLcode code)
|
||||||
|
{
|
||||||
|
if (!_initialized) {
|
||||||
|
return (char *)"CURL initialization failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
return _error(code);
|
||||||
|
}
|
||||||
|
|
||||||
bool Curlhelper::LoadLib()
|
bool Curlhelper::LoadLib()
|
||||||
{
|
{
|
||||||
_lib = new QLibrary(curl_library_name, nullptr);
|
_lib = new QLibrary(curl_library_name, nullptr);
|
||||||
@@ -101,8 +110,10 @@ bool Curlhelper::Resolve()
|
|||||||
_slistAppend = (slistAppendFunction)_lib->resolve("curl_slist_append");
|
_slistAppend = (slistAppendFunction)_lib->resolve("curl_slist_append");
|
||||||
_perform = (performFunction)_lib->resolve("curl_easy_perform");
|
_perform = (performFunction)_lib->resolve("curl_easy_perform");
|
||||||
_cleanup = (cleanupFunction)_lib->resolve("curl_easy_cleanup");
|
_cleanup = (cleanupFunction)_lib->resolve("curl_easy_cleanup");
|
||||||
|
_error = (errorFunction)_lib->resolve("curl_easy_strerror");
|
||||||
|
|
||||||
if (_init && _setopt && _slistAppend && _perform && _cleanup) {
|
if (_init && _setopt && _slistAppend && _perform && _cleanup &&
|
||||||
|
_error) {
|
||||||
blog(LOG_INFO, "[adv-ss] curl loaded successfully");
|
blog(LOG_INFO, "[adv-ss] curl loaded successfully");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,22 +5,25 @@
|
|||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
typedef CURL *(*initFunction)(void);
|
typedef CURL *(*initFunction)(void);
|
||||||
typedef void (*cleanupFunction)(CURL *);
|
|
||||||
typedef CURLcode (*setOptFunction)(CURL *, CURLoption, ...);
|
typedef CURLcode (*setOptFunction)(CURL *, CURLoption, ...);
|
||||||
typedef struct curl_slist *(*slistAppendFunction)(struct curl_slist *list,
|
typedef struct curl_slist *(*slistAppendFunction)(struct curl_slist *list,
|
||||||
const char *string);
|
const char *string);
|
||||||
typedef CURLcode (*performFunction)(CURL *);
|
typedef CURLcode (*performFunction)(CURL *);
|
||||||
|
typedef void (*cleanupFunction)(CURL *);
|
||||||
|
typedef char *(*errorFunction)(CURLcode);
|
||||||
|
|
||||||
class Curlhelper {
|
class Curlhelper {
|
||||||
public:
|
public:
|
||||||
Curlhelper();
|
Curlhelper();
|
||||||
~Curlhelper();
|
~Curlhelper();
|
||||||
|
|
||||||
|
bool Initialized() { return _initialized; }
|
||||||
|
|
||||||
template<typename... Args> CURLcode SetOpt(CURLoption, Args...);
|
template<typename... Args> CURLcode SetOpt(CURLoption, Args...);
|
||||||
struct curl_slist *SlistAppend(struct curl_slist *list,
|
struct curl_slist *SlistAppend(struct curl_slist *list,
|
||||||
const char *string);
|
const char *string);
|
||||||
CURLcode Perform();
|
CURLcode Perform();
|
||||||
bool Initialized() { return _initialized; }
|
char *GetError(CURLcode code);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool LoadLib();
|
bool LoadLib();
|
||||||
@@ -31,6 +34,7 @@ private:
|
|||||||
slistAppendFunction _slistAppend = nullptr;
|
slistAppendFunction _slistAppend = nullptr;
|
||||||
performFunction _perform = nullptr;
|
performFunction _perform = nullptr;
|
||||||
cleanupFunction _cleanup = nullptr;
|
cleanupFunction _cleanup = nullptr;
|
||||||
|
errorFunction _error = nullptr;
|
||||||
CURL *_curl = nullptr;
|
CURL *_curl = nullptr;
|
||||||
QLibrary *_lib;
|
QLibrary *_lib;
|
||||||
bool _initialized = false;
|
bool _initialized = false;
|
||||||
|
|||||||
@@ -1,168 +1,12 @@
|
|||||||
#include "duration-control.hpp"
|
#include "duration-control.hpp"
|
||||||
#include "mouse-wheel-guard.hpp"
|
#include "mouse-wheel-guard.hpp"
|
||||||
#include "obs-module.h"
|
#include "obs-module-helper.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
Duration::Duration(double initialValueInSeconds) : _value(initialValueInSeconds)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void Duration::Save(obs_data_t *obj, const char *name) const
|
|
||||||
{
|
|
||||||
auto data = obs_data_create();
|
|
||||||
_value.Save(data, "value");
|
|
||||||
obs_data_set_int(data, "unit", static_cast<int>(_unit));
|
|
||||||
obs_data_set_int(data, "version", 1);
|
|
||||||
obs_data_set_obj(obj, name, data);
|
|
||||||
obs_data_release(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int durationUnitToMultiplier(Duration::Unit u)
|
|
||||||
{
|
|
||||||
switch (u) {
|
|
||||||
case Duration::Unit::SECONDS:
|
|
||||||
return 1;
|
|
||||||
case Duration::Unit::MINUTES:
|
|
||||||
return 60;
|
|
||||||
case Duration::Unit::HOURS:
|
|
||||||
return 3600;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Duration::Load(obs_data_t *obj, const char *name)
|
|
||||||
{
|
|
||||||
auto data = obs_data_get_obj(obj, name);
|
|
||||||
|
|
||||||
// TODO: remove this fallback
|
|
||||||
if (!data || !obs_data_has_user_value(data, "version") ||
|
|
||||||
obs_data_get_int(data, "version") != 1) {
|
|
||||||
bool usingDefaultArgs = strcmp("duration", name) == 0;
|
|
||||||
if (usingDefaultArgs) {
|
|
||||||
_value = obs_data_get_double(obj, "seconds");
|
|
||||||
}
|
|
||||||
if (_value.GetValue() == 0.0) {
|
|
||||||
_value = obs_data_get_double(obj, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (usingDefaultArgs) {
|
|
||||||
_unit = static_cast<Duration::Unit>(
|
|
||||||
obs_data_get_int(obj, "displayUnit"));
|
|
||||||
} else if (_value.GetValue() >= 86400) {
|
|
||||||
_unit = (_value.GetValue() / 60 >= 86400)
|
|
||||||
? Duration::Unit::HOURS
|
|
||||||
: Duration::Unit::MINUTES;
|
|
||||||
} else {
|
|
||||||
_unit = Duration::Unit::SECONDS;
|
|
||||||
}
|
|
||||||
|
|
||||||
_value = _value / durationUnitToMultiplier(_unit);
|
|
||||||
|
|
||||||
obs_data_release(data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_value.Load(data, "value");
|
|
||||||
_unit = static_cast<Duration::Unit>(obs_data_get_int(data, "unit"));
|
|
||||||
obs_data_release(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Duration::DurationReached()
|
|
||||||
{
|
|
||||||
if (IsReset()) {
|
|
||||||
_startTime = std::chrono::high_resolution_clock::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto runTime = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
std::chrono::high_resolution_clock::now() - _startTime);
|
|
||||||
return runTime.count() >= Milliseconds();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Duration::IsReset() const
|
|
||||||
{
|
|
||||||
return _startTime.time_since_epoch().count() == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
double Duration::Seconds() const
|
|
||||||
{
|
|
||||||
return _value.GetValue() * durationUnitToMultiplier(_unit);
|
|
||||||
}
|
|
||||||
|
|
||||||
double Duration::Milliseconds() const
|
|
||||||
{
|
|
||||||
return Seconds() * 1000.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
double Duration::TimeRemaining() const
|
|
||||||
{
|
|
||||||
if (IsReset()) {
|
|
||||||
return Seconds();
|
|
||||||
}
|
|
||||||
auto runTime = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
std::chrono::high_resolution_clock::now() - _startTime);
|
|
||||||
|
|
||||||
if (runTime.count() >= Milliseconds()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return (Milliseconds() - runTime.count()) / 1000.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Duration::SetTimeRemaining(double remaining)
|
|
||||||
{
|
|
||||||
long long msPassed = (Seconds() - remaining) * 1000;
|
|
||||||
_startTime = std::chrono::high_resolution_clock::now() -
|
|
||||||
std::chrono::milliseconds(msPassed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Duration::Reset()
|
|
||||||
{
|
|
||||||
_startTime = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string durationUnitToString(Duration::Unit u)
|
|
||||||
{
|
|
||||||
switch (u) {
|
|
||||||
case Duration::Unit::SECONDS:
|
|
||||||
return obs_module_text("AdvSceneSwitcher.unit.secends");
|
|
||||||
case Duration::Unit::MINUTES:
|
|
||||||
return obs_module_text("AdvSceneSwitcher.unit.minutes");
|
|
||||||
case Duration::Unit::HOURS:
|
|
||||||
return obs_module_text("AdvSceneSwitcher.unit.hours");
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Duration::ToString() const
|
|
||||||
{
|
|
||||||
std::ostringstream ss;
|
|
||||||
ss << std::fixed << std::setprecision(2) << _value.GetValue() << " "
|
|
||||||
<< durationUnitToString(_unit);
|
|
||||||
if (!_value.IsFixedType()) {
|
|
||||||
ss << " [" << GetWeakVariableName(_value.GetVariable()) << "]";
|
|
||||||
}
|
|
||||||
return ss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Duration::SetUnit(Unit u)
|
|
||||||
{
|
|
||||||
double prevMultiplier = durationUnitToMultiplier(_unit);
|
|
||||||
double newMultiplier = durationUnitToMultiplier(u);
|
|
||||||
_unit = u;
|
|
||||||
_value = _value * (prevMultiplier / newMultiplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void populateUnits(QComboBox *list)
|
static void populateUnits(QComboBox *list)
|
||||||
{
|
{
|
||||||
list->addItem(obs_module_text("AdvSceneSwitcher.unit.secends"));
|
list->addItem(obs_module_text("AdvSceneSwitcher.unit.secends"));
|
||||||
@@ -214,13 +58,28 @@ void DurationSelection::_DurationChanged(const NumberVariable<double> &value)
|
|||||||
emit DurationChanged(_current);
|
emit DurationChanged(_current);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int durationUnitToMultiplier(Duration::Unit u)
|
||||||
|
{
|
||||||
|
switch (u) {
|
||||||
|
case Duration::Unit::SECONDS:
|
||||||
|
return 1;
|
||||||
|
case Duration::Unit::MINUTES:
|
||||||
|
return 60;
|
||||||
|
case Duration::Unit::HOURS:
|
||||||
|
return 3600;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void DurationSelection::_UnitChanged(int idx)
|
void DurationSelection::_UnitChanged(int idx)
|
||||||
{
|
{
|
||||||
Duration::Unit unit = static_cast<Duration::Unit>(idx);
|
Duration::Unit unit = static_cast<Duration::Unit>(idx);
|
||||||
double prevMultiplier = durationUnitToMultiplier(_current._unit);
|
double prevMultiplier = durationUnitToMultiplier(_current._unit);
|
||||||
double newMultiplier = durationUnitToMultiplier(unit);
|
double newMultiplier = durationUnitToMultiplier(unit);
|
||||||
_current._unit = unit;
|
_current._unit = unit;
|
||||||
const QSignalBlocker b(_duration);
|
|
||||||
_duration->SetFixedValue(_duration->Value() *
|
_duration->SetFixedValue(_duration->Value() *
|
||||||
(prevMultiplier / newMultiplier));
|
(prevMultiplier / newMultiplier));
|
||||||
emit DurationChanged(_current);
|
emit DurationChanged(_current);
|
||||||
|
|||||||
@@ -1,48 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "duration.hpp"
|
||||||
#include "variable-spinbox.hpp"
|
#include "variable-spinbox.hpp"
|
||||||
#include "obs-data.h"
|
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QDoubleSpinBox>
|
#include <QDoubleSpinBox>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
class Duration {
|
|
||||||
public:
|
|
||||||
Duration() = default;
|
|
||||||
Duration(double initialValueInSeconds);
|
|
||||||
|
|
||||||
void Save(obs_data_t *obj, const char *name = "duration") const;
|
|
||||||
void Load(obs_data_t *obj, const char *name = "duration");
|
|
||||||
|
|
||||||
bool DurationReached();
|
|
||||||
bool IsReset() const;
|
|
||||||
double Seconds() const;
|
|
||||||
double Milliseconds() const;
|
|
||||||
double TimeRemaining() const;
|
|
||||||
void SetTimeRemaining(double);
|
|
||||||
void Reset();
|
|
||||||
std::string ToString() const;
|
|
||||||
|
|
||||||
enum class Unit {
|
|
||||||
SECONDS,
|
|
||||||
MINUTES,
|
|
||||||
HOURS,
|
|
||||||
};
|
|
||||||
Unit GetUnit() const { return _unit; }
|
|
||||||
void SetUnit(Unit u);
|
|
||||||
|
|
||||||
private:
|
|
||||||
NumberVariable<double> _value = 0.;
|
|
||||||
Unit _unit = Unit::SECONDS;
|
|
||||||
std::chrono::high_resolution_clock::time_point _startTime;
|
|
||||||
|
|
||||||
friend class DurationSelection;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DurationSelection : public QWidget {
|
class DurationSelection : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
|||||||
169
src/utils/duration.cpp
Normal file
169
src/utils/duration.cpp
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#include "duration.hpp"
|
||||||
|
#include "obs-module-helper.hpp"
|
||||||
|
#include "utility.hpp"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
Duration::Duration(double initialValueInSeconds) : _value(initialValueInSeconds)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Duration::Save(obs_data_t *obj, const char *name) const
|
||||||
|
{
|
||||||
|
auto data = obs_data_create();
|
||||||
|
_value.Save(data, "value");
|
||||||
|
obs_data_set_int(data, "unit", static_cast<int>(_unit));
|
||||||
|
obs_data_set_int(data, "version", 1);
|
||||||
|
obs_data_set_obj(obj, name, data);
|
||||||
|
obs_data_release(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int durationUnitToMultiplier(Duration::Unit u);
|
||||||
|
|
||||||
|
void Duration::Load(obs_data_t *obj, const char *name)
|
||||||
|
{
|
||||||
|
auto data = obs_data_get_obj(obj, name);
|
||||||
|
|
||||||
|
// TODO: remove this fallback
|
||||||
|
if (!data || !obs_data_has_user_value(data, "version") ||
|
||||||
|
obs_data_get_int(data, "version") != 1) {
|
||||||
|
bool usingDefaultArgs = strcmp("duration", name) == 0;
|
||||||
|
if (usingDefaultArgs) {
|
||||||
|
_value = obs_data_get_double(obj, "seconds");
|
||||||
|
}
|
||||||
|
if (_value.GetValue() == 0.0) {
|
||||||
|
_value = obs_data_get_double(obj, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usingDefaultArgs) {
|
||||||
|
_unit = static_cast<Duration::Unit>(
|
||||||
|
obs_data_get_int(obj, "displayUnit"));
|
||||||
|
} else if (_value.GetValue() >= 86400) {
|
||||||
|
_unit = (_value.GetValue() / 60 >= 86400)
|
||||||
|
? Duration::Unit::HOURS
|
||||||
|
: Duration::Unit::MINUTES;
|
||||||
|
} else {
|
||||||
|
_unit = Duration::Unit::SECONDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
_value = _value / durationUnitToMultiplier(_unit);
|
||||||
|
|
||||||
|
obs_data_release(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_value.Load(data, "value");
|
||||||
|
_unit = static_cast<Duration::Unit>(obs_data_get_int(data, "unit"));
|
||||||
|
obs_data_release(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Duration::DurationReached()
|
||||||
|
{
|
||||||
|
if (IsReset()) {
|
||||||
|
_startTime = std::chrono::high_resolution_clock::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto runTime = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::high_resolution_clock::now() - _startTime);
|
||||||
|
return runTime.count() >= Milliseconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Duration::IsReset() const
|
||||||
|
{
|
||||||
|
return _startTime.time_since_epoch().count() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Duration::Seconds() const
|
||||||
|
{
|
||||||
|
return _value.GetValue() * durationUnitToMultiplier(_unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
double Duration::Milliseconds() const
|
||||||
|
{
|
||||||
|
return Seconds() * 1000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Duration::TimeRemaining() const
|
||||||
|
{
|
||||||
|
if (IsReset()) {
|
||||||
|
return Seconds();
|
||||||
|
}
|
||||||
|
auto runTime = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::high_resolution_clock::now() - _startTime);
|
||||||
|
|
||||||
|
if (runTime.count() >= Milliseconds()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (Milliseconds() - runTime.count()) / 1000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Duration::SetTimeRemaining(double remaining)
|
||||||
|
{
|
||||||
|
long long msPassed = (Seconds() - remaining) * 1000;
|
||||||
|
_startTime = std::chrono::high_resolution_clock::now() -
|
||||||
|
std::chrono::milliseconds(msPassed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Duration::Reset()
|
||||||
|
{
|
||||||
|
_startTime = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string durationUnitToString(Duration::Unit u)
|
||||||
|
{
|
||||||
|
switch (u) {
|
||||||
|
case Duration::Unit::SECONDS:
|
||||||
|
return obs_module_text("AdvSceneSwitcher.unit.secends");
|
||||||
|
case Duration::Unit::MINUTES:
|
||||||
|
return obs_module_text("AdvSceneSwitcher.unit.minutes");
|
||||||
|
case Duration::Unit::HOURS:
|
||||||
|
return obs_module_text("AdvSceneSwitcher.unit.hours");
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Duration::ToString() const
|
||||||
|
{
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << std::fixed << std::setprecision(2) << _value.GetValue() << " "
|
||||||
|
<< durationUnitToString(_unit);
|
||||||
|
if (!_value.IsFixedType()) {
|
||||||
|
ss << " [" << GetWeakVariableName(_value.GetVariable()) << "]";
|
||||||
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove the code below
|
||||||
|
// Only used for backwards compatibility
|
||||||
|
|
||||||
|
static int durationUnitToMultiplier(Duration::Unit u)
|
||||||
|
{
|
||||||
|
switch (u) {
|
||||||
|
case Duration::Unit::SECONDS:
|
||||||
|
return 1;
|
||||||
|
case Duration::Unit::MINUTES:
|
||||||
|
return 60;
|
||||||
|
case Duration::Unit::HOURS:
|
||||||
|
return 3600;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Duration::SetUnit(Unit u)
|
||||||
|
{
|
||||||
|
double prevMultiplier = durationUnitToMultiplier(_unit);
|
||||||
|
double newMultiplier = durationUnitToMultiplier(u);
|
||||||
|
_unit = u;
|
||||||
|
_value = _value * (prevMultiplier / newMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
45
src/utils/duration.hpp
Normal file
45
src/utils/duration.hpp
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "variable-number.hpp"
|
||||||
|
#include "obs-data.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
class Duration {
|
||||||
|
public:
|
||||||
|
Duration() = default;
|
||||||
|
Duration(double initialValueInSeconds);
|
||||||
|
|
||||||
|
void Save(obs_data_t *obj, const char *name = "duration") const;
|
||||||
|
void Load(obs_data_t *obj, const char *name = "duration");
|
||||||
|
|
||||||
|
bool DurationReached();
|
||||||
|
bool IsReset() const;
|
||||||
|
double Seconds() const;
|
||||||
|
double Milliseconds() const;
|
||||||
|
double TimeRemaining() const;
|
||||||
|
void SetTimeRemaining(double);
|
||||||
|
void Reset();
|
||||||
|
std::string ToString() const;
|
||||||
|
|
||||||
|
enum class Unit {
|
||||||
|
SECONDS,
|
||||||
|
MINUTES,
|
||||||
|
HOURS,
|
||||||
|
};
|
||||||
|
Unit GetUnit() const { return _unit; }
|
||||||
|
|
||||||
|
// TODO: Remove
|
||||||
|
// Only use this function if you intend to convert old settings formats
|
||||||
|
void SetUnit(Unit u);
|
||||||
|
|
||||||
|
private:
|
||||||
|
NumberVariable<double> _value = 0.;
|
||||||
|
Unit _unit = Unit::SECONDS;
|
||||||
|
std::chrono::high_resolution_clock::time_point _startTime;
|
||||||
|
|
||||||
|
friend class DurationSelection;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
@@ -10,19 +10,26 @@ namespace advss {
|
|||||||
|
|
||||||
MacroRunButton::MacroRunButton(QWidget *parent) : QPushButton(parent)
|
MacroRunButton::MacroRunButton(QWidget *parent) : QPushButton(parent)
|
||||||
{
|
{
|
||||||
if (window()) {
|
installEventFilter(this);
|
||||||
window()->installEventFilter(this);
|
auto parentWindow = window();
|
||||||
|
if (parentWindow) {
|
||||||
|
parentWindow->installEventFilter(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setToolTip(obs_module_text("AdvSceneSwitcher.macroTab.run.tooltip"));
|
||||||
|
|
||||||
QWidget::connect(this, SIGNAL(pressed()), this, SLOT(Pressed()));
|
QWidget::connect(this, SIGNAL(pressed()), this, SLOT(Pressed()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroRunButton::SetMacroTree(MacroTree *macros)
|
void MacroRunButton::SetMacroTree(MacroTree *macros)
|
||||||
{
|
{
|
||||||
_macros = macros;
|
_macros = macros;
|
||||||
|
|
||||||
QWidget::connect(macros, SIGNAL(MacroSelectionChanged()), this,
|
QWidget::connect(macros, SIGNAL(MacroSelectionChanged()), this,
|
||||||
SLOT(MacroSelectionChanged()));
|
SLOT(MacroSelectionChanged()));
|
||||||
QWidget::connect(&_timer, &QTimer::timeout, this,
|
QWidget::connect(&_timer, &QTimer::timeout, this,
|
||||||
[this]() { MacroSelectionChanged(); });
|
[this]() { MacroSelectionChanged(); });
|
||||||
|
|
||||||
_timer.start(1000);
|
_timer.start(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,35 +40,53 @@ void MacroRunButton::MacroSelectionChanged()
|
|||||||
_macroHasElseActions = false;
|
_macroHasElseActions = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_macroHasElseActions = macro->ElseActions().size() > 0;
|
_macroHasElseActions = macro->ElseActions().size() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroRunButton::eventFilter(QObject *obj, QEvent *event)
|
bool MacroRunButton::eventFilter(QObject *obj, QEvent *event)
|
||||||
{
|
{
|
||||||
if (!_macroHasElseActions) {
|
if (!_macroHasElseActions) {
|
||||||
setText(obs_module_text("AdvSceneSwitcher.macroTab.run"));
|
DeactivateElseState();
|
||||||
_runElseActionsKeyHeld = false;
|
|
||||||
return QPushButton::eventFilter(obj, event);
|
return QPushButton::eventFilter(obj, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event->type() == QEvent::KeyPress) {
|
auto eventType = event->type();
|
||||||
|
if (eventType == QEvent::KeyPress) {
|
||||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||||
if (keyEvent->key() == Qt::Key_Control) {
|
if (keyEvent->key() == Qt::Key_Shift) {
|
||||||
setText(obs_module_text(
|
if (underMouse()) {
|
||||||
"AdvSceneSwitcher.macroTab.runElse"));
|
ActivateElseState();
|
||||||
_runElseActionsKeyHeld = true;
|
}
|
||||||
|
_shiftHeld = true;
|
||||||
}
|
}
|
||||||
} else if (event->type() == QEvent::KeyRelease) {
|
} else if (eventType == QEvent::KeyRelease) {
|
||||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||||
if (keyEvent->key() == Qt::Key_Control) {
|
if (keyEvent->key() == Qt::Key_Shift) {
|
||||||
setText(obs_module_text(
|
DeactivateElseState();
|
||||||
"AdvSceneSwitcher.macroTab.run"));
|
_shiftHeld = false;
|
||||||
_runElseActionsKeyHeld = false;
|
|
||||||
}
|
}
|
||||||
|
} else if (_shiftHeld && obj == this && eventType == QEvent::Enter) {
|
||||||
|
ActivateElseState();
|
||||||
|
} else if (obj == this && eventType == QEvent::Leave) {
|
||||||
|
DeactivateElseState();
|
||||||
}
|
}
|
||||||
|
|
||||||
return QPushButton::eventFilter(obj, event);
|
return QPushButton::eventFilter(obj, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroRunButton::ActivateElseState()
|
||||||
|
{
|
||||||
|
setText(obs_module_text("AdvSceneSwitcher.macroTab.runElse"));
|
||||||
|
_elseStateActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MacroRunButton::DeactivateElseState()
|
||||||
|
{
|
||||||
|
setText(obs_module_text("AdvSceneSwitcher.macroTab.run"));
|
||||||
|
_elseStateActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
void MacroRunButton::Pressed()
|
void MacroRunButton::Pressed()
|
||||||
{
|
{
|
||||||
auto macro = _macros->GetCurrentMacro();
|
auto macro = _macros->GetCurrentMacro();
|
||||||
@@ -69,9 +94,9 @@ void MacroRunButton::Pressed()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ret = _runElseActionsKeyHeld
|
bool ret = _elseStateActive ? macro->PerformActions(false, true, true)
|
||||||
? macro->PerformActions(false, true, true)
|
: macro->PerformActions(true, true, true);
|
||||||
: macro->PerformActions(true, true, true);
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
QString err =
|
QString err =
|
||||||
obs_module_text("AdvSceneSwitcher.macroTab.runFail");
|
obs_module_text("AdvSceneSwitcher.macroTab.runFail");
|
||||||
|
|||||||
@@ -20,9 +20,13 @@ private slots:
|
|||||||
void Pressed();
|
void Pressed();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MacroTree *_macros = nullptr;
|
void ActivateElseState();
|
||||||
|
void DeactivateElseState();
|
||||||
|
|
||||||
bool _macroHasElseActions = false;
|
bool _macroHasElseActions = false;
|
||||||
bool _runElseActionsKeyHeld = false;
|
bool _elseStateActive = false;
|
||||||
|
bool _shiftHeld = false;
|
||||||
|
MacroTree *_macros = nullptr;
|
||||||
QTimer _timer;
|
QTimer _timer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,35 @@
|
|||||||
#include "math-helpers.hpp"
|
#include "math-helpers.hpp"
|
||||||
|
#include "obs-module-helper.hpp"
|
||||||
|
|
||||||
#include <exprtk.hpp>
|
#include <exprtk.hpp>
|
||||||
|
#include <random>
|
||||||
#ifdef UNIT_TEST
|
|
||||||
const char *obs_module_text(const char *text)
|
|
||||||
{
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#include <obs-module.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef exprtk::expression<double> expression_t;
|
|
||||||
typedef exprtk::parser<double> parser_t;
|
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
std::variant<double, std::string> EvalMathExpression(const std::string &expr)
|
std::variant<double, std::string> EvalMathExpression(const std::string &expr)
|
||||||
{
|
{
|
||||||
expression_t expression;
|
static bool setupDone = false;
|
||||||
|
static exprtk::symbol_table<double> symbolTable;
|
||||||
|
static std::random_device rd;
|
||||||
|
static std::mt19937 gen(rd());
|
||||||
|
static std::uniform_real_distribution<double> dis(0.0, 1.0);
|
||||||
|
static auto randomFunc = []() { return dis(gen); };
|
||||||
|
|
||||||
parser_t parser;
|
if (!setupDone) {
|
||||||
|
symbolTable.add_function("random", randomFunc);
|
||||||
|
setupDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
exprtk::expression<double> expression;
|
||||||
|
expression.register_symbol_table(symbolTable);
|
||||||
|
exprtk::parser<double> parser;
|
||||||
|
|
||||||
if (parser.compile(expr, expression)) {
|
if (parser.compile(expr, expression)) {
|
||||||
return expression.value();
|
return expression.value();
|
||||||
}
|
}
|
||||||
return std::string(obs_module_text(
|
return std::string(obs_module_text(
|
||||||
"AdvSceneSwitcher.math.expressionFail")) +
|
"AdvSceneSwitcher.math.expressionFail")) +
|
||||||
" \"" + expr;
|
" \"" + expr + "\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsValidNumber(const std::string &str)
|
bool IsValidNumber(const std::string &str)
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#include "obs-module-helper.hpp"
|
#include "obs-module-helper.hpp"
|
||||||
|
#ifndef UNIT_TEST
|
||||||
#include "switcher-data.hpp"
|
#include "switcher-data.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef UNIT_TEST
|
||||||
const char *obs_module_text(const char *text)
|
const char *obs_module_text(const char *text)
|
||||||
{
|
{
|
||||||
if (!advss::switcher) {
|
if (!advss::switcher) {
|
||||||
@@ -16,3 +19,15 @@ obs_module_t *obs_current_module()
|
|||||||
}
|
}
|
||||||
return advss::switcher->GetModule();
|
return advss::switcher->GetModule();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
const char *obs_module_text(const char *text)
|
||||||
|
{
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
obs_module_t *obs_current_module()
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#ifndef UNIT_TEST
|
||||||
#include <obs-module.h>
|
#include <obs-module.h>
|
||||||
|
#else
|
||||||
|
typedef struct obs_module obs_module_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
const char *obs_module_text(const char *text);
|
const char *obs_module_text(const char *text);
|
||||||
obs_module_t *obs_current_module();
|
obs_module_t *obs_current_module();
|
||||||
|
|||||||
61
src/utils/plugin-state-helper.cpp
Normal file
61
src/utils/plugin-state-helper.cpp
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#include "plugin-state-helper.hpp"
|
||||||
|
#include "switcher-data.hpp"
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
void LoadPluginSettings(obs_data_t *obj)
|
||||||
|
{
|
||||||
|
GetSwitcher()->LoadSettings(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddSaveStep(std::function<void(obs_data_t *)> step)
|
||||||
|
{
|
||||||
|
GetSwitcher()->AddSaveStep(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddLoadStep(std::function<void(obs_data_t *)> step)
|
||||||
|
{
|
||||||
|
GetSwitcher()->AddLoadStep(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddPostLoadStep(std::function<void()> step)
|
||||||
|
{
|
||||||
|
GetSwitcher()->AddPostLoadStep(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddIntervalResetStep(std::function<void()> step)
|
||||||
|
{
|
||||||
|
GetSwitcher()->AddIntervalResetStep(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StopPlugin()
|
||||||
|
{
|
||||||
|
GetSwitcher()->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartPlugin()
|
||||||
|
{
|
||||||
|
GetSwitcher()->Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetPluginNoMatchBehavior(NoMatchBehavior behavior)
|
||||||
|
{
|
||||||
|
GetSwitcher()->switchIfNotMatching = behavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetNoMatchScene(const OBSWeakSource &scene)
|
||||||
|
{
|
||||||
|
GetSwitcher()->nonMatchingScene = scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
NoMatchBehavior GetPluginNoMatchBehavior()
|
||||||
|
{
|
||||||
|
return GetSwitcher()->switchIfNotMatching;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SettingsWindowIsOpened()
|
||||||
|
{
|
||||||
|
return GetSwitcher()->settingsWindowOpened;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
24
src/utils/plugin-state-helper.hpp
Normal file
24
src/utils/plugin-state-helper.hpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <functional>
|
||||||
|
#include <obs.hpp>
|
||||||
|
|
||||||
|
namespace advss {
|
||||||
|
|
||||||
|
void LoadPluginSettings(obs_data_t *);
|
||||||
|
|
||||||
|
void AddSaveStep(std::function<void(obs_data_t *)>);
|
||||||
|
void AddLoadStep(std::function<void(obs_data_t *)>);
|
||||||
|
void AddPostLoadStep(std::function<void()>);
|
||||||
|
void AddIntervalResetStep(std::function<void()>);
|
||||||
|
|
||||||
|
void StopPlugin();
|
||||||
|
void StartPlugin();
|
||||||
|
|
||||||
|
enum class NoMatchBehavior { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 };
|
||||||
|
void SetPluginNoMatchBehavior(NoMatchBehavior);
|
||||||
|
NoMatchBehavior GetPluginNoMatchBehavior();
|
||||||
|
void SetNoMatchScene(const OBSWeakSource &);
|
||||||
|
|
||||||
|
bool SettingsWindowIsOpened();
|
||||||
|
|
||||||
|
} // namespace advss
|
||||||
@@ -51,6 +51,23 @@ RegexConfig::GetRegularExpression(const std::string &expr) const
|
|||||||
return GetRegularExpression(QString::fromStdString(expr));
|
return GetRegularExpression(QString::fromStdString(expr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RegexConfig::Matches(const QString &text, const QString &expression) const
|
||||||
|
{
|
||||||
|
auto regex = GetRegularExpression(expression);
|
||||||
|
if (!regex.isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto match = regex.match(text);
|
||||||
|
return match.hasMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RegexConfig::Matches(const std::string &text,
|
||||||
|
const std::string &expression) const
|
||||||
|
{
|
||||||
|
return Matches(QString::fromStdString(text),
|
||||||
|
QString::fromStdString(expression));
|
||||||
|
}
|
||||||
|
|
||||||
RegexConfig RegexConfig::PartialMatchRegexConfig()
|
RegexConfig RegexConfig::PartialMatchRegexConfig()
|
||||||
{
|
{
|
||||||
RegexConfig conf;
|
RegexConfig conf;
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ public:
|
|||||||
};
|
};
|
||||||
QRegularExpression GetRegularExpression(const QString &) const;
|
QRegularExpression GetRegularExpression(const QString &) const;
|
||||||
QRegularExpression GetRegularExpression(const std::string &) const;
|
QRegularExpression GetRegularExpression(const std::string &) const;
|
||||||
|
bool Matches(const QString &text, const QString &expression) const;
|
||||||
|
bool Matches(const std::string &text,
|
||||||
|
const std::string &expression) const;
|
||||||
|
|
||||||
static RegexConfig PartialMatchRegexConfig();
|
static RegexConfig PartialMatchRegexConfig();
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ namespace advss {
|
|||||||
|
|
||||||
static void ScreenshotTick(void *param, float);
|
static void ScreenshotTick(void *param, float);
|
||||||
|
|
||||||
ScreenshotHelper::ScreenshotHelper(obs_source_t *source, bool blocking,
|
ScreenshotHelper::ScreenshotHelper(obs_source_t *source, const QRect &subarea,
|
||||||
int timeout, bool saveToFile,
|
bool blocking, int timeout, bool saveToFile,
|
||||||
std::string path)
|
std::string path)
|
||||||
: weakSource(OBSGetWeakRef(source)),
|
: weakSource(OBSGetWeakRef(source)),
|
||||||
|
_subarea(subarea),
|
||||||
_blocking(blocking),
|
_blocking(blocking),
|
||||||
_saveToFile(saveToFile),
|
_saveToFile(saveToFile),
|
||||||
_path(path)
|
_path(path)
|
||||||
@@ -62,7 +63,12 @@ void ScreenshotHelper::Screenshot()
|
|||||||
cy = ovi.base_height;
|
cy = ovi.base_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cx || !cy) {
|
QRect renderArea(0, 0, cx, cy);
|
||||||
|
if (!_subarea.isEmpty()) {
|
||||||
|
renderArea &= _subarea;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (renderArea.isEmpty()) {
|
||||||
vblog(LOG_WARNING,
|
vblog(LOG_WARNING,
|
||||||
"Cannot screenshot \"%s\", invalid target size",
|
"Cannot screenshot \"%s\", invalid target size",
|
||||||
obs_source_get_name(source));
|
obs_source_get_name(source));
|
||||||
@@ -71,16 +77,24 @@ void ScreenshotHelper::Screenshot()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx = renderArea.width();
|
||||||
|
cy = renderArea.height();
|
||||||
|
|
||||||
texrender = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
|
texrender = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
|
||||||
stagesurf = gs_stagesurface_create(cx, cy, GS_RGBA);
|
stagesurf = gs_stagesurface_create(renderArea.width(),
|
||||||
|
renderArea.height(), GS_RGBA);
|
||||||
|
|
||||||
gs_texrender_reset(texrender);
|
gs_texrender_reset(texrender);
|
||||||
if (gs_texrender_begin(texrender, cx, cy)) {
|
if (gs_texrender_begin(texrender, renderArea.width(),
|
||||||
|
renderArea.height())) {
|
||||||
vec4 zero;
|
vec4 zero;
|
||||||
vec4_zero(&zero);
|
vec4_zero(&zero);
|
||||||
|
|
||||||
gs_clear(GS_CLEAR_COLOR, &zero, 0.0f, 0);
|
gs_clear(GS_CLEAR_COLOR, &zero, 0.0f, 0);
|
||||||
gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f);
|
gs_ortho((float)(renderArea.left()),
|
||||||
|
(float)(renderArea.right() + 1),
|
||||||
|
(float)(renderArea.top()),
|
||||||
|
(float)(renderArea.bottom() + 1), -100.0f, 100.0f);
|
||||||
|
|
||||||
gs_blend_state_push();
|
gs_blend_state_push();
|
||||||
gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
|
gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ namespace advss {
|
|||||||
class ScreenshotHelper {
|
class ScreenshotHelper {
|
||||||
public:
|
public:
|
||||||
ScreenshotHelper() = default;
|
ScreenshotHelper() = default;
|
||||||
ScreenshotHelper(obs_source_t *source, bool blocking = false,
|
ScreenshotHelper(obs_source_t *source, const QRect &subarea = QRect(),
|
||||||
int timeout = 1000, bool saveToFile = false,
|
bool blocking = false, int timeout = 1000,
|
||||||
std::string path = "");
|
bool saveToFile = false, std::string path = "");
|
||||||
ScreenshotHelper &operator=(const ScreenshotHelper &) = delete;
|
ScreenshotHelper &operator=(const ScreenshotHelper &) = delete;
|
||||||
ScreenshotHelper(const ScreenshotHelper &) = delete;
|
ScreenshotHelper(const ScreenshotHelper &) = delete;
|
||||||
~ScreenshotHelper();
|
~ScreenshotHelper();
|
||||||
@@ -39,6 +39,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::atomic_bool _initDone = false;
|
std::atomic_bool _initDone = false;
|
||||||
|
QRect _subarea = QRect();
|
||||||
bool _blocking = false;
|
bool _blocking = false;
|
||||||
std::thread _saveThread;
|
std::thread _saveThread;
|
||||||
bool _saveToFile = false;
|
bool _saveToFile = false;
|
||||||
|
|||||||
@@ -50,11 +50,17 @@ std::vector<SourceSetting> GetSoruceSettings(obs_source_t *source)
|
|||||||
if (!it) {
|
if (!it) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
SourceSetting setting(
|
auto name = obs_property_name(it);
|
||||||
obs_property_name(it), obs_property_description(it),
|
if (!name) {
|
||||||
obs_property_long_description(it)
|
continue;
|
||||||
? obs_property_long_description(it)
|
}
|
||||||
: "");
|
auto description = obs_property_description(it);
|
||||||
|
if (!description) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto longDescription = obs_property_long_description(it);
|
||||||
|
SourceSetting setting(name, description,
|
||||||
|
longDescription ? longDescription : "");
|
||||||
settings.emplace_back(setting);
|
settings.emplace_back(setting);
|
||||||
} while (obs_property_next(&it));
|
} while (obs_property_next(&it));
|
||||||
obs_properties_destroy(properties);
|
obs_properties_destroy(properties);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#include "advanced-scene-switcher.hpp"
|
#include "advanced-scene-switcher.hpp"
|
||||||
#include "macro.hpp"
|
#include "macro.hpp"
|
||||||
#include "macro-segment.hpp"
|
#include "macro-segment.hpp"
|
||||||
#include "switcher-data.hpp"
|
#include "plugin-state-helper.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
|
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
@@ -170,7 +170,7 @@ void TempVariableRef::Load(obs_data_t *obj, Macro *macro, const char *name)
|
|||||||
_id = obs_data_get_string(data, "id");
|
_id = obs_data_get_string(data, "id");
|
||||||
const auto type =
|
const auto type =
|
||||||
static_cast<SegmentType>(obs_data_get_int(data, "type"));
|
static_cast<SegmentType>(obs_data_get_int(data, "type"));
|
||||||
switcher->AddPostLoadStep([this, idx, type, macro]() {
|
AddPostLoadStep([this, idx, type, macro]() {
|
||||||
this->PostLoad(idx, type, macro);
|
this->PostLoad(idx, type, macro);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -427,7 +427,7 @@ void NotifyUIAboutTempVarChange()
|
|||||||
obs_queue_task(
|
obs_queue_task(
|
||||||
OBS_TASK_UI,
|
OBS_TASK_UI,
|
||||||
[](void *) {
|
[](void *) {
|
||||||
if (!GetSwitcher()->settingsWindowOpened) {
|
if (!SettingsWindowIsOpened()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AdvSceneSwitcher::window->SegmentTempVarsChanged();
|
AdvSceneSwitcher::window->SegmentTempVarsChanged();
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "websocket-helpers.hpp"
|
#include "websocket-helpers.hpp"
|
||||||
#include "connection-manager.hpp"
|
#include "connection-manager.hpp"
|
||||||
#include "switcher-data.hpp"
|
#include "log-helper.hpp"
|
||||||
|
#include "plugin-state-helper.hpp"
|
||||||
|
#include "sync-helper.hpp"
|
||||||
|
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include <obs-websocket-api.h>
|
#include <obs-websocket-api.h>
|
||||||
@@ -13,16 +15,38 @@ using websocketpp::lib::bind;
|
|||||||
|
|
||||||
#define RPC_VERSION 1
|
#define RPC_VERSION 1
|
||||||
|
|
||||||
|
constexpr char VendorName[] = "AdvancedSceneSwitcher";
|
||||||
|
constexpr char VendorRequest[] = "AdvancedSceneSwitcherMessage";
|
||||||
|
constexpr char VendorEvent[] = "AdvancedSceneSwitcherEvent";
|
||||||
obs_websocket_vendor vendor;
|
obs_websocket_vendor vendor;
|
||||||
|
|
||||||
void ClearWebsocketMessages()
|
static void clearWebsocketMessages();
|
||||||
|
static std::vector<std::string> websocketMessages;
|
||||||
|
|
||||||
|
void SetupWebsocketHelpers()
|
||||||
{
|
{
|
||||||
switcher->websocketMessages.clear();
|
static bool done = false;
|
||||||
for (auto &connection : switcher->connections) {
|
if (done) {
|
||||||
Connection *c = dynamic_cast<Connection *>(connection.get());
|
return;
|
||||||
if (c) {
|
}
|
||||||
c->Events().clear();
|
AddIntervalResetStep(clearWebsocketMessages);
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> &GetWebsocketMessages()
|
||||||
|
{
|
||||||
|
return websocketMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearWebsocketMessages()
|
||||||
|
{
|
||||||
|
websocketMessages.clear();
|
||||||
|
for (auto &connection : GetConnections()) {
|
||||||
|
auto c = dynamic_cast<Connection *>(connection.get());
|
||||||
|
if (!c) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
c->Events().clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,8 +68,8 @@ static void receiveWebsocketMessage(obs_data_t *request_data, obs_data_t *,
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto msg = obs_data_get_string(request_data, "message");
|
auto msg = obs_data_get_string(request_data, "message");
|
||||||
std::lock_guard<std::mutex> lock(switcher->m);
|
auto lock = LockContext();
|
||||||
switcher->websocketMessages.emplace_back(msg);
|
websocketMessages.emplace_back(msg);
|
||||||
vblog(LOG_INFO, "received message: %s", msg);
|
vblog(LOG_INFO, "received message: %s", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +318,7 @@ void WSConnection::HandleEvent(obs_data_t *msg)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto eventDataNested = obs_data_get_obj(eventData, "eventData");
|
auto eventDataNested = obs_data_get_obj(eventData, "eventData");
|
||||||
std::lock_guard<std::mutex> lock(switcher->m);
|
auto lock = LockContext();
|
||||||
_messages.emplace_back(obs_data_get_string(eventDataNested, "message"));
|
_messages.emplace_back(obs_data_get_string(eventDataNested, "message"));
|
||||||
vblog(LOG_INFO, "received event msg \"%s\"",
|
vblog(LOG_INFO, "received event msg \"%s\"",
|
||||||
obs_data_get_string(eventDataNested, "message"));
|
obs_data_get_string(eventDataNested, "message"));
|
||||||
@@ -326,7 +350,7 @@ void WSConnection::OnGenericMessage(connection_hdl, client::message_ptr message)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(switcher->m);
|
auto lock = LockContext();
|
||||||
const auto payload = message->get_payload();
|
const auto payload = message->get_payload();
|
||||||
_messages.emplace_back(payload);
|
_messages.emplace_back(payload);
|
||||||
vblog(LOG_INFO, "received event msg \"%s\"", payload.c_str());
|
vblog(LOG_INFO, "received event msg \"%s\"", payload.c_str());
|
||||||
|
|||||||
@@ -21,18 +21,17 @@
|
|||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
using websocketpp::connection_hdl;
|
using websocketpp::connection_hdl;
|
||||||
typedef websocketpp::server<websocketpp::config::asio> server;
|
|
||||||
typedef websocketpp::client<websocketpp::config::asio_client> client;
|
|
||||||
|
|
||||||
constexpr char VendorName[] = "AdvancedSceneSwitcher";
|
void SetupWebsocketHelpers();
|
||||||
constexpr char VendorRequest[] = "AdvancedSceneSwitcherMessage";
|
|
||||||
constexpr char VendorEvent[] = "AdvancedSceneSwitcherEvent";
|
|
||||||
|
|
||||||
void ClearWebsocketMessages();
|
|
||||||
void SendWebsocketEvent(const std::string &);
|
void SendWebsocketEvent(const std::string &);
|
||||||
std::string ConstructVendorRequestMessage(const std::string &message);
|
std::string ConstructVendorRequestMessage(const std::string &message);
|
||||||
|
std::vector<std::string> &GetWebsocketMessages();
|
||||||
|
|
||||||
class WSConnection : public QObject {
|
class WSConnection : public QObject {
|
||||||
|
using server = websocketpp::server<websocketpp::config::asio>;
|
||||||
|
using client = websocketpp::client<websocketpp::config::asio_client>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit WSConnection(bool useOBSProtocol = true);
|
explicit WSConnection(bool useOBSProtocol = true);
|
||||||
virtual ~WSConnection();
|
virtual ~WSConnection();
|
||||||
|
|||||||
@@ -5,13 +5,17 @@ get_target_property(ADVSS_SOURCE_DIR advanced-scene-switcher-lib SOURCE_DIR)
|
|||||||
add_executable(${PROJECT_NAME})
|
add_executable(${PROJECT_NAME})
|
||||||
target_compile_definitions(${PROJECT_NAME} PRIVATE UNIT_TEST)
|
target_compile_definitions(${PROJECT_NAME} PRIVATE UNIT_TEST)
|
||||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
||||||
target_sources(${PROJECT_NAME}
|
|
||||||
PRIVATE tests.cpp ${ADVSS_SOURCE_DIR}/src/utils/math-helpers.cpp)
|
target_sources(
|
||||||
|
${PROJECT_NAME}
|
||||||
|
PRIVATE tests.cpp ${ADVSS_SOURCE_DIR}/src/utils/math-helpers.cpp
|
||||||
|
${ADVSS_SOURCE_DIR}/src/utils/obs-module-helper.cpp)
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
${PROJECT_NAME}
|
${PROJECT_NAME}
|
||||||
PRIVATE "${ADVSS_SOURCE_DIR}/src" "${ADVSS_SOURCE_DIR}/src/legacy"
|
PRIVATE "${ADVSS_SOURCE_DIR}/src" "${ADVSS_SOURCE_DIR}/src/legacy"
|
||||||
"${ADVSS_SOURCE_DIR}/src/macro-core" "${ADVSS_SOURCE_DIR}/src/utils"
|
"${ADVSS_SOURCE_DIR}/src/macro-core" "${ADVSS_SOURCE_DIR}/src/utils"
|
||||||
"${ADVSS_SOURCE_DIR}/forms" "${ADVSS_SOURCE_DIR}/deps/exprtk")
|
"${ADVSS_SOURCE_DIR}/forms" "${ADVSS_SOURCE_DIR}/deps/exprtk")
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
target_compile_options(${PROJECT_NAME} PUBLIC /MP /d2FH4- /wd4267 /wd4267
|
target_compile_options(${PROJECT_NAME} PUBLIC /MP /d2FH4- /wd4267 /wd4267
|
||||||
/bigobj)
|
/bigobj)
|
||||||
|
|||||||
Reference in New Issue
Block a user