mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 04:36:21 -05:00
Compare commits
28 Commits
1.32.0-bet
...
1.32.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0e6e6f528 | ||
|
|
e30d5845fa | ||
|
|
d12911cd02 | ||
|
|
b8ecc40e8c | ||
|
|
8ec4849b1d | ||
|
|
e6e9f3a831 | ||
|
|
555f7c1381 | ||
|
|
f0f8b0fd92 | ||
|
|
661e83162f | ||
|
|
4606f80a9d | ||
|
|
9c109742fb | ||
|
|
b8b0682aaf | ||
|
|
f93175db77 | ||
|
|
8f92ba3ffa | ||
|
|
0d56de11d1 | ||
|
|
e1020a1909 | ||
|
|
8b0bd4193b | ||
|
|
d55bb6bc86 | ||
|
|
0583331bfd | ||
|
|
6932de866d | ||
|
|
00db0cf7c4 | ||
|
|
e9baf27ca2 | ||
|
|
b1a5db0c9c | ||
|
|
8f3b868fd9 | ||
|
|
b3bf89840b | ||
|
|
84f7d0d214 | ||
|
|
e1164c4fa3 | ||
|
|
4534b23bad |
@@ -22,7 +22,7 @@ runs:
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1.13
|
||||
with:
|
||||
cmake-version: '3.28.x'
|
||||
cmake-version: '3.x.x'
|
||||
|
||||
- name: Restore cached dependencies
|
||||
id: restore-cache
|
||||
|
||||
44
.github/scripts/.build-deps.zsh
vendored
44
.github/scripts/.build-deps.zsh
vendored
@@ -394,8 +394,11 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
|
||||
popd
|
||||
|
||||
pushd ${advss_dep_path}
|
||||
log_info "Prepare openssl ..."
|
||||
rm -rf ${advss_dep_path}/openssl ${advss_dep_path}/openssl_build
|
||||
mkdir ${advss_dep_path}/openssl_build
|
||||
pushd ${advss_dep_path}/openssl_build
|
||||
|
||||
rm -rf openssl
|
||||
git clone https://github.com/openssl/openssl.git --branch openssl-3.1.2 --depth 1
|
||||
mv openssl openssl_x86
|
||||
@@ -403,25 +406,27 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
|
||||
log_info "Building openssl x86 ..."
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.9
|
||||
cd openssl_x86
|
||||
./Configure darwin64-x86_64-cc shared
|
||||
make
|
||||
pushd openssl_x86
|
||||
./Configure darwin64-x86_64-cc no-shared no-module no-zlib --prefix=${advss_dep_path}
|
||||
make -j$(nproc)
|
||||
popd
|
||||
|
||||
log_info "Building openssl arm ..."
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.15
|
||||
cd ../openssl_arm
|
||||
./Configure enable-rc5 zlib darwin64-arm64-cc no-asm
|
||||
make
|
||||
pushd openssl_arm
|
||||
./Configure enable-rc5 darwin64-arm64-cc no-shared no-module no-asm no-zlib --prefix=${advss_dep_path}
|
||||
make -j$(nproc)
|
||||
|
||||
log_info "Install openssl ..."
|
||||
make install
|
||||
popd
|
||||
|
||||
log_info "Combine arm and x86 openssl binaries ..."
|
||||
cd ..
|
||||
mkdir openssl-combined
|
||||
lipo -create openssl_x86/libcrypto.a openssl_arm/libcrypto.a -output openssl-combined/libcrypto.a
|
||||
lipo -create openssl_x86/libssl.a openssl_arm/libssl.a -output openssl-combined/libssl.a
|
||||
lipo -create openssl_x86/libcrypto.a openssl_arm/libcrypto.a -output ${advss_dep_path}/lib/libcrypto.a
|
||||
lipo -create openssl_x86/libssl.a openssl_arm/libssl.a -output ${advss_dep_path}/lib/libssl.a
|
||||
|
||||
log_info "Clean up openssl dir ..."
|
||||
mv openssl_x86 openssl
|
||||
rm -rf openssl_arm
|
||||
rm -rf openssl_x86 openssl_arm
|
||||
popd
|
||||
|
||||
pushd ${project_root}/deps/libusb
|
||||
@@ -439,14 +444,16 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
mkdir ${project_root}/deps/libusb/out_x86
|
||||
./autogen.sh
|
||||
./configure --host=x86_64-apple-darwin --prefix=${advss_dep_path}
|
||||
make && make install
|
||||
make -j$(nproc)
|
||||
make install
|
||||
|
||||
log_info "Configure libusb arm ..."
|
||||
make clean
|
||||
rm -r ${project_root}/deps/libusb/out_x86
|
||||
mkdir ${project_root}/deps/libusb/out_x86
|
||||
./configure --host=aarch64-apple-darwin --prefix=${project_root}/deps/libusb/out_x86
|
||||
make && make install
|
||||
make -j$(nproc)
|
||||
make install
|
||||
|
||||
log_info "Building libusb arm ..."
|
||||
make clean
|
||||
@@ -459,7 +466,8 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.15
|
||||
mkdir ${project_root}/deps/libusb/out_arm
|
||||
./configure --host=aarch64-apple-darwin --prefix=${project_root}/deps/libusb/out_arm
|
||||
make && make install
|
||||
make -j$(nproc)
|
||||
make install
|
||||
|
||||
log_info "Combine arm and x86 libusb binaries ..."
|
||||
lipo -create ${project_root}/deps/libusb/out_x86/lib/libusb-1.0.0.dylib \
|
||||
@@ -491,8 +499,8 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
-DPAHO_BUILD_SHARED=OFF
|
||||
-DPAHO_BUILD_STATIC=ON
|
||||
-DPAHO_WITH_MQTT_C=ON
|
||||
-DOPENSSL_ROOT_DIR="${advss_dep_path}"
|
||||
-DPAHO_WITH_SSL=OFF # TODO: figure out linking issues with openssl
|
||||
-DPAHO_WITH_SSL=ON
|
||||
-DOPENSSL_USE_STATIC_LIBS=ON
|
||||
)
|
||||
|
||||
pushd ${mqtt_dir}
|
||||
|
||||
6
.github/scripts/.build.zsh
vendored
6
.github/scripts/.build.zsh
vendored
@@ -253,12 +253,8 @@ ${_usage_host:-}"
|
||||
macos-*)
|
||||
if (( ${+CI} )) typeset -gx NSUnbufferedIO=YES
|
||||
|
||||
local openssl_lib_dir="${advss_deps_path}/openssl-combined/"
|
||||
local openssl_include_dir="${advss_deps_path}/openssl/include"
|
||||
|
||||
cmake_args+=(
|
||||
-DOPENSSL_INCLUDE_DIR="${openssl_include_dir}"
|
||||
-DOPENSSL_LIBRARIES="${openssl_lib_dir}/libcrypto.a;${openssl_lib_dir}/libssl.a"
|
||||
-DCMAKE_PREFIX_PATH="${advss_deps_path}"
|
||||
--preset ${_preset}
|
||||
)
|
||||
|
||||
|
||||
16
.github/scripts/Build-Deps-Windows.ps1
vendored
16
.github/scripts/Build-Deps-Windows.ps1
vendored
@@ -234,8 +234,24 @@ function Build {
|
||||
"-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}"
|
||||
"-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}"
|
||||
"-DPAHO_WITH_MQTT_C=ON"
|
||||
"-DPAHO_WITH_SSL=ON"
|
||||
)
|
||||
|
||||
# Try to find OpenSSL installed via winget
|
||||
$pf64 = Join-Path $Env:ProgramFiles "OpenSSL-Win64"
|
||||
$pf = Join-Path $Env:ProgramFiles "OpenSSL"
|
||||
$possibleDirs = @($pf64, $pf)
|
||||
$opensslDir = $possibleDirs | Where-Object { Test-Path (Join-Path $_ "include\openssl\ssl.h") } | Select-Object -First 1
|
||||
|
||||
if ($opensslDir) {
|
||||
Write-Host "Detected OpenSSL at: $opensslDir"
|
||||
$MqttCmakeArgs += "-DOPENSSL_ROOT_DIR=$opensslDir"
|
||||
$MqttCmakeArgs += "-DOPENSSL_CRYPTO_LIBRARY=$opensslDir\lib\VC\x64\MD\libcrypto.lib"
|
||||
$MqttCmakeArgs += "-DOPENSSL_SSL_LIBRARY=$opensslDir\lib\VC\x64\MD\libssl.lib"
|
||||
} else {
|
||||
Write-Warning "OpenSSL not found - maybe cmake will find it ..."
|
||||
}
|
||||
|
||||
Log-Information "Configuring paho.mqtt.cpp..."
|
||||
Invoke-External cmake -S ${MqttPath} -B ${MqttBuildPath} @MqttCmakeArgs
|
||||
|
||||
|
||||
4
.github/scripts/utils.zsh/check_macos
vendored
4
.github/scripts/utils.zsh/check_macos
vendored
@@ -18,5 +18,7 @@ if (( ! ${+commands[brew]} )) {
|
||||
}
|
||||
|
||||
brew bundle --file ${SCRIPT_HOME}/.Brewfile
|
||||
rehash
|
||||
# Workaround to make sure locally built openssl is picked up by cmake
|
||||
brew uninstall --ignore-dependencies openssl@3 || true
|
||||
rehash || true
|
||||
log_group
|
||||
|
||||
4
.github/workflows/build-project.yaml
vendored
4
.github/workflows/build-project.yaml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
description: "Project name detected by parsing build spec file"
|
||||
value: ${{ jobs.check-event.outputs.pluginName }}
|
||||
env:
|
||||
DEP_DIR: .deps/advss-build-dependencies-2
|
||||
DEP_DIR: .deps/advss-build-dependencies-3
|
||||
jobs:
|
||||
check-event:
|
||||
name: Check GitHub Event Data 🔎
|
||||
@@ -213,7 +213,7 @@ jobs:
|
||||
- name: Set up CMake 🏗️
|
||||
uses: jwlawson/actions-setup-cmake@v1.13
|
||||
with:
|
||||
cmake-version: '3.24.x'
|
||||
cmake-version: '3.x.x'
|
||||
|
||||
- name: Set up Homebrew 🍺
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
|
||||
@@ -34,6 +34,11 @@ include(cmake/common/get_git_revision_description.cmake)
|
||||
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
|
||||
git_describe(GIT_TAG)
|
||||
|
||||
# Helper for OpenSSL
|
||||
if(OS_WINDOWS)
|
||||
include(cmake/windows/wingetssl.cmake)
|
||||
endif()
|
||||
|
||||
if(${GIT_TAG} STREQUAL "GIT-NOTFOUND")
|
||||
set(GIT_TAG ${PROJECT_VERSION})
|
||||
endif()
|
||||
|
||||
78
cmake/windows/wingetssl.cmake
Normal file
78
cmake/windows/wingetssl.cmake
Normal file
@@ -0,0 +1,78 @@
|
||||
# ---------------------------------------------------------------------------
|
||||
# Detects OpenSSL installed via winget or other common Windows locations,
|
||||
# without requiring the user to manually set OPENSSL_ROOT_DIR.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
if(WIN32 AND (NOT OpenSSL_FOUND))
|
||||
|
||||
set(_openssl_roots
|
||||
"$ENV{ProgramFiles}/OpenSSL-Win64" "$ENV{ProgramFiles}/OpenSSL"
|
||||
"$ENV{ProgramW6432}/OpenSSL-Win64")
|
||||
|
||||
set(_openssl_lib_suffixes "lib/VC/x64/MD" "lib/VC/x64/MDd" "lib/VC/x64/MT"
|
||||
"lib/VC/x64/MTd" "lib")
|
||||
|
||||
# Determine which configuration we're building
|
||||
if(CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||
set(_is_debug TRUE)
|
||||
else()
|
||||
set(_is_debug FALSE)
|
||||
endif()
|
||||
|
||||
# Determine which runtime we use Default to /MD (shared CRT)
|
||||
set(_crt_kind "MD")
|
||||
if(MSVC)
|
||||
if(CMAKE_MSVC_RUNTIME_LIBRARY MATCHES "MultiThreaded")
|
||||
if(CMAKE_MSVC_RUNTIME_LIBRARY MATCHES "Debug")
|
||||
set(_crt_kind "MTd")
|
||||
else()
|
||||
set(_crt_kind "MT")
|
||||
endif()
|
||||
else()
|
||||
if(_is_debug)
|
||||
set(_crt_kind "MDd")
|
||||
else()
|
||||
set(_crt_kind "MD")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
message(STATUS "Looking for OpenSSL built with CRT variant: ${_crt_kind}")
|
||||
|
||||
# Try to find the root and corresponding lib path
|
||||
foreach(_root ${_openssl_roots})
|
||||
if(EXISTS "${_root}/include/openssl/ssl.h")
|
||||
foreach(_suffix ${_openssl_lib_suffixes})
|
||||
if(_suffix MATCHES "${_crt_kind}$"
|
||||
AND EXISTS "${_root}/${_suffix}/libcrypto.lib")
|
||||
set(OPENSSL_ROOT_DIR
|
||||
"${_root}"
|
||||
CACHE PATH "Path to OpenSSL root")
|
||||
set(OPENSSL_CRYPTO_LIBRARY
|
||||
"${_root}/${_suffix}/libcrypto.lib"
|
||||
CACHE FILEPATH "OpenSSL crypto lib")
|
||||
set(OPENSSL_SSL_LIBRARY
|
||||
"${_root}/${_suffix}/libssl.lib"
|
||||
CACHE FILEPATH "OpenSSL ssl lib")
|
||||
set(OPENSSL_INCLUDE_DIR
|
||||
"${_root}/include"
|
||||
CACHE PATH "OpenSSL include dir")
|
||||
set(OpenSSL_FOUND
|
||||
TRUE
|
||||
CACHE BOOL "Whether OpenSSL was found")
|
||||
message(STATUS "Found OpenSSL at: ${_root}/${_suffix}")
|
||||
return()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
if(OpenSSL_FOUND)
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(NOT OpenSSL_FOUND)
|
||||
message(WARNING "Could not auto-detect OpenSSL under Program Files. "
|
||||
"Might have to set OPENSSL_ROOT_DIR manually.")
|
||||
endif()
|
||||
|
||||
endif()
|
||||
@@ -91,6 +91,12 @@ AdvSceneSwitcher.variableTab.lastUsed.text.never="Never"
|
||||
AdvSceneSwitcher.variableTab.lastChanged.header="Last changed"
|
||||
AdvSceneSwitcher.variableTab.lastChanged.text.none="No change since launch"
|
||||
AdvSceneSwitcher.variableTab.lastChanged.tooltip="Times changed: %1\n\nPrevious value: %2"
|
||||
AdvSceneSwitcher.variableTab.search.placeholder="Search ..."
|
||||
AdvSceneSwitcher.variableTab.search.all="All columns"
|
||||
AdvSceneSwitcher.variableTab.search.name="Name column"
|
||||
AdvSceneSwitcher.variableTab.search.value="Value column"
|
||||
AdvSceneSwitcher.variableTab.addDock="Add dock"
|
||||
AdvSceneSwitcher.variableTab.clear="Clear"
|
||||
|
||||
# Action Queue Tab
|
||||
AdvSceneSwitcher.actionQueueTab.title="Action Queues"
|
||||
@@ -928,6 +934,9 @@ AdvSceneSwitcher.action.source.type.deinterlaceOrder="Set deinterlace field orde
|
||||
AdvSceneSwitcher.action.source.type.openInteractionDialog="Open interaction dialog"
|
||||
AdvSceneSwitcher.action.source.type.openFilterDialog="Open filter dialog"
|
||||
AdvSceneSwitcher.action.source.type.openPropertiesDialog="Open properties dialog"
|
||||
AdvSceneSwitcher.action.source.type.closeInteractionDialog="Close interaction dialog"
|
||||
AdvSceneSwitcher.action.source.type.closeFilterDialog="Close filter dialog"
|
||||
AdvSceneSwitcher.action.source.type.closePropertiesDialog="Close properties dialog"
|
||||
AdvSceneSwitcher.action.source.entry="{{actions}}{{sources}}{{settingsButtons}}{{deinterlaceMode}}{{deinterlaceOrder}}{{refresh}}"
|
||||
AdvSceneSwitcher.action.source.entry.settings="{{settings}}{{settingsInputMethod}}{{settingValue}}{{tempVar}}"
|
||||
AdvSceneSwitcher.action.source.warning="Warning: Enabling and disabling sources globally cannot be controlled by the OBS UI\nYou might be looking for \"Scene item visibility\""
|
||||
@@ -949,6 +958,7 @@ AdvSceneSwitcher.action.source.inputMethod.individualTempvar="Set to macro prope
|
||||
AdvSceneSwitcher.action.source.inputMethod.json="Set setting JSON string"
|
||||
AdvSceneSwitcher.action.source.refresh="Refresh"
|
||||
AdvSceneSwitcher.action.source.refresh.tooltip="Repopulate the source settings selection with the settings of the source which's name matches the variable value."
|
||||
AdvSceneSwitcher.action.source.dialog.accept="Accept changes"
|
||||
AdvSceneSwitcher.action.media="Media"
|
||||
AdvSceneSwitcher.action.media.type.play="Play"
|
||||
AdvSceneSwitcher.action.media.type.pause="Pause"
|
||||
@@ -984,12 +994,16 @@ AdvSceneSwitcher.action.macro.type.disableAction="Disable action"
|
||||
AdvSceneSwitcher.action.macro.type.enableAction="Enable action"
|
||||
AdvSceneSwitcher.action.macro.type.toggleAction="Toggle action"
|
||||
AdvSceneSwitcher.action.macro.type.nestedMacro="Nested macro"
|
||||
AdvSceneSwitcher.action.macro.actionSelectionType.index="at index"
|
||||
AdvSceneSwitcher.action.macro.actionSelectionType.label="with label"
|
||||
AdvSceneSwitcher.action.macro.actionSelectionType.id="of action type"
|
||||
AdvSceneSwitcher.action.macro.type.nestedMacro.conditionHelp="This section allows you to define macro conditions.\n\nClick the plus button below to add a new condition."
|
||||
AdvSceneSwitcher.action.macro.type.nestedMacro.actionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are met.\n\nClick the plus button below to add a new action."
|
||||
AdvSceneSwitcher.action.macro.type.nestedMacro.elseActionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are *not* met.\n\nClick the plus button below to add a new action."
|
||||
AdvSceneSwitcher.action.macro.entry.run="{{actions}}{{actionTypes}}of{{macros}}"
|
||||
AdvSceneSwitcher.action.macro.entry.run.condition="{{conditionBehaviors}}of{{conditionMacros}}"
|
||||
AdvSceneSwitcher.action.macro.entry.other="{{actions}}{{actionIndex}}{{macros}}"
|
||||
AdvSceneSwitcher.action.macro.layout.run="{{actions}}{{actionSections}}of{{macros}}"
|
||||
AdvSceneSwitcher.action.macro.layout.run.condition="{{conditionBehaviors}}of{{conditionMacros}}"
|
||||
AdvSceneSwitcher.action.macro.layout.actionState="{{actions}}{{actionSelectionType}}{{actionIndex}}{{label}}{{regex}}{{actionTypes}}in{{actionSections}}section of{{macros}}"
|
||||
AdvSceneSwitcher.action.macro.layout.other="{{actions}}{{macros}}"
|
||||
AdvSceneSwitcher.action.pluginState="Plugin state"
|
||||
AdvSceneSwitcher.action.pluginState.type.stop="Stop the Advanced Scene Switcher plugin"
|
||||
AdvSceneSwitcher.action.pluginState.type.noMatch="Change the no-match behaviour:"
|
||||
@@ -1141,6 +1155,7 @@ AdvSceneSwitcher.action.http.entry.line2="Timeout:{{timeout}}seconds"
|
||||
AdvSceneSwitcher.action.variable="Variable"
|
||||
AdvSceneSwitcher.action.variable.type.set="Set to value"
|
||||
AdvSceneSwitcher.action.variable.type.append="Append"
|
||||
AdvSceneSwitcher.action.variable.type.copy="Copy variable"
|
||||
AdvSceneSwitcher.action.variable.type.appendVar="Append variable"
|
||||
AdvSceneSwitcher.action.variable.type.increment="Increment"
|
||||
AdvSceneSwitcher.action.variable.type.decrement="Decrement"
|
||||
@@ -1514,6 +1529,13 @@ AdvSceneSwitcher.mqttConnection.name="Name:"
|
||||
AdvSceneSwitcher.mqttConnection.address="Address:"
|
||||
AdvSceneSwitcher.mqttConnection.username="Username:"
|
||||
AdvSceneSwitcher.mqttConnection.password="Password:"
|
||||
AdvSceneSwitcher.mqttConnection.trustStore="Trust store:"
|
||||
AdvSceneSwitcher.mqttConnection.trustStore.help="The file in PEM format containing the public digital certificates trusted by the client."
|
||||
AdvSceneSwitcher.mqttConnection.keyStore="Key store:"
|
||||
AdvSceneSwitcher.mqttConnection.keyStore.help="The file in PEM format containing the public certificate chain of the client.\nIt may also include the client's private key."
|
||||
AdvSceneSwitcher.mqttConnection.privateKey="Private key:"
|
||||
AdvSceneSwitcher.mqttConnection.privateKey.help="If not included in the key store, this is the file in PEM format containing the client's private key."
|
||||
AdvSceneSwitcher.mqttConnection.verifyServerCert="Verify server certificate"
|
||||
AdvSceneSwitcher.mqttConnection.reconnect="Reconnect automatically:"
|
||||
AdvSceneSwitcher.mqttConnection.reconnectDelay="Automatically reconnect after:"
|
||||
AdvSceneSwitcher.mqttConnection.connectOnStart="Connect on startup:"
|
||||
@@ -2301,6 +2323,9 @@ AdvSceneSwitcher.tempVar.mqtt.message="Message"
|
||||
AdvSceneSwitcher.tempVar.cursor.x="Cursor position (X)"
|
||||
AdvSceneSwitcher.tempVar.cursor.y="Cursor position (Y)"
|
||||
|
||||
AdvSceneSwitcher.tempVar.replay.lastSavePath="Last save path"
|
||||
AdvSceneSwitcher.tempVar.replay.lastSavePath.description="The file path of the last replay buffer saved."
|
||||
|
||||
AdvSceneSwitcher.selectScene="--select scene--"
|
||||
AdvSceneSwitcher.selectCanvas="--select canvas--"
|
||||
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
|
||||
|
||||
@@ -861,9 +861,9 @@ AdvSceneSwitcher.action.macro.type.stop="Parar ações"
|
||||
AdvSceneSwitcher.action.macro.type.disableAction="Desabilitar ação"
|
||||
AdvSceneSwitcher.action.macro.type.enableAction="Habilitar ação"
|
||||
AdvSceneSwitcher.action.macro.type.toggleAction="Alternar ação"
|
||||
AdvSceneSwitcher.action.macro.entry.run="{{actions}}{{actionTypes}}de{{macros}}"
|
||||
AdvSceneSwitcher.action.macro.entry.run.condition="{{conditionBehaviors}}de{{conditionMacros}}"
|
||||
AdvSceneSwitcher.action.macro.entry.other="{{actions}}{{actionIndex}}{{macros}}"
|
||||
AdvSceneSwitcher.action.macro.layout.run="{{actions}}{{actionSections}}de{{macros}}"
|
||||
AdvSceneSwitcher.action.macro.layout.run.condition="{{conditionBehaviors}}de{{conditionMacros}}"
|
||||
AdvSceneSwitcher.action.macro.layout.other="{{actions}}{{macros}}"
|
||||
AdvSceneSwitcher.action.pluginState="Estado do plugin"
|
||||
AdvSceneSwitcher.action.pluginState.type.stop="Parar o plugin Advanced Scene Switcher"
|
||||
AdvSceneSwitcher.action.pluginState.type.noMatch="Alterar o comportamento em caso de não correspondência:"
|
||||
|
||||
@@ -920,9 +920,9 @@ AdvSceneSwitcher.action.macro.type.stop="停止操作"
|
||||
AdvSceneSwitcher.action.macro.type.disableAction="停用操作"
|
||||
AdvSceneSwitcher.action.macro.type.enableAction="启用操作"
|
||||
AdvSceneSwitcher.action.macro.type.toggleAction="切换操作开关"
|
||||
AdvSceneSwitcher.action.macro.entry.run="{{actions}}{{actionTypes}}{{macros}}"
|
||||
AdvSceneSwitcher.action.macro.entry.run.condition="{{conditionBehaviors}}{{conditionMacros}}"
|
||||
AdvSceneSwitcher.action.macro.entry.other="{{actions}}{{actionIndex}}{{macros}}"
|
||||
AdvSceneSwitcher.action.macro.layout.run="{{actions}}{{actionSections}}{{macros}}"
|
||||
AdvSceneSwitcher.action.macro.layout.run.condition="{{conditionBehaviors}}{{conditionMacros}}"
|
||||
AdvSceneSwitcher.action.macro.layout.other="{{actions}}{{macros}}"
|
||||
AdvSceneSwitcher.action.pluginState="插件状态"
|
||||
AdvSceneSwitcher.action.pluginState.type.stop="停止高级场景切换插件"
|
||||
AdvSceneSwitcher.action.pluginState.type.noMatch="没有匹配项时:"
|
||||
|
||||
@@ -421,7 +421,7 @@ bool SwitcherData::CheckForMatch(OBSWeakSource &scene,
|
||||
|
||||
static void ResetMacros()
|
||||
{
|
||||
for (auto &m : GetMacros()) {
|
||||
for (auto &m : GetTopLevelMacros()) {
|
||||
ResetMacroRunCount(m.get());
|
||||
ResetMacroConditionTimers(m.get());
|
||||
}
|
||||
|
||||
@@ -459,7 +459,7 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
|
||||
LoadHotkeys(obj);
|
||||
LoadUISettings(obj);
|
||||
|
||||
RunPostLoadSteps();
|
||||
RunAndClearPostLoadSteps();
|
||||
|
||||
// Reset on startup and scene collection change
|
||||
ResetLastOpenedTab();
|
||||
|
||||
@@ -117,7 +117,7 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
|
||||
*_entryData = MacroActionFactory::Create(id, macro);
|
||||
(*_entryData)->SetIndex(idx);
|
||||
(*_entryData)->PostLoad();
|
||||
RunPostLoadSteps();
|
||||
RunAndClearPostLoadSteps();
|
||||
}
|
||||
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
|
||||
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "help-icon.hpp"
|
||||
#include "layout-helpers.hpp"
|
||||
#include "macro.hpp"
|
||||
#include "macro-action-factory.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
@@ -12,6 +13,74 @@ bool MacroActionMacro::_registered = MacroActionFactory::Register(
|
||||
{MacroActionMacro::Create, MacroActionMacroEdit::Create,
|
||||
"AdvSceneSwitcher.action.macro"});
|
||||
|
||||
void MacroActionMacro::AdjustActionState(Macro *macro) const
|
||||
{
|
||||
const auto ¯oActions = _useElseSection ? macro->ElseActions()
|
||||
: macro->Actions();
|
||||
|
||||
std::vector<std::shared_ptr<MacroAction>> actionsToModify;
|
||||
switch (_actionSelectionType) {
|
||||
case SelectionType::INDEX: {
|
||||
const bool isValidAction =
|
||||
(_useElseSection &&
|
||||
IsValidElseActionIndex(macro, _actionIndex - 1)) ||
|
||||
(!_useElseSection &&
|
||||
IsValidActionIndex(macro, _actionIndex - 1));
|
||||
|
||||
if (isValidAction) {
|
||||
actionsToModify.emplace_back(
|
||||
macroActions.at(_actionIndex - 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SelectionType::LABEL:
|
||||
for (const auto &action : macroActions) {
|
||||
if (!action->GetUseCustomLabel()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto label = action->GetCustomLabel();
|
||||
|
||||
if (_regex.Enabled()) {
|
||||
if (_regex.Matches(label, _label)) {
|
||||
actionsToModify.emplace_back(action);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (label == std::string(_label)) {
|
||||
actionsToModify.emplace_back(action);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SelectionType::ID:
|
||||
for (const auto &action : macroActions) {
|
||||
if (action->GetId() == _actionId) {
|
||||
actionsToModify.emplace_back(action);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (const auto &action : actionsToModify) {
|
||||
switch (_action) {
|
||||
case Action::DISABLE_ACTION:
|
||||
action->SetEnabled(false);
|
||||
break;
|
||||
case Action::ENABLE_ACTION:
|
||||
action->SetEnabled(true);
|
||||
break;
|
||||
case Action::TOGGLE_ACTION:
|
||||
action->SetEnabled(!action->Enabled());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionMacro::PerformAction()
|
||||
{
|
||||
if (_action == Action::NESTED_MACRO) {
|
||||
@@ -44,23 +113,9 @@ bool MacroActionMacro::PerformAction()
|
||||
macro->Stop();
|
||||
break;
|
||||
case Action::DISABLE_ACTION:
|
||||
if (IsValidMacroSegmentIndex(macro.get(), _actionIndex - 1,
|
||||
false)) {
|
||||
macro->Actions().at(_actionIndex - 1)->SetEnabled(false);
|
||||
}
|
||||
break;
|
||||
case Action::ENABLE_ACTION:
|
||||
if (IsValidMacroSegmentIndex(macro.get(), _actionIndex - 1,
|
||||
false)) {
|
||||
macro->Actions().at(_actionIndex - 1)->SetEnabled(true);
|
||||
}
|
||||
break;
|
||||
case Action::TOGGLE_ACTION:
|
||||
if (IsValidMacroSegmentIndex(macro.get(), _actionIndex - 1,
|
||||
false)) {
|
||||
auto action = macro->Actions().at(_actionIndex - 1);
|
||||
action->SetEnabled(!action->Enabled());
|
||||
}
|
||||
AdjustActionState(macro.get());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -115,9 +170,14 @@ void MacroActionMacro::LogAction() const
|
||||
bool MacroActionMacro::Save(obs_data_t *obj) const
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_macro.Save(obj);
|
||||
_actionIndex.Save(obj, "actionIndex");
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
_macro.Save(obj);
|
||||
obs_data_set_int(obj, "actionSelectionType",
|
||||
static_cast<int>(_actionSelectionType));
|
||||
_actionIndex.Save(obj, "actionIndex");
|
||||
_label.Save(obj, "label");
|
||||
obs_data_set_string(obj, "actionId", _actionId.c_str());
|
||||
_regex.Save(obj);
|
||||
_runOptions.Save(obj);
|
||||
OBSDataAutoRelease nestedMacroData = obs_data_create();
|
||||
_nestedMacro->Save(nestedMacroData);
|
||||
@@ -129,10 +189,15 @@ bool MacroActionMacro::Save(obs_data_t *obj) const
|
||||
bool MacroActionMacro::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_macro.Load(obj);
|
||||
_actionIndex.Load(obj, "actionIndex");
|
||||
_action = static_cast<MacroActionMacro::Action>(
|
||||
obs_data_get_int(obj, "action"));
|
||||
_macro.Load(obj);
|
||||
_actionSelectionType = static_cast<SelectionType>(
|
||||
obs_data_get_int(obj, "actionSelectionType"));
|
||||
_actionIndex.Load(obj, "actionIndex");
|
||||
_label.Load(obj, "label");
|
||||
_actionId = obs_data_get_string(obj, "actionId");
|
||||
_regex.Load(obj);
|
||||
_runOptions.Load(obj);
|
||||
|
||||
if (obs_data_has_user_value(obj, "nestedMacro")) {
|
||||
@@ -183,6 +248,7 @@ std::shared_ptr<MacroAction> MacroActionMacro::Copy() const
|
||||
void MacroActionMacro::ResolveVariablesToFixedValues()
|
||||
{
|
||||
_actionIndex.ResolveVariables();
|
||||
_label.ResolveVariables();
|
||||
}
|
||||
|
||||
static void runActionsHelper(Macro *macro, bool runElseActions, bool setInputs,
|
||||
@@ -266,7 +332,7 @@ static void populateConditionBehaviorSelection(QComboBox *list)
|
||||
"AdvSceneSwitcher.action.macro.type.run.conditions.false"));
|
||||
}
|
||||
|
||||
static void populateActionTypeSelection(QComboBox *list)
|
||||
static void populateActionSectionSelection(QComboBox *list)
|
||||
{
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.action.macro.type.run.actionType.regular"));
|
||||
@@ -274,19 +340,47 @@ static void populateActionTypeSelection(QComboBox *list)
|
||||
"AdvSceneSwitcher.action.macro.type.run.actionType.else"));
|
||||
}
|
||||
|
||||
static void populateActionTypes(QComboBox *list)
|
||||
{
|
||||
for (const auto &[id, info] : MacroActionFactory::GetActionTypes()) {
|
||||
list->addItem(obs_module_text(info._name.c_str()),
|
||||
QString::fromStdString(id));
|
||||
}
|
||||
}
|
||||
|
||||
static void populateActionSelectionTypes(QComboBox *list)
|
||||
{
|
||||
list->addItem(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.macro.actionSelectionType.index"),
|
||||
static_cast<int>(MacroActionMacro::SelectionType::INDEX));
|
||||
list->addItem(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.macro.actionSelectionType.label"),
|
||||
static_cast<int>(MacroActionMacro::SelectionType::LABEL));
|
||||
list->addItem(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.macro.actionSelectionType.id"),
|
||||
static_cast<int>(MacroActionMacro::SelectionType::ID));
|
||||
}
|
||||
|
||||
MacroActionMacroEdit::MacroActionMacroEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionMacro> entryData)
|
||||
: ResizableWidget(parent),
|
||||
_actions(new QComboBox()),
|
||||
_macros(new MacroSelection(parent)),
|
||||
_actionSelectionType(new QComboBox(this)),
|
||||
_actionIndex(new MacroSegmentSelection(
|
||||
this, MacroSegmentSelection::Type::ACTION)),
|
||||
_actions(new QComboBox()),
|
||||
_label(new VariableLineEdit(this)),
|
||||
_actionTypes(new FilterComboBox(this)),
|
||||
_regex(new RegexConfigWidget(this)),
|
||||
_conditionMacros(new MacroSelection(parent)),
|
||||
_conditionBehaviors(new QComboBox()),
|
||||
_reevaluateConditionState(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.macro.type.run."
|
||||
"updateConditionMatchState"))),
|
||||
_actionTypes(new QComboBox()),
|
||||
_actionSections(new QComboBox(this)),
|
||||
_skipWhenPaused(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.action.macro.type.run.skipWhenPaused"))),
|
||||
_setInputs(new QCheckBox(obs_module_text(
|
||||
@@ -305,7 +399,9 @@ MacroActionMacroEdit::MacroActionMacroEdit(
|
||||
{
|
||||
populateActionSelection(_actions);
|
||||
populateConditionBehaviorSelection(_conditionBehaviors);
|
||||
populateActionTypeSelection(_actionTypes);
|
||||
populateActionSectionSelection(_actionSections);
|
||||
populateActionSelectionTypes(_actionSelectionType);
|
||||
populateActionTypes(_actionTypes);
|
||||
|
||||
_conditionMacros->HideSelectedMacro();
|
||||
|
||||
@@ -313,16 +409,25 @@ MacroActionMacroEdit::MacroActionMacroEdit(
|
||||
this, SLOT(MacroChanged(const QString &)));
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_actionSelectionType, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(ActionSelectionTypeChanged(int)));
|
||||
QWidget::connect(_actionIndex,
|
||||
SIGNAL(SelectionChanged(const IntVariable &)), this,
|
||||
SLOT(ActionIndexChanged(const IntVariable &)));
|
||||
QWidget::connect(_label, SIGNAL(editingFinished()), this,
|
||||
SLOT(LabelChanged()));
|
||||
QWidget::connect(_actionTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionTypeChanged(int)));
|
||||
QWidget::connect(_regex,
|
||||
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
|
||||
SLOT(RegexChanged(const RegexConfig &)));
|
||||
QWidget::connect(_conditionMacros,
|
||||
SIGNAL(currentTextChanged(const QString &)), this,
|
||||
SLOT(ConditionMacroChanged(const QString &)));
|
||||
QWidget::connect(_conditionBehaviors, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(ConditionBehaviorChanged(int)));
|
||||
QWidget::connect(_actionTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionTypeChanged(int)));
|
||||
QWidget::connect(_actionSections, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(ActionSectionChanged(int)));
|
||||
QWidget::connect(_skipWhenPaused, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(SkipWhenPausedChanged(int)));
|
||||
QWidget::connect(_setInputs, SIGNAL(stateChanged(int)), this,
|
||||
@@ -375,17 +480,24 @@ void MacroActionMacroEdit::UpdateEntryData()
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_actions->setCurrentIndex(
|
||||
_actions->findData(static_cast<int>(_entryData->_action)));
|
||||
_actionSelectionType->setCurrentIndex(_actionSelectionType->findData(
|
||||
static_cast<int>(_entryData->_actionSelectionType)));
|
||||
_actionIndex->SetValue(_entryData->_actionIndex);
|
||||
_actionIndex->SetMacro(_entryData->_macro.GetMacro());
|
||||
_label->setText(_entryData->_label);
|
||||
_actionTypes->setCurrentIndex(_actionTypes->findData(
|
||||
QString::fromStdString(_entryData->_actionId)));
|
||||
_regex->SetRegexConfig(_entryData->_regex);
|
||||
_macros->SetCurrentMacro(_entryData->_macro);
|
||||
_conditionMacros->SetCurrentMacro(_entryData->_runOptions.macro);
|
||||
_conditionBehaviors->setCurrentIndex(
|
||||
static_cast<int>(_entryData->_runOptions.logic));
|
||||
_reevaluateConditionState->setChecked(
|
||||
_entryData->_runOptions.reevaluateConditionState);
|
||||
_actionTypes->setCurrentIndex(
|
||||
_actionSections->setCurrentIndex(
|
||||
_entryData->_runOptions.runElseActions ? 1 : 0);
|
||||
_skipWhenPaused->setChecked(_entryData->_runOptions.skipWhenPaused);
|
||||
_setInputs->setChecked(_entryData->_runOptions.setInputs);
|
||||
@@ -424,12 +536,40 @@ void MacroActionMacroEdit::ActionChanged(int idx)
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionMacroEdit::ActionSelectionTypeChanged(int idx)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_actionSelectionType =
|
||||
static_cast<MacroActionMacro::SelectionType>(
|
||||
_actionSelectionType->itemData(idx).toInt());
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionMacroEdit::ActionIndexChanged(const IntVariable &value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_actionIndex = value;
|
||||
}
|
||||
|
||||
void MacroActionMacroEdit::LabelChanged()
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_label = _label->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroActionMacroEdit::ActionTypeChanged(int idx)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_actionId =
|
||||
_actionTypes->itemData(idx).toString().toStdString();
|
||||
}
|
||||
|
||||
void MacroActionMacroEdit::RegexChanged(const RegexConfig ®ex)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_regex = regex;
|
||||
}
|
||||
|
||||
void MacroActionMacroEdit::ConditionMacroChanged(const QString &text)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
@@ -451,10 +591,13 @@ void MacroActionMacroEdit::ReevaluateConditionStateChanged(int value)
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionMacroEdit::ActionTypeChanged(int value)
|
||||
void MacroActionMacroEdit::ActionSectionChanged(int useElse)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_runOptions.runElseActions = value;
|
||||
_entryData->_runOptions.runElseActions = useElse;
|
||||
_entryData->_useElseSection = useElse;
|
||||
_actionIndex->SetType(useElse ? MacroSegmentSelection::Type::ELSE_ACTION
|
||||
: MacroSegmentSelection::Type::ACTION);
|
||||
}
|
||||
|
||||
void MacroActionMacroEdit::SkipWhenPausedChanged(int value)
|
||||
@@ -483,7 +626,11 @@ void MacroActionMacroEdit::SetWidgetVisibility()
|
||||
_entryLayout->removeWidget(_actions);
|
||||
_entryLayout->removeWidget(_actionIndex);
|
||||
_entryLayout->removeWidget(_macros);
|
||||
_entryLayout->removeWidget(_actionSections);
|
||||
_entryLayout->removeWidget(_label);
|
||||
_entryLayout->removeWidget(_regex);
|
||||
_entryLayout->removeWidget(_actionTypes);
|
||||
_entryLayout->removeWidget(_actionSelectionType);
|
||||
_conditionLayout->removeWidget(_conditionBehaviors);
|
||||
_conditionLayout->removeWidget(_conditionMacros);
|
||||
|
||||
@@ -494,19 +641,40 @@ void MacroActionMacroEdit::SetWidgetVisibility()
|
||||
{"{{actions}}", _actions},
|
||||
{"{{actionIndex}}", _actionIndex},
|
||||
{"{{macros}}", _macros},
|
||||
{"{{actionTypes}}", _actionTypes},
|
||||
{"{{actionSections}}", _actionSections},
|
||||
{"{{conditionBehaviors}}", _conditionBehaviors},
|
||||
{"{{conditionMacros}}", _conditionMacros},
|
||||
{"{{actionSelectionType}}", _actionSelectionType},
|
||||
{"{{label}}", _label},
|
||||
{"{{regex}}", _regex},
|
||||
{"{{actionTypes}}", _actionTypes},
|
||||
|
||||
};
|
||||
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
_entryData->_action ==
|
||||
MacroActionMacro::Action::RUN_ACTIONS
|
||||
? "AdvSceneSwitcher.action.macro.entry.run"
|
||||
: "AdvSceneSwitcher.action.macro.entry.other"),
|
||||
_entryLayout, placeholders);
|
||||
const auto action = _entryData->_action;
|
||||
const char *layoutText = "";
|
||||
switch (action) {
|
||||
case MacroActionMacro::Action::PAUSE:
|
||||
case MacroActionMacro::Action::UNPAUSE:
|
||||
case MacroActionMacro::Action::TOGGLE_PAUSE:
|
||||
case MacroActionMacro::Action::RESET_COUNTER:
|
||||
case MacroActionMacro::Action::STOP:
|
||||
case MacroActionMacro::Action::NESTED_MACRO:
|
||||
layoutText = "AdvSceneSwitcher.action.macro.layout.other";
|
||||
break;
|
||||
case MacroActionMacro::Action::RUN_ACTIONS:
|
||||
layoutText = "AdvSceneSwitcher.action.macro.layout.run";
|
||||
break;
|
||||
case MacroActionMacro::Action::DISABLE_ACTION:
|
||||
case MacroActionMacro::Action::ENABLE_ACTION:
|
||||
case MacroActionMacro::Action::TOGGLE_ACTION:
|
||||
layoutText = "AdvSceneSwitcher.action.macro.layout.actionState";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
PlaceWidgets(obs_module_text(layoutText), _entryLayout, placeholders);
|
||||
|
||||
if (_entryData->_runOptions.logic ==
|
||||
MacroActionMacro::RunOptions::Logic::IGNORE_CONDITIONS) {
|
||||
@@ -515,56 +683,63 @@ void MacroActionMacroEdit::SetWidgetVisibility()
|
||||
} else {
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.macro.entry.run.condition"),
|
||||
"AdvSceneSwitcher.action.macro.layout.run.condition"),
|
||||
_conditionLayout, placeholders);
|
||||
}
|
||||
|
||||
if (_entryData->_action == MacroActionMacro::Action::RUN_ACTIONS ||
|
||||
_entryData->_action == MacroActionMacro::Action::STOP) {
|
||||
if (action == MacroActionMacro::Action::RUN_ACTIONS ||
|
||||
action == MacroActionMacro::Action::STOP) {
|
||||
_macros->HideSelectedMacro();
|
||||
} else {
|
||||
_macros->ShowAllMacros();
|
||||
}
|
||||
|
||||
const auto actionSelectionType = _entryData->_actionSelectionType;
|
||||
const bool isModifyingActionState =
|
||||
_entryData->_action ==
|
||||
MacroActionMacro::Action::DISABLE_ACTION ||
|
||||
_entryData->_action ==
|
||||
MacroActionMacro::Action::ENABLE_ACTION ||
|
||||
_entryData->_action == MacroActionMacro::Action::TOGGLE_ACTION;
|
||||
_actionIndex->setVisible(isModifyingActionState);
|
||||
action == MacroActionMacro::Action::DISABLE_ACTION ||
|
||||
action == MacroActionMacro::Action::ENABLE_ACTION ||
|
||||
action == MacroActionMacro::Action::TOGGLE_ACTION;
|
||||
_actionSelectionType->setVisible(isModifyingActionState);
|
||||
_actionIndex->setVisible(
|
||||
isModifyingActionState &&
|
||||
actionSelectionType == MacroActionMacro::SelectionType::INDEX);
|
||||
_label->setVisible(isModifyingActionState &&
|
||||
actionSelectionType ==
|
||||
MacroActionMacro::SelectionType::LABEL);
|
||||
_regex->setVisible(isModifyingActionState &&
|
||||
actionSelectionType ==
|
||||
MacroActionMacro::SelectionType::LABEL);
|
||||
_actionTypes->setVisible(isModifyingActionState &&
|
||||
actionSelectionType ==
|
||||
MacroActionMacro::SelectionType::ID);
|
||||
|
||||
SetLayoutVisible(_conditionLayout,
|
||||
_entryData->_action ==
|
||||
MacroActionMacro::Action::RUN_ACTIONS);
|
||||
action == MacroActionMacro::Action::RUN_ACTIONS);
|
||||
const bool needsAdditionalConditionWidgets =
|
||||
_entryData->_action == MacroActionMacro::Action::RUN_ACTIONS &&
|
||||
action == MacroActionMacro::Action::RUN_ACTIONS &&
|
||||
_entryData->_runOptions.logic !=
|
||||
MacroActionMacro::RunOptions::Logic::IGNORE_CONDITIONS;
|
||||
_conditionMacros->setVisible(needsAdditionalConditionWidgets);
|
||||
SetLayoutVisible(_reevaluateConditionStateLayout,
|
||||
needsAdditionalConditionWidgets);
|
||||
SetLayoutVisible(_setInputsLayout,
|
||||
_entryData->_action ==
|
||||
MacroActionMacro::Action::RUN_ACTIONS);
|
||||
_inputs->setVisible(_entryData->_action ==
|
||||
MacroActionMacro::Action::RUN_ACTIONS &&
|
||||
action == MacroActionMacro::Action::RUN_ACTIONS);
|
||||
_inputs->setVisible(action == MacroActionMacro::Action::RUN_ACTIONS &&
|
||||
_entryData->_runOptions.setInputs);
|
||||
HighlightMacroSettingsButton(
|
||||
_entryData->_action == MacroActionMacro::Action::RUN_ACTIONS &&
|
||||
action == MacroActionMacro::Action::RUN_ACTIONS &&
|
||||
_entryData->_runOptions.setInputs &&
|
||||
!_inputs->HasInputsToSet());
|
||||
_actionTypes->setVisible(_entryData->_action ==
|
||||
MacroActionMacro::Action::RUN_ACTIONS);
|
||||
_skipWhenPaused->setVisible(_entryData->_action ==
|
||||
_actionSections->setVisible(
|
||||
action == MacroActionMacro::Action::RUN_ACTIONS ||
|
||||
isModifyingActionState);
|
||||
_skipWhenPaused->setVisible(action ==
|
||||
MacroActionMacro::Action::RUN_ACTIONS);
|
||||
|
||||
_nestedMacro->setVisible(_entryData->_action ==
|
||||
_nestedMacro->setVisible(action ==
|
||||
MacroActionMacro::Action::NESTED_MACRO);
|
||||
_macros->setVisible(_entryData->_action !=
|
||||
MacroActionMacro::Action::NESTED_MACRO);
|
||||
SetResizingEnabled(_entryData->_action ==
|
||||
MacroActionMacro::Action::NESTED_MACRO);
|
||||
_macros->setVisible(action != MacroActionMacro::Action::NESTED_MACRO);
|
||||
SetResizingEnabled(action == MacroActionMacro::Action::NESTED_MACRO);
|
||||
|
||||
if (_nestedMacro->IsEmpty()) {
|
||||
_nestedMacro->ShowAllMacroSections();
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
#include "macro-input.hpp"
|
||||
#include "macro-selection.hpp"
|
||||
#include "macro-segment-selection.hpp"
|
||||
#include "regex-config.hpp"
|
||||
#include "resizable-widget.hpp"
|
||||
#include "variable-line-edit.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QHBoxLayout>
|
||||
@@ -55,14 +57,23 @@ public:
|
||||
TOGGLE_PAUSE,
|
||||
NESTED_MACRO,
|
||||
};
|
||||
|
||||
enum class SelectionType { INDEX, LABEL, ID };
|
||||
|
||||
Action _action = Action::NESTED_MACRO;
|
||||
SelectionType _actionSelectionType = SelectionType::INDEX;
|
||||
bool _useElseSection = false;
|
||||
IntVariable _actionIndex = 1;
|
||||
StringVariable _label = "Custom label";
|
||||
std::string _actionId;
|
||||
RegexConfig _regex;
|
||||
RunOptions _runOptions = {};
|
||||
std::shared_ptr<Macro> _nestedMacro = std::make_shared<Macro>();
|
||||
int _customWidgetHeight = 0;
|
||||
|
||||
private:
|
||||
void RunActions(Macro *actionMacro) const;
|
||||
void AdjustActionState(Macro *) const;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
@@ -82,11 +93,15 @@ public:
|
||||
private slots:
|
||||
void MacroChanged(const QString &text);
|
||||
void ActionChanged(int value);
|
||||
void ActionSelectionTypeChanged(int value);
|
||||
void ActionIndexChanged(const IntVariable &value);
|
||||
void LabelChanged();
|
||||
void ActionTypeChanged(int value);
|
||||
void RegexChanged(const RegexConfig &);
|
||||
void ConditionMacroChanged(const QString &text);
|
||||
void ConditionBehaviorChanged(int value);
|
||||
void ReevaluateConditionStateChanged(int value);
|
||||
void ActionTypeChanged(int value);
|
||||
void ActionSectionChanged(int value);
|
||||
void SkipWhenPausedChanged(int value);
|
||||
void SetInputsChanged(int value);
|
||||
void InputsChanged(const StringList &);
|
||||
@@ -98,13 +113,17 @@ private:
|
||||
void SetWidgetVisibility();
|
||||
void SetupMacroInput(Macro *) const;
|
||||
|
||||
MacroSelection *_macros;
|
||||
MacroSegmentSelection *_actionIndex;
|
||||
QComboBox *_actions;
|
||||
MacroSelection *_macros;
|
||||
QComboBox *_actionSelectionType;
|
||||
MacroSegmentSelection *_actionIndex;
|
||||
VariableLineEdit *_label;
|
||||
FilterComboBox *_actionTypes;
|
||||
RegexConfigWidget *_regex;
|
||||
MacroSelection *_conditionMacros;
|
||||
QComboBox *_conditionBehaviors;
|
||||
QCheckBox *_reevaluateConditionState;
|
||||
QComboBox *_actionTypes;
|
||||
QComboBox *_actionSections;
|
||||
QCheckBox *_skipWhenPaused;
|
||||
QCheckBox *_setInputs;
|
||||
MacroInputEdit *_inputs;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "macro-action-variable.hpp"
|
||||
#include "help-icon.hpp"
|
||||
#include "json-helpers.hpp"
|
||||
#include "layout-helpers.hpp"
|
||||
#include "macro-condition-edit.hpp"
|
||||
@@ -285,6 +286,14 @@ bool MacroActionVariable::PerformAction()
|
||||
case Action::APPEND:
|
||||
apppend(*var, _strValue);
|
||||
break;
|
||||
case Action::COPY_VAR: {
|
||||
auto var2 = _variable2.lock();
|
||||
if (!var2) {
|
||||
return true;
|
||||
}
|
||||
var->SetValue(var2->Value());
|
||||
break;
|
||||
}
|
||||
case Action::APPEND_VAR: {
|
||||
auto var2 = _variable2.lock();
|
||||
if (!var2) {
|
||||
@@ -669,6 +678,8 @@ static inline void populateActionSelection(QComboBox *list)
|
||||
actions = {
|
||||
{MacroActionVariable::Action::SET_VALUE,
|
||||
"AdvSceneSwitcher.action.variable.type.set"},
|
||||
{MacroActionVariable::Action::COPY_VAR,
|
||||
"AdvSceneSwitcher.action.variable.type.copy"},
|
||||
{MacroActionVariable::Action::APPEND,
|
||||
"AdvSceneSwitcher.action.variable.type.append"},
|
||||
{MacroActionVariable::Action::PAD,
|
||||
@@ -828,7 +839,10 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
this)),
|
||||
_randomLayout(new QVBoxLayout()),
|
||||
_jsonQuery(new VariableLineEdit(this)),
|
||||
_jsonQueryHelp(new QLabel(this)),
|
||||
_jsonQueryHelp(new HelpIcon(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.variable.type.queryJson.info"),
|
||||
this)),
|
||||
_jsonIndex(new VariableSpinBox(this)),
|
||||
_entryLayout(new QHBoxLayout())
|
||||
{
|
||||
@@ -859,14 +873,6 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
_randomNumberStart->setMaximum(9999999999);
|
||||
_randomNumberEnd->setMinimum(-9999999999);
|
||||
_randomNumberEnd->setMaximum(9999999999);
|
||||
const QString path = GetThemeTypeName() == "Light"
|
||||
? ":/res/images/help.svg"
|
||||
: ":/res/images/help_light.svg";
|
||||
const QIcon icon(path);
|
||||
const QPixmap pixmap = icon.pixmap(QSize(16, 16));
|
||||
_jsonQueryHelp->setPixmap(pixmap);
|
||||
_jsonQueryHelp->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.action.variable.type.queryJson.info"));
|
||||
_jsonIndex->setMaximum(999);
|
||||
|
||||
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
|
||||
@@ -1525,6 +1531,7 @@ void MacroActionVariableEdit::SetWidgetVisibility()
|
||||
}
|
||||
|
||||
_variables2->setVisible(
|
||||
_entryData->_action == MacroActionVariable::Action::COPY_VAR ||
|
||||
_entryData->_action ==
|
||||
MacroActionVariable::Action::APPEND_VAR ||
|
||||
_entryData->_action ==
|
||||
|
||||
@@ -55,6 +55,7 @@ public:
|
||||
RANDOM_NUMBER,
|
||||
QUERY_JSON,
|
||||
ARRAY_JSON,
|
||||
COPY_VAR,
|
||||
};
|
||||
|
||||
Action _action = Action::SET_VALUE;
|
||||
|
||||
@@ -239,7 +239,7 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
|
||||
(*_entryData)->SetIndex(idx);
|
||||
(*_entryData)->SetLogicType(logic);
|
||||
(*_entryData)->PostLoad();
|
||||
RunPostLoadSteps();
|
||||
RunAndClearPostLoadSteps();
|
||||
}
|
||||
auto widget =
|
||||
MacroConditionFactory::CreateWidget(id, this, *_entryData);
|
||||
|
||||
@@ -102,7 +102,7 @@ bool MacroConditionMacro::CheckActionStateCondition()
|
||||
if (!macro) {
|
||||
return false;
|
||||
}
|
||||
if (!IsValidMacroSegmentIndex(macro.get(), _actionIndex - 1, false)) {
|
||||
if (!IsValidActionIndex(macro.get(), _actionIndex - 1)) {
|
||||
return false;
|
||||
}
|
||||
if (_type == Type::ACTION_DISABLED) {
|
||||
|
||||
@@ -110,7 +110,7 @@ void MacroDock::RunClicked()
|
||||
return;
|
||||
}
|
||||
|
||||
auto ret = macro->PerformActions(true);
|
||||
auto ret = macro->PerformActions(true, true, true);
|
||||
if (!ret) {
|
||||
QString err =
|
||||
obs_module_text("AdvSceneSwitcher.macroTab.runFail");
|
||||
|
||||
@@ -1080,7 +1080,7 @@ void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id,
|
||||
macro->Actions().at(idx)->Load(data);
|
||||
}
|
||||
macro->Actions().at(idx)->PostLoad();
|
||||
RunPostLoadSteps();
|
||||
RunAndClearPostLoadSteps();
|
||||
macro->UpdateActionIndices();
|
||||
ui->actionsList->Insert(
|
||||
idx,
|
||||
@@ -1383,7 +1383,7 @@ void MacroEdit::AddMacroElseAction(Macro *macro, int idx, const std::string &id,
|
||||
macro->ElseActions().at(idx)->Load(data);
|
||||
}
|
||||
macro->ElseActions().at(idx)->PostLoad();
|
||||
RunPostLoadSteps();
|
||||
RunAndClearPostLoadSteps();
|
||||
macro->UpdateElseActionIndices();
|
||||
ui->elseActionsList->Insert(
|
||||
idx, new MacroActionEdit(
|
||||
@@ -1583,7 +1583,7 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
|
||||
macro->Conditions().at(idx)->Load(data);
|
||||
}
|
||||
macro->Conditions().at(idx)->PostLoad();
|
||||
RunPostLoadSteps();
|
||||
RunAndClearPostLoadSteps();
|
||||
(*cond)->SetLogicType(logic);
|
||||
macro->UpdateConditionIndices();
|
||||
ui->conditionsList->Insert(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "macro-helpers.hpp"
|
||||
#include "macro.hpp"
|
||||
#include "macro-action-macro.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
|
||||
namespace advss {
|
||||
@@ -8,6 +9,60 @@ static std::atomic_bool abortMacroWait = {false};
|
||||
static std::atomic_bool macroSceneSwitched = {false};
|
||||
static std::atomic_int shutdownConditionCount = {0};
|
||||
|
||||
static void appendNestedMacros(std::deque<std::shared_ptr<Macro>> ¯os,
|
||||
Macro *macro)
|
||||
{
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto iterate = [¯os](const std::deque<
|
||||
std::shared_ptr<MacroAction>> &actions) {
|
||||
for (const auto &action : actions) {
|
||||
const auto nestedMacroAction =
|
||||
dynamic_cast<MacroActionMacro *>(action.get());
|
||||
if (!nestedMacroAction) {
|
||||
continue;
|
||||
}
|
||||
|
||||
macros.push_back(nestedMacroAction->_nestedMacro);
|
||||
appendNestedMacros(
|
||||
macros, nestedMacroAction->_nestedMacro.get());
|
||||
}
|
||||
};
|
||||
|
||||
iterate(macro->Actions());
|
||||
iterate(macro->ElseActions());
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<Macro>> &GetTopLevelMacros()
|
||||
{
|
||||
static std::deque<std::shared_ptr<Macro>> macros;
|
||||
return macros;
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<Macro>> &GetTemporaryMacros()
|
||||
{
|
||||
static std::deque<std::shared_ptr<Macro>> tempMacros;
|
||||
return tempMacros;
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<Macro>> GetAllMacros()
|
||||
{
|
||||
auto macros = GetTopLevelMacros();
|
||||
for (const auto &topLevelMacro : macros) {
|
||||
appendNestedMacros(macros, topLevelMacro.get());
|
||||
}
|
||||
|
||||
const auto &tempMacros = GetTemporaryMacros();
|
||||
macros.insert(macros.end(), tempMacros.begin(), tempMacros.end());
|
||||
for (const auto &tempMacro : tempMacros) {
|
||||
appendNestedMacros(macros, tempMacro.get());
|
||||
}
|
||||
|
||||
return macros;
|
||||
}
|
||||
|
||||
std::optional<std::deque<std::shared_ptr<MacroAction>>>
|
||||
GetMacroActions(Macro *macro)
|
||||
{
|
||||
@@ -17,6 +72,15 @@ GetMacroActions(Macro *macro)
|
||||
return macro->Actions();
|
||||
}
|
||||
|
||||
std::optional<std::deque<std::shared_ptr<MacroAction>>>
|
||||
GetMacroElseActions(Macro *macro)
|
||||
{
|
||||
if (!macro) {
|
||||
return {};
|
||||
}
|
||||
return macro->ElseActions();
|
||||
}
|
||||
|
||||
std::optional<std::deque<std::shared_ptr<MacroCondition>>>
|
||||
GetMacroConditions(Macro *macro)
|
||||
{
|
||||
@@ -144,20 +208,42 @@ void ResetMacroRunCount(Macro *macro)
|
||||
macro->ResetRunCount();
|
||||
}
|
||||
|
||||
bool IsValidMacroSegmentIndex(const Macro *m, const int idx, bool isCondition)
|
||||
bool IsValidActionIndex(const Macro *m, const int idx)
|
||||
{
|
||||
if (!m || idx < 0) {
|
||||
return false;
|
||||
}
|
||||
if (isCondition) {
|
||||
if (idx >= (int)m->Conditions().size()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (idx >= (int)m->Actions().size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (idx >= (int)m->Actions().size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsValidElseActionIndex(const Macro *m, const int idx)
|
||||
{
|
||||
if (!m || idx < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (idx >= (int)m->ElseActions().size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsValidConditionIndex(const Macro *m, const int idx)
|
||||
{
|
||||
if (!m || idx < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (idx >= (int)m->Conditions().size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,14 @@ class Macro;
|
||||
class MacroAction;
|
||||
class MacroCondition;
|
||||
|
||||
EXPORT std::deque<std::shared_ptr<Macro>> &GetMacros();
|
||||
EXPORT std::deque<std::shared_ptr<Macro>> &GetTopLevelMacros();
|
||||
std::deque<std::shared_ptr<Macro>> &GetTemporaryMacros();
|
||||
EXPORT std::deque<std::shared_ptr<Macro>> GetAllMacros();
|
||||
|
||||
EXPORT std::optional<std::deque<std::shared_ptr<MacroAction>>>
|
||||
GetMacroActions(Macro *);
|
||||
EXPORT std::optional<std::deque<std::shared_ptr<MacroAction>>>
|
||||
GetMacroElseActions(Macro *);
|
||||
EXPORT std::optional<std::deque<std::shared_ptr<MacroCondition>>>
|
||||
GetMacroConditions(Macro *);
|
||||
|
||||
@@ -68,6 +72,8 @@ EXPORT void InvalidateMacroTempVarValues();
|
||||
EXPORT void ResetMacroConditionTimers(Macro *);
|
||||
EXPORT void ResetMacroRunCount(Macro *);
|
||||
|
||||
bool IsValidMacroSegmentIndex(const Macro *m, const int idx, bool isCondition);
|
||||
bool IsValidActionIndex(const Macro *m, const int idx);
|
||||
bool IsValidElseActionIndex(const Macro *m, const int idx);
|
||||
bool IsValidConditionIndex(const Macro *m, const int idx);
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -73,35 +73,46 @@ void MacroSegmentSelection::MacroSegmentOrderChanged()
|
||||
}
|
||||
|
||||
static QString GetMacroSegmentDescription(Macro *macro, int idx,
|
||||
bool isCondition)
|
||||
MacroSegmentSelection::Type type)
|
||||
{
|
||||
if (!macro) {
|
||||
return "";
|
||||
}
|
||||
if (!IsValidMacroSegmentIndex(macro, idx, isCondition)) {
|
||||
|
||||
MacroSegment *segment;
|
||||
|
||||
switch (type) {
|
||||
case MacroSegmentSelection::Type::CONDITION:
|
||||
if (!IsValidConditionIndex(macro, idx)) {
|
||||
return "";
|
||||
}
|
||||
segment = GetMacroConditions(macro)->at(idx).get();
|
||||
break;
|
||||
case MacroSegmentSelection::Type::ACTION:
|
||||
if (!IsValidActionIndex(macro, idx)) {
|
||||
return "";
|
||||
}
|
||||
segment = GetMacroActions(macro)->at(idx).get();
|
||||
break;
|
||||
case MacroSegmentSelection::Type::ELSE_ACTION:
|
||||
if (!IsValidElseActionIndex(macro, idx)) {
|
||||
return "";
|
||||
}
|
||||
segment = GetMacroElseActions(macro)->at(idx).get();
|
||||
break;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
MacroSegment *segment;
|
||||
if (isCondition) {
|
||||
segment = GetMacroConditions(macro)->at(idx).get();
|
||||
} else {
|
||||
segment = GetMacroActions(macro)->at(idx).get();
|
||||
}
|
||||
const auto idToName = type == MacroSegmentSelection::Type::CONDITION
|
||||
? MacroConditionFactory::GetConditionName
|
||||
: MacroActionFactory::GetActionName;
|
||||
const QString typeStr =
|
||||
obs_module_text(idToName(segment->GetId()).c_str());
|
||||
const QString description =
|
||||
QString::fromStdString(segment->GetShortDesc());
|
||||
|
||||
QString description = QString::fromStdString(segment->GetShortDesc());
|
||||
QString type;
|
||||
if (isCondition) {
|
||||
type = obs_module_text(MacroConditionFactory::GetConditionName(
|
||||
segment->GetId())
|
||||
.c_str());
|
||||
} else {
|
||||
type = obs_module_text(
|
||||
MacroActionFactory::GetActionName(segment->GetId())
|
||||
.c_str());
|
||||
}
|
||||
|
||||
QString result = type;
|
||||
QString result = typeStr;
|
||||
if (!description.isEmpty()) {
|
||||
result += ": " + description;
|
||||
}
|
||||
@@ -123,11 +134,25 @@ void MacroSegmentSelection::SetupDescription() const
|
||||
return;
|
||||
}
|
||||
|
||||
bool validIndex = false;
|
||||
switch (_type) {
|
||||
case Type::CONDITION:
|
||||
validIndex = IsValidConditionIndex(_macro, index - 1);
|
||||
break;
|
||||
case Type::ACTION:
|
||||
validIndex = IsValidActionIndex(_macro, index - 1);
|
||||
break;
|
||||
case Type::ELSE_ACTION:
|
||||
validIndex = IsValidElseActionIndex(_macro, index - 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
QString description;
|
||||
if (IsValidMacroSegmentIndex(_macro, index - 1,
|
||||
_type == Type::CONDITION)) {
|
||||
description = GetMacroSegmentDescription(
|
||||
_macro, index - 1, _type == Type::CONDITION);
|
||||
if (validIndex) {
|
||||
description =
|
||||
GetMacroSegmentDescription(_macro, index - 1, _type);
|
||||
} else {
|
||||
description = obs_module_text(
|
||||
"AdvSceneSwitcher.macroSegmentSelection.invalid");
|
||||
@@ -165,8 +190,23 @@ void MacroSegmentSelection::MarkSelectedSegment()
|
||||
if (index.GetType() == IntVariable::Type::VARIABLE) {
|
||||
return;
|
||||
}
|
||||
if (!IsValidMacroSegmentIndex(_macro, index - 1,
|
||||
_type == Type::CONDITION)) {
|
||||
|
||||
bool validIndex = false;
|
||||
switch (_type) {
|
||||
case Type::CONDITION:
|
||||
validIndex = IsValidConditionIndex(_macro, index - 1);
|
||||
break;
|
||||
case Type::ACTION:
|
||||
validIndex = IsValidActionIndex(_macro, index - 1);
|
||||
break;
|
||||
case Type::ELSE_ACTION:
|
||||
validIndex = IsValidElseActionIndex(_macro, index - 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!validIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ class Macro;
|
||||
class MacroSegmentSelection : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class Type { CONDITION, ACTION };
|
||||
enum class Type { CONDITION, ACTION, ELSE_ACTION };
|
||||
|
||||
MacroSegmentSelection(QWidget *parent, Type type,
|
||||
bool allowVariables = true);
|
||||
|
||||
@@ -14,7 +14,7 @@ MacroSelection::MacroSelection(QWidget *parent)
|
||||
: FilterComboBox(parent,
|
||||
obs_module_text("AdvSceneSwitcher.selectMacro"))
|
||||
{
|
||||
for (const auto &m : GetMacros()) {
|
||||
for (const auto &m : GetTopLevelMacros()) {
|
||||
if (m->IsGroup()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ static void addGroupSubitems(std::vector<std::shared_ptr<Macro>> ¯os,
|
||||
subitems.reserve(group->GroupSize());
|
||||
|
||||
// Find all subitems
|
||||
auto allMacros = GetMacros();
|
||||
auto allMacros = GetTopLevelMacros();
|
||||
for (auto it = allMacros.begin(); it < allMacros.end(); it++) {
|
||||
if ((*it)->Name() != group->Name()) {
|
||||
continue;
|
||||
@@ -410,13 +410,17 @@ void AdvSceneSwitcher::ImportMacros()
|
||||
int groupSize = 0;
|
||||
std::shared_ptr<Macro> group;
|
||||
std::vector<std::shared_ptr<Macro>> importedMacros;
|
||||
auto &tempMacros = GetTemporaryMacros();
|
||||
|
||||
auto lock = LockContext();
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
tempMacros.clear();
|
||||
|
||||
OBSDataAutoRelease array_obj = obs_data_array_item(array, i);
|
||||
auto macro = std::make_shared<Macro>();
|
||||
tempMacros.emplace_back(macro);
|
||||
macro->Load(array_obj);
|
||||
RunPostLoadSteps();
|
||||
RunAndClearPostLoadSteps();
|
||||
|
||||
if (macroNameExists(macro->Name()) &&
|
||||
!ResolveMacroImportNameConflict(macro)) {
|
||||
@@ -425,7 +429,7 @@ void AdvSceneSwitcher::ImportMacros()
|
||||
}
|
||||
|
||||
importedMacros.emplace_back(macro);
|
||||
GetMacros().emplace_back(macro);
|
||||
GetTopLevelMacros().emplace_back(macro);
|
||||
if (groupSize > 0 && !macro->IsGroup()) {
|
||||
Macro::PrepareMoveToGroup(group, macro);
|
||||
groupSize--;
|
||||
@@ -444,9 +448,10 @@ void AdvSceneSwitcher::ImportMacros()
|
||||
for (const auto ¯o : importedMacros) {
|
||||
macro->PostLoad();
|
||||
}
|
||||
RunPostLoadSteps();
|
||||
RunAndClearPostLoadSteps();
|
||||
tempMacros.clear();
|
||||
|
||||
ui->macros->Reset(GetMacros(),
|
||||
ui->macros->Reset(GetTopLevelMacros(),
|
||||
GetGlobalMacroSettings()._highlightExecuted);
|
||||
disableAddButtonHighlight();
|
||||
}
|
||||
@@ -633,7 +638,7 @@ void AdvSceneSwitcher::SetupMacroTab()
|
||||
{
|
||||
ui->macros->installEventFilter(this);
|
||||
|
||||
if (GetMacros().size() == 0 && !switcher->disableHints) {
|
||||
if (GetTopLevelMacros().size() == 0 && !switcher->disableHints) {
|
||||
addPulse = HighlightWidget(ui->macroAdd, QColor(Qt::green));
|
||||
}
|
||||
|
||||
@@ -641,7 +646,7 @@ void AdvSceneSwitcher::SetupMacroTab()
|
||||
{ui->macroUp, ui->macroDown}});
|
||||
ui->macroControlLayout->addWidget(macroControls);
|
||||
|
||||
ui->macros->Reset(GetMacros(),
|
||||
ui->macros->Reset(GetTopLevelMacros(),
|
||||
GetGlobalMacroSettings()._highlightExecuted);
|
||||
connect(ui->macros, SIGNAL(MacroSelectionChanged()), this,
|
||||
SLOT(MacroSelectionChanged()));
|
||||
@@ -752,7 +757,7 @@ void AdvSceneSwitcher::CopyMacro()
|
||||
newMacro->Load(data);
|
||||
newMacro->PostLoad();
|
||||
newMacro->SetName(name);
|
||||
RunPostLoadSteps();
|
||||
RunAndClearPostLoadSteps();
|
||||
Macro::PrepareMoveToGroup(macro->Parent(), newMacro);
|
||||
|
||||
ui->macros->Add(newMacro, macro);
|
||||
|
||||
@@ -1184,7 +1184,7 @@ bool MacroTree::SelectionEmpty() const
|
||||
|
||||
bool MacroTree::GroupsExist() const
|
||||
{
|
||||
for (const auto ¯o : GetMacros()) {
|
||||
for (const auto ¯o : GetTopLevelMacros()) {
|
||||
if (macro->IsGroup()) {
|
||||
return true;
|
||||
}
|
||||
@@ -1306,7 +1306,7 @@ void MacroTree::UngroupSelectedGroups()
|
||||
|
||||
void MacroTree::ExpandAll()
|
||||
{
|
||||
for (const auto ¯o : GetMacros()) {
|
||||
for (const auto ¯o : GetTopLevelMacros()) {
|
||||
if (!macro->IsGroup()) {
|
||||
continue;
|
||||
}
|
||||
@@ -1316,7 +1316,7 @@ void MacroTree::ExpandAll()
|
||||
|
||||
void MacroTree::CollapseAll()
|
||||
{
|
||||
for (const auto ¯o : GetMacros()) {
|
||||
for (const auto ¯o : GetTopLevelMacros()) {
|
||||
if (!macro->IsGroup()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
static std::deque<std::shared_ptr<Macro>> macros;
|
||||
|
||||
Macro::Macro(const std::string &name) : _dockSettings(this)
|
||||
{
|
||||
SetName(name);
|
||||
@@ -59,6 +57,7 @@ Macro::CreateGroup(const std::string &name,
|
||||
|
||||
void Macro::RemoveGroup(std::shared_ptr<Macro> group)
|
||||
{
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
auto it = std::find(macros.begin(), macros.end(), group);
|
||||
if (it == macros.end()) {
|
||||
return;
|
||||
@@ -75,6 +74,7 @@ void Macro::RemoveGroup(std::shared_ptr<Macro> group)
|
||||
|
||||
void Macro::PrepareMoveToGroup(Macro *group, std::shared_ptr<Macro> item)
|
||||
{
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
for (const auto &m : macros) {
|
||||
if (m.get() == group) {
|
||||
PrepareMoveToGroup(m, item);
|
||||
@@ -1114,7 +1114,7 @@ void Macro::SetHotkeysDesc() const
|
||||
void SaveMacros(obs_data_t *obj)
|
||||
{
|
||||
obs_data_array_t *macroArray = obs_data_array_create();
|
||||
for (const auto &m : macros) {
|
||||
for (const auto &m : GetTopLevelMacros()) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
|
||||
m->Save(array_obj);
|
||||
@@ -1128,7 +1128,9 @@ void SaveMacros(obs_data_t *obj)
|
||||
|
||||
void LoadMacros(obs_data_t *obj)
|
||||
{
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
macros.clear();
|
||||
|
||||
obs_data_array_t *macroArray = obs_data_get_array(obj, "macros");
|
||||
size_t count = obs_data_array_count(macroArray);
|
||||
|
||||
@@ -1178,15 +1180,10 @@ void LoadMacros(obs_data_t *obj)
|
||||
}
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<Macro>> &GetMacros()
|
||||
{
|
||||
return macros;
|
||||
}
|
||||
|
||||
bool CheckMacros()
|
||||
{
|
||||
bool matchFound = false;
|
||||
for (const auto &m : macros) {
|
||||
for (const auto &m : GetTopLevelMacros()) {
|
||||
if (!m->ConditionsShouldBeChecked()) {
|
||||
vblog(LOG_INFO,
|
||||
"skipping condition check for macro \"%s\" "
|
||||
@@ -1213,7 +1210,7 @@ bool RunMacros()
|
||||
// reordered while macros are currently being executed.
|
||||
// For example, this can happen if a macro is performing a wait action,
|
||||
// as the main lock will be unlocked during this time.
|
||||
auto runPhaseMacros = macros;
|
||||
auto runPhaseMacros = GetTopLevelMacros();
|
||||
|
||||
// Avoid deadlocks when opening settings window and calling frontend
|
||||
// API functions at the same time.
|
||||
@@ -1253,14 +1250,14 @@ bool RunMacros()
|
||||
|
||||
void StopAllMacros()
|
||||
{
|
||||
for (const auto &m : macros) {
|
||||
for (const auto &m : GetAllMacros()) {
|
||||
m->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
Macro *GetMacroByName(const char *name)
|
||||
{
|
||||
for (const auto &m : macros) {
|
||||
for (const auto &m : GetTopLevelMacros()) {
|
||||
if (m->Name() == name) {
|
||||
return m.get();
|
||||
}
|
||||
@@ -1276,7 +1273,7 @@ Macro *GetMacroByQString(const QString &name)
|
||||
|
||||
std::weak_ptr<Macro> GetWeakMacroByName(const char *name)
|
||||
{
|
||||
for (const auto &m : macros) {
|
||||
for (const auto &m : GetTopLevelMacros()) {
|
||||
if (m->Name() == name) {
|
||||
return m;
|
||||
}
|
||||
@@ -1287,7 +1284,7 @@ std::weak_ptr<Macro> GetWeakMacroByName(const char *name)
|
||||
|
||||
void InvalidateMacroTempVarValues()
|
||||
{
|
||||
for (const auto &m : macros) {
|
||||
for (const auto &m : GetTopLevelMacros()) {
|
||||
// Do not invalidate the temp vars set during condition checks
|
||||
// or action executions running in parallel to the "main" macro
|
||||
// loop, as otherwise access to the information stored in those
|
||||
@@ -1302,6 +1299,8 @@ void InvalidateMacroTempVarValues()
|
||||
|
||||
std::shared_ptr<Macro> GetMacroWithInvalidConditionInterval()
|
||||
{
|
||||
auto ¯os = GetTopLevelMacros();
|
||||
|
||||
if (macros.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -217,7 +217,6 @@ private:
|
||||
|
||||
void LoadMacros(obs_data_t *obj);
|
||||
void SaveMacros(obs_data_t *obj);
|
||||
std::deque<std::shared_ptr<Macro>> &GetMacros();
|
||||
bool CheckMacros();
|
||||
bool RunMacros();
|
||||
void StopAllMacros();
|
||||
|
||||
@@ -113,7 +113,7 @@ void ActionQueue::Add(const std::shared_ptr<MacroAction> &action)
|
||||
action->Save(data);
|
||||
copy->Load(data);
|
||||
copy->PostLoad();
|
||||
RunPostLoadSteps();
|
||||
RunAndClearPostLoadSteps();
|
||||
copy->ResolveVariablesToFixedValues();
|
||||
_actions.emplace_back(copy);
|
||||
} else {
|
||||
@@ -423,7 +423,7 @@ void ImportQueues(obs_data_t *data)
|
||||
importedQueues->emplace_back(queue);
|
||||
}
|
||||
|
||||
QeueUITask(signalImportedQueues, importedQueues);
|
||||
QueueUITask(signalImportedQueues, importedQueues);
|
||||
}
|
||||
|
||||
std::weak_ptr<ActionQueue> GetWeakActionQueueByName(const std::string &name)
|
||||
|
||||
@@ -2,31 +2,31 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
AutoUpdateTooltipLabel::AutoUpdateTooltipLabel(
|
||||
AutoUpdateHelpIcon::AutoUpdateHelpIcon(
|
||||
QWidget *parent, const std::function<QString()> &updateTooltipCallback,
|
||||
int updateIntervalMs)
|
||||
: QLabel(parent),
|
||||
: HelpIcon("", parent),
|
||||
_callback(updateTooltipCallback),
|
||||
_timer(new QTimer(this)),
|
||||
_updateIntervalMs(updateIntervalMs)
|
||||
{
|
||||
connect(_timer, &QTimer::timeout, this,
|
||||
&AutoUpdateTooltipLabel::UpdateTooltip);
|
||||
&AutoUpdateHelpIcon::UpdateTooltip);
|
||||
}
|
||||
|
||||
void AutoUpdateTooltipLabel::enterEvent(QEnterEvent *event)
|
||||
void AutoUpdateHelpIcon::enterEvent(QEnterEvent *event)
|
||||
{
|
||||
_timer->start(_updateIntervalMs);
|
||||
QLabel::enterEvent(event);
|
||||
}
|
||||
|
||||
void AutoUpdateTooltipLabel::leaveEvent(QEvent *event)
|
||||
void AutoUpdateHelpIcon::leaveEvent(QEvent *event)
|
||||
{
|
||||
_timer->stop();
|
||||
QLabel::leaveEvent(event);
|
||||
}
|
||||
|
||||
void AutoUpdateTooltipLabel::UpdateTooltip()
|
||||
void AutoUpdateHelpIcon::UpdateTooltip()
|
||||
{
|
||||
setToolTip(_callback());
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
#pragma once
|
||||
#include "export-symbol-helper.hpp"
|
||||
#include "help-icon.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <QLabel>
|
||||
#include <QTimer>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class ADVSS_EXPORT AutoUpdateTooltipLabel : public QLabel {
|
||||
class ADVSS_EXPORT AutoUpdateHelpIcon : public HelpIcon {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AutoUpdateTooltipLabel(
|
||||
QWidget *parent,
|
||||
const std::function<QString()> &updateTooltipCallback,
|
||||
int updateIntervalMs = 300);
|
||||
AutoUpdateHelpIcon(QWidget *parent,
|
||||
const std::function<QString()> &updateTooltipCallback,
|
||||
int updateIntervalMs = 300);
|
||||
|
||||
protected:
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
|
||||
@@ -35,6 +35,11 @@ void FileSelection::SetPath(const QString &path)
|
||||
_filePath->setText(path);
|
||||
}
|
||||
|
||||
QString FileSelection::GetPath() const
|
||||
{
|
||||
return _filePath->text();
|
||||
}
|
||||
|
||||
QString FileSelection::ValidPathOrDesktop(const QString &path)
|
||||
{
|
||||
QFileInfo fileInfo(path);
|
||||
|
||||
@@ -22,6 +22,7 @@ public:
|
||||
QWidget *parent = 0);
|
||||
EXPORT void SetPath(const StringVariable &);
|
||||
EXPORT void SetPath(const QString &);
|
||||
EXPORT QString GetPath() const;
|
||||
EXPORT QPushButton *Button() { return _browseButton; }
|
||||
EXPORT static QString ValidPathOrDesktop(const QString &path);
|
||||
|
||||
|
||||
@@ -113,12 +113,13 @@ void RunLoadSteps(obs_data_t *obj)
|
||||
}
|
||||
}
|
||||
|
||||
void RunPostLoadSteps()
|
||||
void RunAndClearPostLoadSteps()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(postLoadMutex);
|
||||
for (const auto &func : getPostLoadSteps()) {
|
||||
func();
|
||||
}
|
||||
getPostLoadSteps().clear();
|
||||
}
|
||||
|
||||
void ClearPostLoadSteps()
|
||||
|
||||
@@ -14,7 +14,7 @@ EXPORT void AddPostLoadStep(std::function<void()>);
|
||||
EXPORT void AddIntervalResetStep(std::function<void()>);
|
||||
void RunSaveSteps(obs_data_t *);
|
||||
void RunLoadSteps(obs_data_t *);
|
||||
EXPORT void RunPostLoadSteps();
|
||||
EXPORT void RunAndClearPostLoadSteps();
|
||||
void ClearPostLoadSteps();
|
||||
|
||||
EXPORT void AddPluginInitStep(std::function<void()>);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
ResourceTable::ResourceTable(QTabWidget *parent, const QString &help,
|
||||
ResourceTable::ResourceTable(QWidget *parent, const QString &help,
|
||||
const QString &addToolTip,
|
||||
const QString &removeToolTip,
|
||||
const QStringList &headers,
|
||||
|
||||
@@ -14,7 +14,7 @@ class ADVSS_EXPORT ResourceTable : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ResourceTable(QTabWidget *parent, const QString &help,
|
||||
ResourceTable(QWidget *parent, const QString &help,
|
||||
const QString &addToolTip, const QString &removeToolTip,
|
||||
const QStringList &headers,
|
||||
const std::function<void()> &openSettings);
|
||||
|
||||
@@ -239,22 +239,26 @@ void SceneSelection::ResolveVariables()
|
||||
_type = Type::SCENE;
|
||||
}
|
||||
|
||||
SceneSelection SceneSelectionWidget::CurrentSelection()
|
||||
static obs_weak_canvas_t *getWeakRefToMainCanvas()
|
||||
{
|
||||
SceneSelection s;
|
||||
|
||||
static auto mainCanvas = obs_get_main_canvas();
|
||||
static auto mainCanvasWeak = obs_canvas_get_weak_canvas(mainCanvas);
|
||||
static auto canvas = obs_get_main_canvas();
|
||||
static auto weakCanvas = obs_canvas_get_weak_canvas(canvas);
|
||||
[[maybe_unused]] static const bool _ = []() {
|
||||
// Let's just hope we don't have to deal with selecting scenes
|
||||
// when the OBS main canvas gets deleted and release the
|
||||
// references here already to avoid reporting leaks on shutdown
|
||||
obs_canvas_release(mainCanvas);
|
||||
obs_weak_canvas_release(mainCanvasWeak);
|
||||
obs_canvas_release(canvas);
|
||||
obs_weak_canvas_release(weakCanvas);
|
||||
return true;
|
||||
}();
|
||||
return weakCanvas;
|
||||
}
|
||||
|
||||
s._canvas = _forceMainCanvas ? OBSWeakCanvas(mainCanvasWeak)
|
||||
SceneSelection SceneSelectionWidget::CurrentSelection()
|
||||
{
|
||||
SceneSelection s;
|
||||
|
||||
s._canvas = _forceMainCanvas ? OBSWeakCanvas(getWeakRefToMainCanvas())
|
||||
: _canvas->GetCanvas();
|
||||
|
||||
const int idx = _scenes->currentIndex();
|
||||
@@ -330,6 +334,7 @@ static QStringList getCanvasScenesList(obs_weak_canvas_t *weakCanvas)
|
||||
|
||||
QStringList list;
|
||||
obs_canvas_enum_scenes(canvas, enumCanvasScenes, &list);
|
||||
list.sort();
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -342,6 +347,10 @@ void SceneSelectionWidget::Reset()
|
||||
|
||||
void SceneSelectionWidget::PopulateSceneSelection(obs_weak_canvas_t *canvas)
|
||||
{
|
||||
if (_forceMainCanvas) {
|
||||
canvas = getWeakRefToMainCanvas();
|
||||
}
|
||||
|
||||
_scenes->clear();
|
||||
if ((_current || _previous)) {
|
||||
const bool isMain = IsMainCanvas(canvas);
|
||||
@@ -418,7 +427,7 @@ SceneSelectionWidget::SceneSelectionWidget(QWidget *parent, bool variables,
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
if (GetCanvasCount() <= 1) {
|
||||
_canvas->hide();
|
||||
LockToMainCanvas();
|
||||
}
|
||||
|
||||
Resize();
|
||||
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
bool previous = false, bool current = false,
|
||||
bool preview = false);
|
||||
EXPORT void SetScene(const SceneSelection &);
|
||||
void LockToMainCanvas();
|
||||
EXPORT void LockToMainCanvas();
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
@@ -199,7 +199,17 @@ std::chrono::high_resolution_clock::time_point GetLastSceneChangeTime()
|
||||
|
||||
OBSWeakSource GetCurrentScene()
|
||||
{
|
||||
return switcher->currentScene;
|
||||
if (switcher->currentScene) {
|
||||
return switcher->currentScene;
|
||||
}
|
||||
|
||||
// If there wasn't any scene switch yet switcher->currentScene will be
|
||||
// null and we must use obs_frontend_get_current_scene() instead
|
||||
OBSSourceAutoRelease currentSceneSource =
|
||||
obs_frontend_get_current_scene();
|
||||
OBSWeakSourceAutoRelease currentSceneWeakSource =
|
||||
obs_source_get_weak_source(currentSceneSource);
|
||||
return currentSceneWeakSource.Get();
|
||||
}
|
||||
|
||||
OBSWeakSource GetPreviousScene()
|
||||
|
||||
@@ -190,29 +190,6 @@ static bool isMatchingNestedMacroAction(MacroAction *action, const Macro *macro)
|
||||
return nestedMacroAction->_nestedMacro.get() == macro;
|
||||
}
|
||||
|
||||
static void appendNestedMacros(std::deque<std::shared_ptr<Macro>> ¯os,
|
||||
Macro *macro)
|
||||
{
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &action : macro->Actions()) {
|
||||
const auto nestedMacroAction =
|
||||
dynamic_cast<MacroActionMacro *>(action.get());
|
||||
if (nestedMacroAction) {
|
||||
macros.push_back(nestedMacroAction->_nestedMacro);
|
||||
}
|
||||
}
|
||||
for (const auto &action : macro->ElseActions()) {
|
||||
const auto nestedMacroAction =
|
||||
dynamic_cast<MacroActionMacro *>(action.get());
|
||||
if (nestedMacroAction) {
|
||||
macros.push_back(nestedMacroAction->_nestedMacro);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Macro *getParentMacro(const Macro *targetMacro,
|
||||
const std::vector<Macro *> ¯os)
|
||||
{
|
||||
@@ -263,8 +240,10 @@ static Macro *getParentMacro(const Macro *targetMacro,
|
||||
|
||||
static Macro *getParentMacro(const Macro *macro)
|
||||
{
|
||||
const auto &topLevelMacros = GetTopLevelMacros();
|
||||
std::vector<Macro *> macros;
|
||||
for (const auto ¯o : GetMacros()) {
|
||||
macros.reserve(topLevelMacros.size());
|
||||
for (const auto ¯o : topLevelMacros) {
|
||||
macros.emplace_back(macro.get());
|
||||
}
|
||||
return getParentMacro(macro, macros);
|
||||
@@ -299,9 +278,18 @@ void TempVariableRef::Save(obs_data_t *obj, Macro *macro,
|
||||
obs_data_set_obj(obj, name, data);
|
||||
}
|
||||
|
||||
void TempVariableRef::Load(obs_data_t *obj, Macro *macro, const char *name)
|
||||
void TempVariableRef::Load(obs_data_t *obj, Macro *macroPtr, const char *name)
|
||||
{
|
||||
if (!macro) {
|
||||
const auto macros = GetAllMacros();
|
||||
std::weak_ptr<Macro> macro;
|
||||
for (const auto ¯oShared : macros) {
|
||||
if (macroShared.get() == macroPtr) {
|
||||
macro = macroShared;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (macro.expired()) {
|
||||
_segment.reset();
|
||||
return;
|
||||
}
|
||||
@@ -324,8 +312,15 @@ void TempVariableRef::Load(obs_data_t *obj, Macro *macro, const char *name)
|
||||
});
|
||||
}
|
||||
|
||||
void TempVariableRef::PostLoad(int idx, SegmentType type, Macro *macro)
|
||||
void TempVariableRef::PostLoad(int idx, SegmentType type,
|
||||
const std::weak_ptr<Macro> &weakMacro)
|
||||
{
|
||||
auto childMacro = weakMacro.lock();
|
||||
if (!childMacro) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto macro = childMacro.get();
|
||||
for (int i = 0; i < _depth; i++) {
|
||||
macro = getParentMacro(macro);
|
||||
}
|
||||
@@ -404,9 +399,8 @@ TempVariableSelection::TempVariableSelection(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_selection(new FilterComboBox(
|
||||
this, obs_module_text("AdvSceneSwitcher.tempVar.select"))),
|
||||
_info(new AutoUpdateTooltipLabel(this, [this]() {
|
||||
return SetupInfoLabel();
|
||||
}))
|
||||
_info(new AutoUpdateHelpIcon(this,
|
||||
[this]() { return SetupInfoLabel(); }))
|
||||
|
||||
{
|
||||
MacroEdit *edit = findMacroEditParent(parent);
|
||||
@@ -416,12 +410,6 @@ TempVariableSelection::TempVariableSelection(QWidget *parent)
|
||||
_macroEdits.push_back(edit);
|
||||
}
|
||||
|
||||
QString path = GetThemeTypeName() == "Light"
|
||||
? ":/res/images/help.svg"
|
||||
: ":/res/images/help_light.svg";
|
||||
QIcon icon(path);
|
||||
QPixmap pixmap = icon.pixmap(QSize(16, 16));
|
||||
_info->setPixmap(pixmap);
|
||||
_info->hide();
|
||||
|
||||
_selection->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
@@ -478,15 +466,9 @@ void TempVariableSelection::MacroSegmentsChanged()
|
||||
SetVariable(currentSelection);
|
||||
}
|
||||
|
||||
void TempVariableSelection::SegmentTempVarsChanged(MacroSegment *segment)
|
||||
void TempVariableSelection::SegmentTempVarsChanged(MacroSegment *)
|
||||
{
|
||||
const auto currentSegment = GetSegment();
|
||||
const auto currentMacro = currentSegment ? currentSegment->GetMacro()
|
||||
: nullptr;
|
||||
const auto changeMacro = segment ? segment->GetMacro() : nullptr;
|
||||
if (currentMacro == changeMacro) {
|
||||
MacroSegmentsChanged();
|
||||
}
|
||||
MacroSegmentsChanged();
|
||||
}
|
||||
|
||||
void TempVariableSelection::HighlightChanged(int idx)
|
||||
|
||||
@@ -72,10 +72,10 @@ private:
|
||||
enum class SegmentType { NONE, CONDITION, ACTION, ELSEACTION };
|
||||
SegmentType GetType() const;
|
||||
int GetIdx() const;
|
||||
void PostLoad(int idx, SegmentType, Macro *);
|
||||
void PostLoad(int idx, SegmentType, const std::weak_ptr<Macro> &);
|
||||
|
||||
std::string _id = "";
|
||||
std::weak_ptr<MacroSegment> _segment;
|
||||
std::weak_ptr<MacroSegment> _segment = {};
|
||||
int _depth = 0;
|
||||
|
||||
friend TempVariable;
|
||||
@@ -105,7 +105,7 @@ private:
|
||||
MacroSegment *GetSegment() const;
|
||||
|
||||
FilterComboBox *_selection;
|
||||
AutoUpdateTooltipLabel *_info;
|
||||
AutoUpdateHelpIcon *_info;
|
||||
std::vector<MacroEdit *> _macroEdits;
|
||||
};
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ std::string GetThemeTypeName()
|
||||
#endif
|
||||
}
|
||||
|
||||
void QeueUITask(void (*task)(void *param), void *param)
|
||||
void QueueUITask(void (*task)(void *param), void *param)
|
||||
{
|
||||
obs_queue_task(OBS_TASK_UI, task, param, false);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ EXPORT void DisplayTrayMessage(const QString &title, const QString &msg,
|
||||
EXPORT std::string GetThemeTypeName();
|
||||
EXPORT QWidget *GetSettingsWindow();
|
||||
|
||||
void QeueUITask(void (*task)(void *param), void *param);
|
||||
EXPORT void QueueUITask(void (*task)(void *param), void *param);
|
||||
|
||||
bool IsCursorInWidgetArea(QWidget *widget);
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
#include "ui-helpers.hpp"
|
||||
#include "variable.hpp"
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include <QLayout>
|
||||
#include <QTimer>
|
||||
|
||||
namespace advss {
|
||||
@@ -16,14 +19,62 @@ static bool registerTab();
|
||||
static void setupTab(QTabWidget *);
|
||||
static bool registerTabDone = registerTab();
|
||||
|
||||
static VariableTable *tabWidget = nullptr;
|
||||
static VariableTable *dockWidget = nullptr;
|
||||
static bool addDock = false;
|
||||
|
||||
static VariableTable::Settings tabSettings;
|
||||
static VariableTable::Settings dockSettings;
|
||||
|
||||
static void save(obs_data_t *data)
|
||||
{
|
||||
tabSettings.Save(data, "tabSettings");
|
||||
dockSettings.Save(data, "dockSettings");
|
||||
obs_data_set_bool(data, "addVariablesDock", addDock);
|
||||
}
|
||||
|
||||
static void enableDock(bool enable)
|
||||
{
|
||||
if (OBSIsShuttingDown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
obs_frontend_remove_dock("advss-variable-dock");
|
||||
|
||||
addDock = enable;
|
||||
if (!addDock) {
|
||||
return;
|
||||
}
|
||||
|
||||
dockWidget = new VariableTable(dockSettings);
|
||||
dockWidget->HideDockOptions();
|
||||
if (obs_frontend_add_dock_by_id(
|
||||
"advss-variable-dock",
|
||||
obs_module_text("AdvSceneSwitcher.variableTab.title"),
|
||||
dockWidget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
blog(LOG_INFO, "failed to register variable dock!");
|
||||
dockWidget->deleteLater();
|
||||
dockWidget = nullptr;
|
||||
}
|
||||
|
||||
static void load(obs_data_t *data)
|
||||
{
|
||||
tabSettings.Load(data, "tabSettings");
|
||||
dockSettings.Load(data, "dockSettings");
|
||||
|
||||
enableDock(obs_data_get_bool(data, "addVariablesDock"));
|
||||
}
|
||||
|
||||
static bool registerTab()
|
||||
{
|
||||
AddPluginInitStep([]() {
|
||||
AddSetupTabCallback("variableTab", VariableTable::Create,
|
||||
setupTab);
|
||||
AddSetupTabCallback("variableTab",
|
||||
VariableTable::CreateTabTable, setupTab);
|
||||
});
|
||||
AddSaveStep(save);
|
||||
AddLoadStep(load);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -34,10 +85,14 @@ static void setTabVisible(QTabWidget *tabWidget, bool visible)
|
||||
obs_module_text("AdvSceneSwitcher.variableTab.title"));
|
||||
}
|
||||
|
||||
VariableTable *VariableTable::Create()
|
||||
VariableTable *VariableTable::CreateTabTable()
|
||||
{
|
||||
tabWidget = new VariableTable();
|
||||
return tabWidget;
|
||||
return new VariableTable(tabSettings);
|
||||
}
|
||||
|
||||
void VariableTable::HideDockOptions() const
|
||||
{
|
||||
_addDock->hide();
|
||||
}
|
||||
|
||||
void VariableTable::Add()
|
||||
@@ -163,15 +218,14 @@ static void updateVariableStatus(QTableWidget *table)
|
||||
}
|
||||
}
|
||||
|
||||
static void openSettingsDialog()
|
||||
static void openSettingsDialog(VariableTable *table)
|
||||
{
|
||||
auto selectedRows =
|
||||
tabWidget->Table()->selectionModel()->selectedRows();
|
||||
auto selectedRows = table->Table()->selectionModel()->selectedRows();
|
||||
if (selectedRows.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto cell = tabWidget->Table()->item(selectedRows.last().row(), 0);
|
||||
auto cell = table->Table()->item(selectedRows.last().row(), 0);
|
||||
if (!cell) {
|
||||
return;
|
||||
}
|
||||
@@ -194,15 +248,14 @@ static void openSettingsDialog()
|
||||
|
||||
void VariableTable::Remove()
|
||||
{
|
||||
auto selectedRows =
|
||||
tabWidget->Table()->selectionModel()->selectedRows();
|
||||
auto selectedRows = Table()->selectionModel()->selectedRows();
|
||||
if (selectedRows.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList varNames;
|
||||
for (const auto &row : selectedRows) {
|
||||
auto cell = tabWidget->Table()->item(row.row(), 0);
|
||||
auto cell = Table()->item(row.row(), 0);
|
||||
if (!cell) {
|
||||
continue;
|
||||
}
|
||||
@@ -235,7 +288,52 @@ void VariableTable::Remove()
|
||||
}
|
||||
}
|
||||
|
||||
VariableTable::VariableTable(QTabWidget *parent)
|
||||
void VariableTable::Filter()
|
||||
{
|
||||
const auto itemMatches = [&](const QTableWidgetItem *item) {
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_settings.searchString.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto text = item->text();
|
||||
|
||||
if (_settings.regex.Enabled()) {
|
||||
return _settings.regex.Matches(text.toStdString(),
|
||||
_settings.searchString);
|
||||
}
|
||||
|
||||
return text.contains(
|
||||
QString::fromStdString(_settings.searchString),
|
||||
Qt::CaseInsensitive);
|
||||
};
|
||||
|
||||
for (int row = 0; row < Table()->rowCount(); ++row) {
|
||||
bool match = false;
|
||||
|
||||
if (_settings.searchType == Settings::SearchType::ALL) {
|
||||
for (int col = 0; col < Table()->columnCount(); ++col) {
|
||||
if (itemMatches(Table()->item(row, col))) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (itemMatches(Table()->item(
|
||||
row,
|
||||
static_cast<int>(_settings.searchType)))) {
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
|
||||
Table()->setRowHidden(row, !match);
|
||||
}
|
||||
}
|
||||
|
||||
VariableTable::VariableTable(Settings &settings, QWidget *parent)
|
||||
: ResourceTable(
|
||||
parent, obs_module_text("AdvSceneSwitcher.variableTab.help"),
|
||||
obs_module_text(
|
||||
@@ -253,13 +351,113 @@ VariableTable::VariableTable(QTabWidget *parent)
|
||||
"AdvSceneSwitcher.variableTab.lastUsed.header")
|
||||
<< obs_module_text(
|
||||
"AdvSceneSwitcher.variableTab.lastChanged.header"),
|
||||
openSettingsDialog)
|
||||
[this]() { openSettingsDialog(this); }),
|
||||
_searchField(new QLineEdit(this)),
|
||||
_clear(new QPushButton(this)),
|
||||
_searchType(new QComboBox(this)),
|
||||
_regexWidget(new RegexConfigWidget(this)),
|
||||
_addDock(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.variableTab.addDock"))),
|
||||
_settings(settings)
|
||||
{
|
||||
_clear->setMaximumWidth(22);
|
||||
SetButtonIcon(_clear, GetThemeTypeName() == "Light"
|
||||
? "theme:Light/close.svg"
|
||||
: "theme:Dark/close.svg");
|
||||
_clear->setToolTip(
|
||||
obs_module_text("AdvSceneSwitcher.variableTab.clear"));
|
||||
_clear->setDisabled(_settings.searchString.empty());
|
||||
|
||||
connect(_clear, &QPushButton::clicked, this, [this]() {
|
||||
_searchField->setText("");
|
||||
_clear->setDisabled(true);
|
||||
});
|
||||
|
||||
for (const auto &variable : GetVariables()) {
|
||||
auto v = std::static_pointer_cast<Variable>(variable);
|
||||
AddItemTableRow(Table(), getCellLabels(v.get()));
|
||||
}
|
||||
|
||||
connect(Table(), &QTableWidget::itemChanged, this,
|
||||
[this]() { Filter(); });
|
||||
|
||||
_searchField->setPlaceholderText(obs_module_text(
|
||||
("AdvSceneSwitcher.variableTab.search.placeholder")));
|
||||
_searchField->setText(QString::fromStdString(_settings.searchString));
|
||||
connect(_searchField, &QLineEdit::textChanged, this,
|
||||
[this](const QString &text) {
|
||||
_settings.searchString = text.toStdString();
|
||||
_clear->setDisabled(text.isEmpty());
|
||||
Filter();
|
||||
});
|
||||
|
||||
_searchType->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.variableTab.search.all"),
|
||||
Settings::SearchType::ALL);
|
||||
_searchType->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.variableTab.search.name"),
|
||||
Settings::SearchType::NAME);
|
||||
_searchType->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.variableTab.search.value"),
|
||||
Settings::SearchType::VALUE);
|
||||
_searchType->setCurrentIndex(
|
||||
_searchType->findData(_settings.searchType));
|
||||
|
||||
connect(_searchType, &QComboBox::currentIndexChanged, this, [this]() {
|
||||
_settings.searchType = static_cast<Settings::SearchType>(
|
||||
_searchType->currentData().toInt());
|
||||
});
|
||||
|
||||
_regexWidget->SetRegexConfig(_settings.regex);
|
||||
connect(_regexWidget, &RegexConfigWidget::RegexConfigChanged, this,
|
||||
[this](const RegexConfig ®ex) {
|
||||
_settings.regex = regex;
|
||||
Filter();
|
||||
});
|
||||
|
||||
_addDock->setChecked(addDock);
|
||||
connect(_addDock, &QCheckBox::stateChanged, this,
|
||||
[this](int checked) { enableDock(checked); });
|
||||
|
||||
QWidget::connect(VariableSignalManager::Instance(),
|
||||
&VariableSignalManager::Rename, this,
|
||||
[this](const QString &oldName,
|
||||
const QString &newName) {
|
||||
RenameItemTableRow(Table(), oldName, newName);
|
||||
});
|
||||
QWidget::connect(
|
||||
VariableSignalManager::Instance(), &VariableSignalManager::Add,
|
||||
this, [this](const QString &name) {
|
||||
AddItemTableRow(
|
||||
Table(),
|
||||
getCellLabels(GetVariableByQString(name)));
|
||||
SetHelpVisible(false);
|
||||
HighlightAddButton(false);
|
||||
});
|
||||
QWidget::connect(VariableSignalManager::Instance(),
|
||||
&VariableSignalManager::Remove, this,
|
||||
[this](const QString &name) {
|
||||
RemoveItemTableRow(Table(), name);
|
||||
if (Table()->rowCount() == 0) {
|
||||
SetHelpVisible(true);
|
||||
HighlightAddButton(true);
|
||||
}
|
||||
});
|
||||
|
||||
auto timer = new QTimer(this);
|
||||
timer->setInterval(1000);
|
||||
QWidget::connect(timer, &QTimer::timeout,
|
||||
[this]() { updateVariableStatus(Table()); });
|
||||
timer->start();
|
||||
|
||||
auto searchLayout = new QHBoxLayout();
|
||||
searchLayout->addWidget(_searchField);
|
||||
searchLayout->addWidget(_clear);
|
||||
searchLayout->addWidget(_searchType);
|
||||
searchLayout->addWidget(_regexWidget);
|
||||
searchLayout->addWidget(_addDock);
|
||||
qobject_cast<QVBoxLayout *>(layout())->insertLayout(0, searchLayout);
|
||||
|
||||
SetHelpVisible(GetVariables().empty());
|
||||
}
|
||||
|
||||
@@ -269,37 +467,27 @@ static void setupTab(QTabWidget *tab)
|
||||
setTabVisible(tab, false);
|
||||
}
|
||||
|
||||
QWidget::connect(VariableSignalManager::Instance(),
|
||||
&VariableSignalManager::Rename, tab,
|
||||
[](const QString &oldName, const QString &newName) {
|
||||
RenameItemTableRow(tabWidget->Table(), oldName,
|
||||
newName);
|
||||
});
|
||||
QWidget::connect(
|
||||
VariableSignalManager::Instance(), &VariableSignalManager::Add,
|
||||
tab, [tab](const QString &name) {
|
||||
AddItemTableRow(
|
||||
tabWidget->Table(),
|
||||
getCellLabels(GetVariableByQString(name)));
|
||||
tabWidget->SetHelpVisible(false);
|
||||
tabWidget->HighlightAddButton(false);
|
||||
setTabVisible(tab, true);
|
||||
});
|
||||
QWidget::connect(VariableSignalManager::Instance(),
|
||||
&VariableSignalManager::Remove, tab,
|
||||
[](const QString &name) {
|
||||
RemoveItemTableRow(tabWidget->Table(), name);
|
||||
if (tabWidget->Table()->rowCount() == 0) {
|
||||
tabWidget->SetHelpVisible(true);
|
||||
tabWidget->HighlightAddButton(true);
|
||||
}
|
||||
});
|
||||
tab, [tab](const QString &name) { setTabVisible(tab, true); });
|
||||
}
|
||||
|
||||
auto timer = new QTimer(tabWidget);
|
||||
timer->setInterval(1000);
|
||||
QWidget::connect(timer, &QTimer::timeout,
|
||||
[]() { updateVariableStatus(tabWidget->Table()); });
|
||||
timer->start();
|
||||
void VariableTable::Settings::Save(obs_data_t *data, const char *name)
|
||||
{
|
||||
OBSDataAutoRelease settings = obs_data_create();
|
||||
obs_data_set_int(settings, "searchType", static_cast<int>(searchType));
|
||||
obs_data_set_string(settings, "searchString", searchString.c_str());
|
||||
regex.Save(settings);
|
||||
obs_data_set_obj(data, name, settings);
|
||||
}
|
||||
|
||||
void VariableTable::Settings::Load(obs_data_t *data, const char *name)
|
||||
{
|
||||
OBSDataAutoRelease settings = obs_data_get_obj(data, name);
|
||||
searchType = static_cast<SearchType>(
|
||||
obs_data_get_int(settings, "searchType"));
|
||||
searchString = obs_data_get_string(settings, "searchString");
|
||||
regex.Load(settings);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
#include "resource-table.hpp"
|
||||
#include "regex-config.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
@@ -7,14 +10,34 @@ class VariableTable final : public ResourceTable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static VariableTable *Create();
|
||||
struct Settings {
|
||||
void Save(obs_data_t *data, const char *name);
|
||||
void Load(obs_data_t *data, const char *name);
|
||||
|
||||
enum SearchType { ALL = -1, NAME = 0, VALUE = 1 };
|
||||
|
||||
SearchType searchType = SearchType::ALL;
|
||||
std::string searchString;
|
||||
RegexConfig regex;
|
||||
};
|
||||
|
||||
VariableTable(Settings &settings, QWidget *parent = nullptr);
|
||||
static VariableTable *CreateTabTable();
|
||||
void HideDockOptions() const;
|
||||
|
||||
private slots:
|
||||
void Add();
|
||||
void Remove();
|
||||
void Filter();
|
||||
|
||||
private:
|
||||
VariableTable(QTabWidget *parent = nullptr);
|
||||
QLineEdit *_searchField;
|
||||
QPushButton *_clear;
|
||||
QComboBox *_searchType;
|
||||
RegexConfigWidget *_regexWidget;
|
||||
QCheckBox *_addDock;
|
||||
|
||||
Settings &_settings;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -456,7 +456,7 @@ void ImportVariables(obs_data_t *data)
|
||||
|
||||
obs_data_array_release(array);
|
||||
|
||||
QeueUITask(signalImportedVariables, importedVars);
|
||||
QueueUITask(signalImportedVariables, importedVars);
|
||||
}
|
||||
|
||||
std::chrono::high_resolution_clock::time_point GetLastVariableChangeTime()
|
||||
|
||||
@@ -601,10 +601,10 @@ static void populateHotkeyNames(QComboBox *list,
|
||||
MacroActionHotkeyEdit::MacroActionHotkeyEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionHotkey> entryData)
|
||||
: QWidget(parent),
|
||||
_actionType(new QComboBox()),
|
||||
_hotkeyType(new QComboBox()),
|
||||
_obsHotkeys(new QComboBox()),
|
||||
_keys(new QComboBox()),
|
||||
_actionType(new FilterComboBox()),
|
||||
_hotkeyType(new FilterComboBox()),
|
||||
_obsHotkeys(new FilterComboBox()),
|
||||
_keys(new FilterComboBox()),
|
||||
_leftShift(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.hotkey.leftShift"))),
|
||||
_rightShift(new QCheckBox(obs_module_text(
|
||||
|
||||
@@ -85,11 +85,14 @@ private slots:
|
||||
void DurationChanged(const Duration &);
|
||||
void OnlySendToOBSChanged(int state);
|
||||
|
||||
protected:
|
||||
QComboBox *_actionType;
|
||||
QComboBox *_hotkeyType;
|
||||
QComboBox *_obsHotkeys;
|
||||
QComboBox *_keys;
|
||||
private:
|
||||
void RepopulateOBSHotkeySelection();
|
||||
void SetWidgetVisibility();
|
||||
|
||||
FilterComboBox *_actionType;
|
||||
FilterComboBox *_hotkeyType;
|
||||
FilterComboBox *_obsHotkeys;
|
||||
FilterComboBox *_keys;
|
||||
QCheckBox *_leftShift;
|
||||
QCheckBox *_rightShift;
|
||||
QCheckBox *_leftCtrl;
|
||||
@@ -101,15 +104,10 @@ protected:
|
||||
DurationSelection *_duration;
|
||||
QCheckBox *_onlySendToOBS;
|
||||
QLabel *_noKeyPressSimulationWarning;
|
||||
|
||||
std::shared_ptr<MacroActionHotkey> _entryData;
|
||||
|
||||
private:
|
||||
void RepopulateOBSHotkeySelection();
|
||||
void SetWidgetVisibility();
|
||||
|
||||
QHBoxLayout *_entryLayout;
|
||||
QHBoxLayout *_keyConfigLayout;
|
||||
|
||||
std::shared_ptr<MacroActionHotkey> _entryData;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -250,6 +250,10 @@ MacroActionProjectorEdit::MacroActionProjectorEdit(
|
||||
_regex(new RegexConfigWidget(this)),
|
||||
_layout(new QHBoxLayout(this))
|
||||
{
|
||||
// The obs_frontend_open_projector() function does not seem to support
|
||||
// scenes of secondary canvases
|
||||
_scenes->LockToMainCanvas();
|
||||
|
||||
populateActionSelection(_actions);
|
||||
populateWindowTypes(_windowTypes);
|
||||
populateSelectionTypes(_types);
|
||||
@@ -396,26 +400,29 @@ void MacroActionProjectorEdit::SetWidgetVisibility()
|
||||
return;
|
||||
}
|
||||
|
||||
_projectorWindowName->setVisible(_entryData->_action ==
|
||||
const auto &action = _entryData->_action;
|
||||
const auto &type = _entryData->_type;
|
||||
|
||||
_projectorWindowName->setVisible(action ==
|
||||
MacroActionProjector::Action::CLOSE);
|
||||
_regex->setVisible(_entryData->_action ==
|
||||
MacroActionProjector::Action::CLOSE);
|
||||
_types->setVisible(_entryData->_action ==
|
||||
MacroActionProjector::Action::OPEN);
|
||||
_windowTypes->setVisible(_entryData->_action ==
|
||||
MacroActionProjector::Action::OPEN);
|
||||
_scenes->setVisible(
|
||||
_entryData->_action == MacroActionProjector::Action::OPEN &&
|
||||
_entryData->_type == MacroActionProjector::Type::SCENE);
|
||||
_sources->setVisible(
|
||||
_entryData->_action == MacroActionProjector::Action::OPEN &&
|
||||
_entryData->_type == MacroActionProjector::Type::SOURCE);
|
||||
_monitors->setVisible(_entryData->_action ==
|
||||
MacroActionProjector::Action::OPEN &&
|
||||
_regex->setVisible(action == MacroActionProjector::Action::CLOSE);
|
||||
_types->setVisible(action == MacroActionProjector::Action::OPEN);
|
||||
_windowTypes->setVisible(action == MacroActionProjector::Action::OPEN);
|
||||
_scenes->setVisible(action == MacroActionProjector::Action::OPEN &&
|
||||
type == MacroActionProjector::Type::SCENE);
|
||||
_sources->setVisible(action == MacroActionProjector::Action::OPEN &&
|
||||
type == MacroActionProjector::Type::SOURCE);
|
||||
_monitors->setVisible(action == MacroActionProjector::Action::OPEN &&
|
||||
_entryData->_fullscreen);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
|
||||
if (action == MacroActionProjector::Action::CLOSE) {
|
||||
RemoveStretchIfPresent(_layout);
|
||||
} else {
|
||||
AddStretchIfNecessary(_layout);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -240,7 +240,7 @@ bool MacroActionSwitchScene::Load(obs_data_t *obj)
|
||||
GetWeakCanvasByName(obs_data_get_string(obj, "canvas"));
|
||||
} else {
|
||||
OBSCanvasAutoRelease main = obs_get_main_canvas();
|
||||
_canvas = OBSGetWeakRef(main);
|
||||
_canvas = obs_canvas_get_weak_canvas(main);
|
||||
}
|
||||
|
||||
_index.Save(obj, "index");
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const std::string MacroActionSource::id = "source";
|
||||
@@ -37,6 +39,12 @@ const static std::map<MacroActionSource::Action, std::string> actionTypes = {
|
||||
"AdvSceneSwitcher.action.source.type.openFilterDialog"},
|
||||
{MacroActionSource::Action::OPEN_PROPERTIES_DIALOG,
|
||||
"AdvSceneSwitcher.action.source.type.openPropertiesDialog"},
|
||||
{MacroActionSource::Action::CLOSE_INTERACTION_DIALOG,
|
||||
"AdvSceneSwitcher.action.source.type.closeInteractionDialog"},
|
||||
{MacroActionSource::Action::CLOSE_FILTER_DIALOG,
|
||||
"AdvSceneSwitcher.action.source.type.closeFilterDialog"},
|
||||
{MacroActionSource::Action::CLOSE_PROPERTIES_DIALOG,
|
||||
"AdvSceneSwitcher.action.source.type.closePropertiesDialog"},
|
||||
};
|
||||
|
||||
const static std::map<obs_deinterlace_mode, std::string> deinterlaceModes = {
|
||||
@@ -108,9 +116,81 @@ static bool isInteractable(obs_source_t *source)
|
||||
return (flags & OBS_SOURCE_INTERACTION) != 0;
|
||||
}
|
||||
|
||||
static void closeSourceDialog(obs_source_t *source, bool accept,
|
||||
const char *className)
|
||||
{
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto mainWindow =
|
||||
reinterpret_cast<QMainWindow *>(obs_frontend_get_main_window());
|
||||
if (!mainWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto widget : mainWindow->children()) {
|
||||
auto dialog = qobject_cast<QDialog *>(widget);
|
||||
if (!dialog) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dialog->metaObject()->className() != QString(className)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *name = obs_source_get_name(source);
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!dialog->windowTitle().contains(QString(name))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (accept) {
|
||||
dialog->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
// dialog->reject() will ask for confirmation
|
||||
// Try to avoid this by finding and pressing the cancel button
|
||||
for (const auto widget : dialog->children()) {
|
||||
auto buttons = qobject_cast<QDialogButtonBox *>(widget);
|
||||
if (!buttons) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto *button : buttons->buttons()) {
|
||||
if (buttons->buttonRole(button) ==
|
||||
QDialogButtonBox::RejectRole) {
|
||||
button->click();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename F> void QueueUITaskLambda(F &&func)
|
||||
{
|
||||
using FnType = std::decay_t<F>;
|
||||
auto *heapFunc = new FnType(std::forward<F>(func));
|
||||
|
||||
QueueUITask(
|
||||
[](void *param) {
|
||||
std::unique_ptr<FnType> fn(
|
||||
static_cast<FnType *>(param));
|
||||
(*fn)();
|
||||
},
|
||||
heapFunc);
|
||||
}
|
||||
|
||||
bool MacroActionSource::PerformAction()
|
||||
{
|
||||
auto s = obs_weak_source_get_source(_source.GetSource());
|
||||
OBSSource s = obs_weak_source_get_source(_source.GetSource());
|
||||
obs_source_release(s);
|
||||
|
||||
switch (_action) {
|
||||
case Action::ENABLE:
|
||||
obs_source_set_enabled(s, true);
|
||||
@@ -172,10 +252,31 @@ bool MacroActionSource::PerformAction()
|
||||
case Action::OPEN_PROPERTIES_DIALOG:
|
||||
obs_frontend_open_source_properties(s);
|
||||
break;
|
||||
case Action::CLOSE_INTERACTION_DIALOG:
|
||||
if (!isInteractable(s)) {
|
||||
// Nothing to do
|
||||
break;
|
||||
}
|
||||
|
||||
QueueUITaskLambda([&]() {
|
||||
closeSourceDialog(s, true, "OBSBasicInteraction");
|
||||
});
|
||||
break;
|
||||
case Action::CLOSE_FILTER_DIALOG:
|
||||
QueueUITaskLambda([&]() {
|
||||
closeSourceDialog(s, _acceptDialog, "OBSBasicFilters");
|
||||
});
|
||||
break;
|
||||
case Action::CLOSE_PROPERTIES_DIALOG:
|
||||
QueueUITaskLambda([&]() {
|
||||
closeSourceDialog(s, _acceptDialog,
|
||||
"OBSBasicProperties");
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
obs_source_release(s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -207,6 +308,7 @@ bool MacroActionSource::Save(obs_data_t *obj) const
|
||||
static_cast<int>(_deinterlaceMode));
|
||||
obs_data_set_int(obj, "deinterlaceOrder",
|
||||
static_cast<int>(_deinterlaceOrder));
|
||||
obs_data_set_bool(obj, "acceptDialog", _acceptDialog);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -231,6 +333,7 @@ bool MacroActionSource::Load(obs_data_t *obj)
|
||||
obs_data_get_int(obj, "deinterlaceMode"));
|
||||
_deinterlaceOrder = static_cast<obs_deinterlace_field_order>(
|
||||
obs_data_get_int(obj, "deinterlaceOrder"));
|
||||
_acceptDialog = obs_data_get_bool(obj, "acceptDialog");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -307,7 +410,9 @@ MacroActionSourceEdit::MacroActionSourceEdit(
|
||||
_warning(new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.action.source.warning"))),
|
||||
_refreshSettingSelection(new QPushButton(
|
||||
obs_module_text("AdvSceneSwitcher.action.source.refresh")))
|
||||
obs_module_text("AdvSceneSwitcher.action.source.refresh"))),
|
||||
_acceptDialog(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.action.source.dialog.accept")))
|
||||
{
|
||||
populateActionSelection(_actions);
|
||||
|
||||
@@ -347,6 +452,8 @@ MacroActionSourceEdit::MacroActionSourceEdit(
|
||||
SLOT(SelectionChanged(const SourceSetting &)));
|
||||
QWidget::connect(_refreshSettingSelection, SIGNAL(clicked()), this,
|
||||
SLOT(RefreshVariableSourceSelectionValue()));
|
||||
QWidget::connect(_acceptDialog, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(AcceptDialogChanged(int)));
|
||||
|
||||
auto entryLayout = new QHBoxLayout;
|
||||
entryLayout->setContentsMargins(0, 0, 0, 0);
|
||||
@@ -374,6 +481,7 @@ MacroActionSourceEdit::MacroActionSourceEdit(
|
||||
mainLayout->addLayout(_settingsLayout);
|
||||
mainLayout->addWidget(_warning);
|
||||
mainLayout->addWidget(_settingsString);
|
||||
mainLayout->addWidget(_acceptDialog);
|
||||
auto buttonLayout = new QHBoxLayout;
|
||||
buttonLayout->setContentsMargins(0, 0, 0, 0);
|
||||
buttonLayout->addWidget(_getSettings);
|
||||
@@ -406,6 +514,7 @@ void MacroActionSourceEdit::UpdateEntryData()
|
||||
static_cast<int>(_entryData->_settingsInputMethod)));
|
||||
_tempVars->SetVariable(_entryData->_tempVar);
|
||||
_manualSettingValue->setPlainText(_entryData->_manualSettingValue);
|
||||
_acceptDialog->setChecked(_entryData->_acceptDialog);
|
||||
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
@@ -531,6 +640,12 @@ void MacroActionSourceEdit::RefreshVariableSourceSelectionValue()
|
||||
_sourceSettings->SetSource(_entryData->_source.GetSource());
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::AcceptDialogChanged(int state)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_acceptDialog = state;
|
||||
}
|
||||
|
||||
static QString GetIndividualListEntryName()
|
||||
{
|
||||
static const auto matchesInput =
|
||||
@@ -613,6 +728,12 @@ void MacroActionSourceEdit::SetWidgetVisibility()
|
||||
_entryData->_source.GetType() ==
|
||||
SourceSelection::Type::VARIABLE);
|
||||
|
||||
_acceptDialog->setVisible(
|
||||
_entryData->_action ==
|
||||
MacroActionSource::Action::CLOSE_FILTER_DIALOG ||
|
||||
_entryData->_action ==
|
||||
MacroActionSource::Action::CLOSE_PROPERTIES_DIALOG);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
#include "source-selection.hpp"
|
||||
#include "source-setting.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QComboBox>
|
||||
|
||||
namespace advss {
|
||||
|
||||
@@ -33,6 +34,7 @@ public:
|
||||
OBS_DEINTERLACE_FIELD_ORDER_TOP;
|
||||
TempVariableRef _tempVar;
|
||||
SourceSetting _setting;
|
||||
bool _acceptDialog = false;
|
||||
|
||||
enum class Action {
|
||||
ENABLE,
|
||||
@@ -45,6 +47,9 @@ public:
|
||||
OPEN_INTERACTION_DIALOG,
|
||||
OPEN_FILTER_DIALOG,
|
||||
OPEN_PROPERTIES_DIALOG,
|
||||
CLOSE_INTERACTION_DIALOG,
|
||||
CLOSE_FILTER_DIALOG,
|
||||
CLOSE_PROPERTIES_DIALOG,
|
||||
};
|
||||
Action _action = Action::SETTINGS;
|
||||
|
||||
@@ -91,6 +96,7 @@ private slots:
|
||||
void SelectionChanged(const SourceSetting &);
|
||||
void ManualSettingsValueChanged();
|
||||
void RefreshVariableSourceSelectionValue();
|
||||
void AcceptDialogChanged(int);
|
||||
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
@@ -112,6 +118,7 @@ private:
|
||||
QComboBox *_deinterlaceOrder;
|
||||
QLabel *_warning;
|
||||
QPushButton *_refreshSettingSelection;
|
||||
QCheckBox *_acceptDialog;
|
||||
|
||||
std::shared_ptr<MacroActionSource> _entryData;
|
||||
bool _loading = true;
|
||||
|
||||
@@ -58,6 +58,10 @@ bool MacroConditionReplayBuffer::ReplayBufferWasSaved()
|
||||
|
||||
bool MacroConditionReplayBuffer::CheckCondition()
|
||||
{
|
||||
char *lastSavePath = obs_frontend_get_last_replay();
|
||||
SetTempVarValue("lastSavePath", lastSavePath ? lastSavePath : "");
|
||||
bfree(lastSavePath);
|
||||
|
||||
switch (_state) {
|
||||
case Condition::STOP:
|
||||
return !obs_frontend_replay_buffer_active();
|
||||
@@ -85,6 +89,16 @@ bool MacroConditionReplayBuffer::Load(obs_data_t *obj)
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroConditionReplayBuffer::SetupTempVars()
|
||||
{
|
||||
MacroCondition::SetupTempVars();
|
||||
AddTempvar(
|
||||
"lastSavePath",
|
||||
obs_module_text("AdvSceneSwitcher.tempVar.replay.lastSavePath"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.replay.lastSavePath.description"));
|
||||
}
|
||||
|
||||
static inline void populateStateSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &entry : conditions) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
class MacroConditionReplayBuffer : public MacroCondition {
|
||||
class MacroConditionReplayBuffer final : public MacroCondition {
|
||||
public:
|
||||
MacroConditionReplayBuffer(Macro *m) : MacroCondition(m) {}
|
||||
bool CheckCondition();
|
||||
@@ -27,6 +27,7 @@ public:
|
||||
Condition _state = Condition::STOP;
|
||||
|
||||
private:
|
||||
void SetupTempVars();
|
||||
bool ReplayBufferWasSaved();
|
||||
|
||||
bool _saveTimeInitialized = false;
|
||||
@@ -35,7 +36,7 @@ private:
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionReplayBufferEdit : public QWidget {
|
||||
class MacroConditionReplayBufferEdit final : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@@ -55,11 +56,10 @@ public:
|
||||
private slots:
|
||||
void StateChanged(int value);
|
||||
|
||||
protected:
|
||||
QComboBox *_state;
|
||||
std::shared_ptr<MacroConditionReplayBuffer> _entryData;
|
||||
|
||||
private:
|
||||
QComboBox *_state;
|
||||
|
||||
std::shared_ptr<MacroConditionReplayBuffer> _entryData;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -252,6 +252,7 @@ MacroConditionSlideshowEdit::MacroConditionSlideshowEdit(
|
||||
setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.slideshow.updateInterval.tooltip"));
|
||||
_index->setMinimum(1);
|
||||
_index->setMaximum(9999);
|
||||
populateConditionSelection(_conditions);
|
||||
|
||||
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
|
||||
|
||||
@@ -242,7 +242,7 @@ MacroConditionStreamEdit::MacroConditionStreamEdit(
|
||||
_keyFrameInterval(new VariableSpinBox()),
|
||||
_streamKey(new VariableLineEdit(this)),
|
||||
_serviceName(new VariableLineEdit(this)),
|
||||
_currentService(new AutoUpdateTooltipLabel(
|
||||
_currentService(new AutoUpdateHelpIcon(
|
||||
this,
|
||||
[]() {
|
||||
QString formatString = obs_module_text(
|
||||
@@ -255,13 +255,6 @@ MacroConditionStreamEdit::MacroConditionStreamEdit(
|
||||
_keyFrameInterval->setMinimum(0);
|
||||
_keyFrameInterval->setMaximum(25);
|
||||
|
||||
QString path = GetThemeTypeName() == "Light"
|
||||
? ":/res/images/help.svg"
|
||||
: ":/res/images/help_light.svg";
|
||||
QIcon icon(path);
|
||||
QPixmap pixmap = icon.pixmap(QSize(16, 16));
|
||||
_currentService->setPixmap(pixmap);
|
||||
|
||||
_streamKey->setEchoMode(QLineEdit::PasswordEchoOnEdit);
|
||||
|
||||
populateConditionSelection(_conditions);
|
||||
|
||||
@@ -15,12 +15,15 @@ if(NOT TARGET httplib)
|
||||
EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
if(NOT OPENSSL_INCLUDE_DIR OR NOT OPENSSL_LIBRARIES)
|
||||
find_package(OpenSSL)
|
||||
if(NOT OPENSSL_FOUND)
|
||||
message(WARNING "OpenSSL not found!\n" "HTTP support will be disabled!\n\n")
|
||||
return()
|
||||
endif()
|
||||
if(OS_MACOS)
|
||||
set(OPENSSL_USE_STATIC_LIBS
|
||||
ON
|
||||
CACHE BOOL "Use static OpenSSL" FORCE)
|
||||
endif()
|
||||
find_package(OpenSSL)
|
||||
if(NOT OPENSSL_FOUND)
|
||||
message(WARNING "OpenSSL not found!\n" "HTTP support will be disabled!\n\n")
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(ZLIB)
|
||||
|
||||
@@ -3,6 +3,13 @@ project(advanced-scene-switcher-mqtt)
|
||||
|
||||
# --- Check paho.mqtt.cpp requirements ---
|
||||
|
||||
if(OS_MACOS)
|
||||
set(OPENSSL_USE_STATIC_LIBS
|
||||
ON
|
||||
CACHE BOOL "Use static OpenSSL" FORCE)
|
||||
endif()
|
||||
find_package(OpenSSL)
|
||||
|
||||
find_package(PahoMqttCpp)
|
||||
if(NOT PahoMqttCpp_FOUND)
|
||||
message(WARNING "PahoMqttCpp not found!\n"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "mqtt-helpers.hpp"
|
||||
#include "help-icon.hpp"
|
||||
#include "layout-helpers.hpp"
|
||||
#include "log-helper.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
@@ -22,6 +23,10 @@ MqttConnection::MqttConnection(const MqttConnection &other)
|
||||
_uri(other._uri),
|
||||
_username(other._username),
|
||||
_password(other._password),
|
||||
_trustStore(other._trustStore),
|
||||
_keyStore(other._keyStore),
|
||||
_privateKey(other._privateKey),
|
||||
_verifyServerCert(other._verifyServerCert),
|
||||
_connectOnStart(other._connectOnStart),
|
||||
_reconnect(other._reconnect),
|
||||
_reconnectDelay(other._reconnectDelay)
|
||||
@@ -30,12 +35,20 @@ MqttConnection::MqttConnection(const MqttConnection &other)
|
||||
|
||||
MqttConnection::MqttConnection(const std::string &name, const std::string &uri,
|
||||
const std::string &username,
|
||||
const std::string &password, bool connectOnStart,
|
||||
const std::string &password,
|
||||
const std::string &trustStore,
|
||||
const std::string &keyStore,
|
||||
const std::string &privateKey,
|
||||
bool verifyServerCert, bool connectOnStart,
|
||||
bool reconnect, int reconnectDelay)
|
||||
: Item(name),
|
||||
_uri(uri),
|
||||
_username(username),
|
||||
_password(password),
|
||||
_trustStore(trustStore),
|
||||
_keyStore(keyStore),
|
||||
_privateKey(privateKey),
|
||||
_verifyServerCert(verifyServerCert),
|
||||
_connectOnStart(connectOnStart),
|
||||
_reconnect(reconnect),
|
||||
_reconnectDelay(reconnectDelay)
|
||||
@@ -99,26 +112,41 @@ void MqttConnection::ConnectThread()
|
||||
};
|
||||
|
||||
do {
|
||||
std::unique_lock<std::mutex> clientLock(_clientMtx);
|
||||
_client = std::make_shared<mqtt::async_client>(
|
||||
_uri, std::string("advss_") + _name);
|
||||
#ifdef ENABLE_MQTT5_SUPPORT
|
||||
auto connOpts = mqtt::connect_options_builder::v5()
|
||||
#else
|
||||
auto connOpts = mqtt::connect_options_builder()
|
||||
#endif
|
||||
.clean_start(false)
|
||||
.clean_session(true)
|
||||
.connect_timeout(5s)
|
||||
.user_name(_username)
|
||||
.password(_password)
|
||||
.finalize();
|
||||
|
||||
_client->set_connection_lost_handler(logConnectionLost);
|
||||
_client->set_message_callback(dispatchMessage);
|
||||
_client->start_consuming();
|
||||
|
||||
try {
|
||||
std::unique_lock<std::mutex> clientLock(_clientMtx);
|
||||
_client = std::make_shared<mqtt::async_client>(
|
||||
_uri, std::string("advss_") + _name);
|
||||
|
||||
mqtt::ssl_options sslOptions;
|
||||
if (!_trustStore.empty()) {
|
||||
sslOptions.set_trust_store(_trustStore);
|
||||
}
|
||||
if (!_keyStore.empty()) {
|
||||
sslOptions.set_key_store(_keyStore);
|
||||
}
|
||||
if (!_privateKey.empty()) {
|
||||
sslOptions.set_private_key(_privateKey);
|
||||
}
|
||||
sslOptions.set_enable_server_cert_auth(
|
||||
_verifyServerCert);
|
||||
|
||||
#ifdef ENABLE_MQTT5_SUPPORT
|
||||
auto connOpts = mqtt::connect_options_builder::v5()
|
||||
#else
|
||||
auto connOpts = mqtt::connect_options_builder()
|
||||
#endif
|
||||
.clean_start(false)
|
||||
.clean_session(true)
|
||||
.connect_timeout(5s)
|
||||
.user_name(_username)
|
||||
.password(_password)
|
||||
.ssl(sslOptions)
|
||||
.finalize();
|
||||
|
||||
_client->set_connection_lost_handler(logConnectionLost);
|
||||
_client->set_message_callback(dispatchMessage);
|
||||
_client->start_consuming();
|
||||
|
||||
vblog(LOG_INFO, "connecting to MQTT server \"%s\" ...",
|
||||
_name.c_str());
|
||||
auto tok = _client->connect(connOpts);
|
||||
@@ -220,6 +248,10 @@ void MqttConnection::Load(obs_data_t *data)
|
||||
_uri = obs_data_get_string(data, "uri");
|
||||
_username = obs_data_get_string(data, "username");
|
||||
_password = obs_data_get_string(data, "password");
|
||||
_trustStore = obs_data_get_string(data, "trustStore");
|
||||
_keyStore = obs_data_get_string(data, "keyStore");
|
||||
_privateKey = obs_data_get_string(data, "privateKey");
|
||||
_verifyServerCert = obs_data_get_bool(data, "verifyServerCert");
|
||||
_connectOnStart = obs_data_get_bool(data, "connectOnStart");
|
||||
_reconnect = obs_data_get_bool(data, "reconnect");
|
||||
_reconnectDelay = obs_data_get_int(data, "reconnectDelay");
|
||||
@@ -251,6 +283,10 @@ void MqttConnection::Save(obs_data_t *data) const
|
||||
obs_data_set_string(data, "uri", _uri.c_str());
|
||||
obs_data_set_string(data, "username", _username.c_str());
|
||||
obs_data_set_string(data, "password", _password.c_str());
|
||||
obs_data_set_string(data, "trustStore", _trustStore.c_str());
|
||||
obs_data_set_string(data, "keyStore", _keyStore.c_str());
|
||||
obs_data_set_string(data, "privateKey", _privateKey.c_str());
|
||||
obs_data_set_bool(data, "verifyServerCert", _verifyServerCert);
|
||||
obs_data_set_bool(data, "connectOnStart", _connectOnStart);
|
||||
obs_data_set_bool(data, "reconnect", _reconnect);
|
||||
obs_data_set_int(data, "reconnectDelay", _reconnectDelay);
|
||||
@@ -308,6 +344,10 @@ MqttConnectionSettingsDialog::MqttConnectionSettingsDialog(
|
||||
_username(new QLineEdit()),
|
||||
_password(new QLineEdit()),
|
||||
_showPassword(new QPushButton()),
|
||||
_trustStore(new FileSelection()),
|
||||
_keyStore(new FileSelection()),
|
||||
_privateKey(new FileSelection()),
|
||||
_verifyServerCert(new QCheckBox()),
|
||||
_topics(new MqttTopicListWidget(this)),
|
||||
_connectOnStart(new QCheckBox()),
|
||||
_reconnect(new QCheckBox()),
|
||||
@@ -324,6 +364,10 @@ MqttConnectionSettingsDialog::MqttConnectionSettingsDialog(
|
||||
_uri->setText(QString::fromStdString(connection._uri));
|
||||
_username->setText(QString::fromStdString(connection._username));
|
||||
_password->setText(QString::fromStdString(connection._password));
|
||||
_trustStore->SetPath(QString::fromStdString(connection._trustStore));
|
||||
_keyStore->SetPath(QString::fromStdString(connection._keyStore));
|
||||
_privateKey->SetPath(QString::fromStdString(connection._privateKey));
|
||||
_verifyServerCert->setChecked(connection._verifyServerCert);
|
||||
_topics->SetValues(connection._topics, connection._qos);
|
||||
_reconnectDelay->setMaximum(9999);
|
||||
_reconnectDelay->setSuffix("s");
|
||||
@@ -364,6 +408,41 @@ MqttConnectionSettingsDialog::MqttConnectionSettingsDialog(
|
||||
passLayout->addWidget(_showPassword);
|
||||
_layout->addLayout(passLayout, row, 1);
|
||||
++row;
|
||||
_layout->addWidget(
|
||||
new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.mqttConnection.trustStore")),
|
||||
row, 0);
|
||||
auto trustStoreLayout = new QHBoxLayout();
|
||||
trustStoreLayout->addWidget(_trustStore);
|
||||
trustStoreLayout->addWidget(new HelpIcon(obs_module_text(
|
||||
"AdvSceneSwitcher.mqttConnection.trustStore.help")));
|
||||
_layout->addLayout(trustStoreLayout, row, 1);
|
||||
++row;
|
||||
_layout->addWidget(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.mqttConnection.keyStore")),
|
||||
row, 0);
|
||||
auto keyStoreLayout = new QHBoxLayout();
|
||||
keyStoreLayout->addWidget(_keyStore);
|
||||
keyStoreLayout->addWidget(new HelpIcon(obs_module_text(
|
||||
"AdvSceneSwitcher.mqttConnection.keyStore.help")));
|
||||
_layout->addLayout(keyStoreLayout, row, 1);
|
||||
++row;
|
||||
_layout->addWidget(
|
||||
new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.mqttConnection.privateKey")),
|
||||
row, 0);
|
||||
auto privateKeyLayout = new QHBoxLayout();
|
||||
privateKeyLayout->addWidget(_privateKey);
|
||||
privateKeyLayout->addWidget(new HelpIcon(obs_module_text(
|
||||
"AdvSceneSwitcher.mqttConnection.privateKey.help")));
|
||||
_layout->addLayout(privateKeyLayout, row, 1);
|
||||
++row;
|
||||
_layout->addWidget(
|
||||
new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.mqttConnection.verifyServerCert")),
|
||||
row, 0);
|
||||
_layout->addWidget(_verifyServerCert, row, 1);
|
||||
++row;
|
||||
_layout->addWidget(new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.mqttConnection.topics")));
|
||||
++row;
|
||||
@@ -412,6 +491,10 @@ bool MqttConnectionSettingsDialog::AskForSettings(QWidget *parent,
|
||||
connection._uri = dialog._uri->text().toStdString();
|
||||
connection._username = dialog._username->text().toStdString();
|
||||
connection._password = dialog._password->text().toStdString();
|
||||
connection._trustStore = dialog._trustStore->GetPath().toStdString();
|
||||
connection._keyStore = dialog._keyStore->GetPath().toStdString();
|
||||
connection._privateKey = dialog._privateKey->GetPath().toStdString();
|
||||
connection._verifyServerCert = dialog._verifyServerCert->isChecked();
|
||||
connection._topics = dialog._topics->GetTopics();
|
||||
connection._qos = dialog._topics->GetQoS();
|
||||
connection._connectOnStart = dialog._connectOnStart->isChecked();
|
||||
@@ -455,6 +538,10 @@ void MqttConnectionSettingsDialog::TestConnection()
|
||||
connection->_uri = _uri->text().toStdString();
|
||||
connection->_username = _username->text().toStdString();
|
||||
connection->_password = _password->text().toStdString();
|
||||
connection->_trustStore = _trustStore->GetPath().toStdString();
|
||||
connection->_keyStore = _keyStore->GetPath().toStdString();
|
||||
connection->_privateKey = _privateKey->GetPath().toStdString();
|
||||
connection->_verifyServerCert = _verifyServerCert->isChecked();
|
||||
connection->_topics = _topics->GetTopics();
|
||||
connection->_qos = _topics->GetQoS();
|
||||
connection->_connectOnStart = false;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "message-dispatcher.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "item-selection-helpers.hpp"
|
||||
#include "topic-selection.hpp"
|
||||
|
||||
@@ -27,6 +28,9 @@ public:
|
||||
MqttConnection(const MqttConnection &other);
|
||||
MqttConnection(const std::string &name, const std::string &uri,
|
||||
const std::string &username, const std::string &password,
|
||||
const std::string &trustStore,
|
||||
const std::string &keyStore,
|
||||
const std::string &privateKey, bool verifyServerCert,
|
||||
bool connectOnStart, bool reconnect, int reconnectDelay);
|
||||
static std::shared_ptr<Item> Create()
|
||||
{
|
||||
@@ -51,8 +55,12 @@ private:
|
||||
std::string _uri = "mqtt://localhost:1883";
|
||||
std::string _username = "user";
|
||||
std::string _password = "password";
|
||||
std::string _trustStore = "";
|
||||
std::string _keyStore = "";
|
||||
std::string _privateKey = "";
|
||||
bool _verifyServerCert = false;
|
||||
|
||||
std::vector<std::string> _topics = {"/#"};
|
||||
std::vector<std::string> _topics = {"/example"};
|
||||
std::vector<int> _qos = {1};
|
||||
|
||||
std::thread _thread;
|
||||
@@ -91,6 +99,10 @@ private:
|
||||
QLineEdit *_username;
|
||||
QLineEdit *_password;
|
||||
QPushButton *_showPassword;
|
||||
FileSelection *_trustStore;
|
||||
FileSelection *_keyStore;
|
||||
FileSelection *_privateKey;
|
||||
QCheckBox *_verifyServerCert;
|
||||
MqttTopicListWidget *_topics;
|
||||
QCheckBox *_connectOnStart;
|
||||
QCheckBox *_reconnect;
|
||||
|
||||
@@ -15,13 +15,15 @@ if(NOT TARGET httplib)
|
||||
EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
if(NOT OPENSSL_INCLUDE_DIR OR NOT OPENSSL_LIBRARIES)
|
||||
find_package(OpenSSL)
|
||||
if(NOT OPENSSL_FOUND)
|
||||
message(WARNING "OpenSSL not found!\n"
|
||||
"Twitch support will be disabled!\n\n")
|
||||
return()
|
||||
endif()
|
||||
if(OS_MACOS)
|
||||
set(OPENSSL_USE_STATIC_LIBS
|
||||
ON
|
||||
CACHE BOOL "Use static OpenSSL" FORCE)
|
||||
endif()
|
||||
find_package(OpenSSL)
|
||||
if(NOT OPENSSL_FOUND)
|
||||
message(WARNING "OpenSSL not found!\n" "Twitch support will be disabled!\n\n")
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(ZLIB)
|
||||
|
||||
@@ -32,28 +32,17 @@ static const int reconnectDelay = 15;
|
||||
|
||||
#undef DispatchMessage
|
||||
|
||||
EventSub::EventSub() : QObject(nullptr)
|
||||
EventSub::EventSub()
|
||||
: QObject(nullptr),
|
||||
_client(std::make_unique<EventSubWSClient>())
|
||||
{
|
||||
_client.get_alog().clear_channels(
|
||||
websocketpp::log::alevel::frame_header |
|
||||
websocketpp::log::alevel::frame_payload |
|
||||
websocketpp::log::alevel::control);
|
||||
_client.init_asio();
|
||||
#ifndef _WIN32
|
||||
_client.set_reuse_addr(true);
|
||||
#endif
|
||||
SetupClient(*_client);
|
||||
|
||||
_client.set_open_handler(bind(&EventSub::OnOpen, this, _1));
|
||||
_client.set_message_handler(bind(&EventSub::OnMessage, this, _1, _2));
|
||||
_client.set_close_handler(bind(&EventSub::OnClose, this, _1));
|
||||
_client.set_fail_handler(bind(&EventSub::OnFail, this, _1));
|
||||
_client->set_open_handler(bind(&EventSub::OnOpen, this, _1));
|
||||
_client->set_message_handler(bind(&EventSub::OnMessage, this, _1, _2));
|
||||
_client->set_close_handler(bind(&EventSub::OnClose, this, _1));
|
||||
_client->set_fail_handler(bind(&EventSub::OnFail, this, _1));
|
||||
|
||||
#ifndef USE_TWITCH_CLI_MOCK
|
||||
_client.set_tls_init_handler([](websocketpp::connection_hdl) {
|
||||
return websocketpp::lib::make_shared<asio::ssl::context>(
|
||||
asio::ssl::context::sslv23_client);
|
||||
});
|
||||
#endif
|
||||
_url = defaultURL.data();
|
||||
RegisterInstance();
|
||||
}
|
||||
@@ -82,28 +71,34 @@ void EventSub::UnregisterInstance()
|
||||
|
||||
void EventSub::ConnectThread()
|
||||
{
|
||||
while (!_disconnect) {
|
||||
std::unique_lock<std::mutex> lock(_waitMtx);
|
||||
_client.reset();
|
||||
_connected = true;
|
||||
websocketpp::lib::error_code ec;
|
||||
EventSubWSClient::connection_ptr con =
|
||||
_client.get_connection(_url, ec);
|
||||
if (ec) {
|
||||
blog(LOG_INFO, "Twitch EventSub failed: %s",
|
||||
ec.message().c_str());
|
||||
} else {
|
||||
_client.connect(con);
|
||||
_connection = connection_hdl(con);
|
||||
_client.run();
|
||||
}
|
||||
_client->reset();
|
||||
_connected = true;
|
||||
websocketpp::lib::error_code ec;
|
||||
EventSubWSClient::connection_ptr con =
|
||||
_client->get_connection(_url, ec);
|
||||
if (ec) {
|
||||
blog(LOG_INFO, "Twitch EventSub failed: %s",
|
||||
ec.message().c_str());
|
||||
} else {
|
||||
_client->connect(con);
|
||||
_connection = connection_hdl(con);
|
||||
_client->run();
|
||||
}
|
||||
|
||||
_connected = false;
|
||||
}
|
||||
|
||||
void EventSub::WaitAndReconnect()
|
||||
{
|
||||
auto thread = std::thread([this]() {
|
||||
std::unique_lock<std::mutex> lock(_waitMtx);
|
||||
blog(LOG_INFO,
|
||||
"Twitch EventSub trying to reconnect to in %d seconds.",
|
||||
reconnectDelay);
|
||||
_cv.wait_for(lock, std::chrono::seconds(reconnectDelay));
|
||||
}
|
||||
_connected = false;
|
||||
Connect();
|
||||
});
|
||||
thread.detach();
|
||||
}
|
||||
|
||||
void EventSub::Connect()
|
||||
@@ -137,8 +132,8 @@ void EventSub::Disconnect()
|
||||
std::lock_guard<std::mutex> lock(_connectMtx);
|
||||
_disconnect = true;
|
||||
websocketpp::lib::error_code ec;
|
||||
_client.close(_connection, websocketpp::close::status::normal,
|
||||
"Twitch EventSub stopping", ec);
|
||||
_client->close(_connection, websocketpp::close::status::normal,
|
||||
"Twitch EventSub stopping", ec);
|
||||
{
|
||||
std::unique_lock<std::mutex> waitLock(_waitMtx);
|
||||
_cv.notify_all();
|
||||
@@ -146,8 +141,8 @@ void EventSub::Disconnect()
|
||||
|
||||
while (_connected) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
_client.close(_connection, websocketpp::close::status::normal,
|
||||
"Twitch EventSub stopping", ec);
|
||||
_client->close(_connection, websocketpp::close::status::normal,
|
||||
"Twitch EventSub stopping", ec);
|
||||
}
|
||||
|
||||
if (_thread.joinable()) {
|
||||
@@ -238,6 +233,24 @@ std::string EventSub::AddEventSubscription(std::shared_ptr<TwitchToken> token,
|
||||
return subscription.id;
|
||||
}
|
||||
|
||||
void EventSub::SetupClient(EventSubWSClient &client)
|
||||
{
|
||||
client.get_alog().clear_channels(
|
||||
websocketpp::log::alevel::frame_header |
|
||||
websocketpp::log::alevel::frame_payload |
|
||||
websocketpp::log::alevel::control);
|
||||
client.init_asio();
|
||||
#ifndef _WIN32
|
||||
client.set_reuse_addr(true);
|
||||
#endif
|
||||
#ifndef USE_TWITCH_CLI_MOCK
|
||||
client.set_tls_init_handler([](websocketpp::connection_hdl) {
|
||||
return websocketpp::lib::make_shared<asio::ssl::context>(
|
||||
asio::ssl::context::sslv23_client);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void EventSub::OnOpen(connection_hdl)
|
||||
{
|
||||
vblog(LOG_INFO, "Twitch EventSub connection opened");
|
||||
@@ -303,13 +316,14 @@ bool EventSub::IsValidID(const std::string &id)
|
||||
return !_sessionID.empty() && id == _sessionID;
|
||||
}
|
||||
|
||||
void EventSub::OnMessage(connection_hdl, EventSubWSClient::message_ptr message)
|
||||
std::optional<const EventSub::ParsedMessage>
|
||||
EventSub::ParseWebSocketMessage(const EventSubWSClient::message_ptr &message)
|
||||
{
|
||||
if (!message) {
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
if (message->get_opcode() != websocketpp::frame::opcode::text) {
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string payload = message->get_payload();
|
||||
@@ -317,7 +331,7 @@ void EventSub::OnMessage(connection_hdl, EventSubWSClient::message_ptr message)
|
||||
if (!json) {
|
||||
blog(LOG_ERROR, "invalid JSON payload received for '%s'",
|
||||
payload.c_str());
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
OBSDataAutoRelease metadata = obs_data_get_obj(json, "metadata");
|
||||
@@ -325,31 +339,46 @@ void EventSub::OnMessage(connection_hdl, EventSubWSClient::message_ptr message)
|
||||
obs_data_get_string(metadata, "message_timestamp");
|
||||
if (_validateTimestamps && !isValidTimestamp(timestamp)) {
|
||||
blog(LOG_WARNING,
|
||||
"Discarding Twitch EventSub with invalid timestamp %s",
|
||||
"discarding Twitch EventSub with invalid timestamp %s",
|
||||
timestamp.c_str());
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
std::string id = obs_data_get_string(metadata, "message_id");
|
||||
if (!IsValidMessageID(id)) {
|
||||
blog(LOG_WARNING,
|
||||
"Discarding Twitch EventSub with invalid message_id");
|
||||
"discarding Twitch EventSub with invalid message_id");
|
||||
return {};
|
||||
}
|
||||
|
||||
ParsedMessage parsedMessage{obs_data_get_string(metadata,
|
||||
"message_type"),
|
||||
obs_data_get_obj(json, "payload")};
|
||||
return parsedMessage;
|
||||
}
|
||||
|
||||
void EventSub::OnMessage(connection_hdl, EventSubWSClient::message_ptr message)
|
||||
{
|
||||
const auto msg = ParseWebSocketMessage(message);
|
||||
if (!msg) {
|
||||
return;
|
||||
}
|
||||
std::string messageType = obs_data_get_string(metadata, "message_type");
|
||||
OBSDataAutoRelease payloadJson = obs_data_get_obj(json, "payload");
|
||||
if (messageType == "session_welcome") {
|
||||
HandleWelcome(payloadJson);
|
||||
} else if (messageType == "session_keepalive") {
|
||||
|
||||
const auto &type = msg->type;
|
||||
const auto &data = msg->payload;
|
||||
if (type == "session_welcome") {
|
||||
HandleWelcome(data);
|
||||
} else if (type == "session_keepalive") {
|
||||
HandleKeepAlive();
|
||||
} else if (messageType == "notification") {
|
||||
HandleNotification(payloadJson);
|
||||
} else if (messageType == "session_reconnect") {
|
||||
HandleReconnect(payloadJson);
|
||||
} else if (messageType == "revocation") {
|
||||
HandleRevocation(payloadJson);
|
||||
} else if (type == "notification") {
|
||||
HandleNotification(data);
|
||||
} else if (type == "session_reconnect") {
|
||||
HandleServerMigration(data);
|
||||
} else if (type == "revocation") {
|
||||
HandleRevocation(data);
|
||||
} else {
|
||||
vblog(LOG_INFO, "ignoring message of unknown type '%s'",
|
||||
messageType.c_str());
|
||||
vblog(LOG_INFO,
|
||||
"Twitch EventSub ignoring message of unknown type '%s'",
|
||||
type.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,9 +406,96 @@ void EventSub::HandleNotification(obs_data_t *data)
|
||||
_dispatcher.DispatchMessage(event);
|
||||
}
|
||||
|
||||
void EventSub::HandleReconnect(obs_data_t *data)
|
||||
void EventSub::OnServerMigrationWelcome(
|
||||
connection_hdl newHdl, std::unique_ptr<EventSubWSClient> &newClient)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_connectMtx);
|
||||
|
||||
// Disable reconnect handling for old connection which will be closed
|
||||
_client->set_close_handler([](connection_hdl) {});
|
||||
_client->set_fail_handler([](connection_hdl) {});
|
||||
auto connection = _client->get_con_from_hdl(_connection);
|
||||
connection->set_close_handler([](connection_hdl) {
|
||||
vblog(LOG_INFO, "previous Twitch EventSub connection closed");
|
||||
});
|
||||
connection->set_fail_handler([](connection_hdl) {});
|
||||
|
||||
websocketpp::lib::error_code ec;
|
||||
_client->close(_connection, websocketpp::close::status::normal,
|
||||
"Switching to new connection", ec);
|
||||
|
||||
_client.swap(newClient);
|
||||
_sessionID = _migrationSessionID;
|
||||
_connection = newHdl;
|
||||
|
||||
_client->set_open_handler(bind(&EventSub::OnOpen, this, _1));
|
||||
_client->set_message_handler(bind(&EventSub::OnMessage, this, _1, _2));
|
||||
_client->set_close_handler(bind(&EventSub::OnClose, this, _1));
|
||||
_client->set_fail_handler(bind(&EventSub::OnFail, this, _1));
|
||||
auto newConnection = _client->get_con_from_hdl(_connection);
|
||||
newConnection->set_open_handler(bind(&EventSub::OnOpen, this, _1));
|
||||
newConnection->set_message_handler(
|
||||
bind(&EventSub::OnMessage, this, _1, _2));
|
||||
newConnection->set_close_handler(bind(&EventSub::OnClose, this, _1));
|
||||
newConnection->set_fail_handler(bind(&EventSub::OnFail, this, _1));
|
||||
|
||||
_connected = true;
|
||||
_migrating = false;
|
||||
}
|
||||
|
||||
void EventSub::StartServerMigrationClient(const std::string &url)
|
||||
{
|
||||
auto client = std::make_unique<EventSubWSClient>();
|
||||
SetupClient(*client);
|
||||
|
||||
client->set_open_handler([this](connection_hdl hdl) {
|
||||
vblog(LOG_INFO, "Twitch EventSub migration client opened");
|
||||
});
|
||||
|
||||
client->set_message_handler([this,
|
||||
&client](connection_hdl hdl,
|
||||
EventSubWSClient::message_ptr
|
||||
message) {
|
||||
const auto msg = ParseWebSocketMessage(message);
|
||||
if (!msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &type = msg->type;
|
||||
const auto &data = msg->payload;
|
||||
|
||||
if (type == "session_welcome") {
|
||||
vblog(LOG_INFO,
|
||||
"Twitch EventSub migration successful - switching to new connection");
|
||||
OBSDataAutoRelease session =
|
||||
obs_data_get_obj(data, "session");
|
||||
_migrationSessionID =
|
||||
obs_data_get_string(session, "id");
|
||||
OnServerMigrationWelcome(hdl, client);
|
||||
} else {
|
||||
OnMessage(hdl, message);
|
||||
}
|
||||
});
|
||||
|
||||
websocketpp::lib::error_code ec;
|
||||
auto con = client->get_connection(url, ec);
|
||||
if (ec) {
|
||||
blog(LOG_ERROR,
|
||||
"Twitch EventSub migration connection failed: %s",
|
||||
ec.message().c_str());
|
||||
_migrating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_migrationConnection = con;
|
||||
client->connect(con);
|
||||
client->run();
|
||||
}
|
||||
|
||||
void EventSub::HandleServerMigration(obs_data_t *data)
|
||||
{
|
||||
blog(LOG_INFO, "Twitch EventSub session_reconnect received");
|
||||
|
||||
OBSDataAutoRelease session = obs_data_get_obj(data, "session");
|
||||
auto id = obs_data_get_string(session, "id");
|
||||
if (!IsValidID(id)) {
|
||||
@@ -388,8 +504,25 @@ void EventSub::HandleReconnect(obs_data_t *data)
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// Implement proper reconnect handing to avoid dropped events
|
||||
const std::string newURL =
|
||||
obs_data_get_string(session, "reconnect_url");
|
||||
if (newURL.empty()) {
|
||||
blog(LOG_WARNING, "missing reconnect_url in session_reconnect");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_migrating.exchange(true)) {
|
||||
vblog(LOG_INFO,
|
||||
"ignoring Twitch EventSub session_reconnect - already in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
vblog(LOG_INFO, "Twitch EventSub reconnect: connecting to %s",
|
||||
newURL.c_str());
|
||||
|
||||
std::thread([this, newURL]() {
|
||||
StartServerMigrationClient(newURL);
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void EventSub::HandleRevocation(obs_data_t *data)
|
||||
@@ -425,23 +558,41 @@ void EventSub::HandleRevocation(obs_data_t *data)
|
||||
|
||||
void EventSub::OnClose(connection_hdl hdl)
|
||||
{
|
||||
EventSubWSClient::connection_ptr con = _client.get_con_from_hdl(hdl);
|
||||
EventSubWSClient::connection_ptr con = _client->get_con_from_hdl(hdl);
|
||||
const auto msg = con->get_ec().message();
|
||||
const auto reason = con->get_remote_close_reason();
|
||||
const auto code = con->get_remote_close_code();
|
||||
blog(LOG_INFO, "Twitch EventSub connection closed: %s / %s (%d)",
|
||||
msg.c_str(), reason.c_str(), code);
|
||||
ClearActiveSubscriptions();
|
||||
|
||||
if (_migrating) {
|
||||
ClearActiveSubscriptions();
|
||||
}
|
||||
_connected = false;
|
||||
|
||||
if (_disconnect) {
|
||||
return;
|
||||
}
|
||||
|
||||
WaitAndReconnect();
|
||||
}
|
||||
|
||||
void EventSub::OnFail(connection_hdl hdl)
|
||||
{
|
||||
EventSubWSClient::connection_ptr con = _client.get_con_from_hdl(hdl);
|
||||
EventSubWSClient::connection_ptr con = _client->get_con_from_hdl(hdl);
|
||||
auto msg = con->get_ec().message();
|
||||
blog(LOG_INFO, "Twitch EventSub connection failed: %s", msg.c_str());
|
||||
ClearActiveSubscriptions();
|
||||
|
||||
if (!_migrating) {
|
||||
ClearActiveSubscriptions();
|
||||
}
|
||||
_connected = false;
|
||||
|
||||
if (_disconnect) {
|
||||
return;
|
||||
}
|
||||
|
||||
WaitAndReconnect();
|
||||
}
|
||||
|
||||
bool Subscription::operator<(const Subscription &other) const
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
namespace advss {
|
||||
|
||||
#ifdef USE_TWITCH_CLI_MOCK
|
||||
typedef websocketpp::client<websocketpp::config::asio_client> EventSubWSClient;
|
||||
using EventSubWSClient = websocketpp::client<websocketpp::config::asio_client>;
|
||||
#else
|
||||
typedef websocketpp::client<websocketpp::config::asio_tls_client>
|
||||
EventSubWSClient;
|
||||
using EventSubWSClient =
|
||||
websocketpp::client<websocketpp::config::asio_tls_client>;
|
||||
#endif
|
||||
|
||||
struct Event;
|
||||
@@ -61,12 +61,16 @@ public:
|
||||
void EnableTimestampValidation(bool enable);
|
||||
|
||||
private:
|
||||
static void SetupClient(EventSubWSClient &);
|
||||
|
||||
void OnOpen(connection_hdl hdl);
|
||||
void OnMessage(connection_hdl hdl,
|
||||
EventSubWSClient::message_ptr message);
|
||||
void OnClose(connection_hdl hdl);
|
||||
void OnFail(connection_hdl hdl);
|
||||
|
||||
void ConnectThread();
|
||||
void WaitAndReconnect();
|
||||
|
||||
bool IsValidMessageID(const std::string &);
|
||||
bool IsValidID(const std::string &);
|
||||
@@ -74,20 +78,39 @@ private:
|
||||
void HandleWelcome(obs_data_t *);
|
||||
void HandleKeepAlive() const;
|
||||
void HandleNotification(obs_data_t *);
|
||||
void HandleReconnect(obs_data_t *);
|
||||
void HandleServerMigration(obs_data_t *);
|
||||
void HandleRevocation(obs_data_t *);
|
||||
|
||||
void RegisterInstance();
|
||||
void UnregisterInstance();
|
||||
|
||||
EventSubWSClient _client;
|
||||
void StartServerMigrationClient(const std::string &url);
|
||||
void OnServerMigrationWelcome(connection_hdl,
|
||||
std::unique_ptr<EventSubWSClient> &);
|
||||
|
||||
struct ParsedMessage {
|
||||
std::string type;
|
||||
OBSDataAutoRelease payload;
|
||||
};
|
||||
|
||||
std::optional<const ParsedMessage>
|
||||
ParseWebSocketMessage(const EventSubWSClient::message_ptr &);
|
||||
|
||||
std::unique_ptr<EventSubWSClient> _client;
|
||||
connection_hdl _connection;
|
||||
|
||||
std::unique_ptr<EventSubWSClient> _migrationClient;
|
||||
connection_hdl _migrationConnection;
|
||||
std::atomic_bool _migrating{false};
|
||||
std::string _migrationSessionID;
|
||||
|
||||
std::thread _thread;
|
||||
std::mutex _waitMtx;
|
||||
std::mutex _connectMtx;
|
||||
std::condition_variable _cv;
|
||||
std::atomic_bool _connected{false};
|
||||
std::atomic_bool _disconnect{false};
|
||||
|
||||
std::string _url;
|
||||
std::string _sessionID;
|
||||
bool _validateTimestamps = true;
|
||||
|
||||
@@ -40,7 +40,7 @@ std::string GetThemeTypeName()
|
||||
return "Dark";
|
||||
}
|
||||
|
||||
void QeueUITask(void (*task)(void *param), void *) {}
|
||||
void QueueUITask(void (*task)(void *param), void *) {}
|
||||
|
||||
QWidget *GetSettingsWindow()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user