Compare commits

..

43 Commits

Author SHA1 Message Date
WarmUpTill
5a69e774c2 Fix warnings 2025-12-11 18:43:49 +01:00
WarmUpTill
2389f37ed6 Add temp var for last recording save path 2025-12-11 17:22:37 +01:00
WarmUpTill
de20c93b14 Rearrange layout of "General" tab
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-12-06 19:27:54 +01:00
WarmUpTill
b0d877db8c Cleanup unnecessary nested layouts 2025-12-06 19:27:54 +01:00
WarmUpTill
caf9e59475 Remove option to disable widget caching
Option was only introduced in case instabilities arise.
However, there weren't any reports of issues for a long time.
2025-12-06 19:27:54 +01:00
WarmUpTill
5f6982b5bb Add macro search functionality
Allows search by ...
* Macro name
* Segment type
* Segment label
2025-12-06 19:27:54 +01:00
WarmUpTill
ce399cc647 Move functions 2025-12-06 19:27:54 +01:00
WarmUpTill
03f67534c7 Default to main canvas in scene selection
Some checks are pending
debian-build / build (push) Waiting to run
Check locale / ubuntu64 (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
New scene selections would have no canvas selected at all.
This would cause issues in places which do not support an empty canvas
selection.

For example, the "scene changed" check of the scene condition would not
work as without any canvas it is not clear which scene to check for.
2025-12-05 17:30:16 +01:00
WarmUpTill
246667e65e Fix scene list scene switches not working
* Assume default canvas to be main canvas
* Add proper handling for the preview scene for next / previous
* Only display canvas warning if a canvas was selected
* Fix canvas selection not showing backend value
2025-12-05 17:30:16 +01:00
WarmUpTill
68fe7716e7 Add special handling for main canvas 2025-12-05 17:30:16 +01:00
WarmUpTill
98cc710b4a Add tooltip to "verify timestamps" option 2025-12-05 17:30:16 +01:00
WarmUpTill
c602c30c54 Display warning if Twitch token expired or is invalid 2025-12-05 17:30:16 +01:00
WarmUpTill
cc1b89fe1d Fix crash on macro tab setup 2025-12-05 17:30:16 +01:00
WarmUpTill
1de4ef7ec2 Add retry mechanism for downloading dependencies 2025-12-05 17:30:16 +01:00
WarmUpTill
e29060fdea Throttle Twitch API access for at least 1 second
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-12-03 18:30:17 +01:00
WarmUpTill
a0e6e6f528 Add options to close source dialogs
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-11-14 13:56:03 +01:00
WarmUpTill
e30d5845fa Run macro via dock button in parallel and ignore pause 2025-11-14 13:56:03 +01:00
WarmUpTill
d12911cd02 Fix typo 2025-11-14 13:56:03 +01:00
WarmUpTill
b8ecc40e8c Add search clear button to the variable tab 2025-11-14 12:38:24 +01:00
WarmUpTill
8ec4849b1d Add option copy value of another variable 2025-11-14 12:38:24 +01:00
WarmUpTill
e6e9f3a831 Add temp var for last replay buffer save path
Some checks are pending
debian-build / build (push) Waiting to run
Check locale / ubuntu64 (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
2025-11-13 21:06:29 +01:00
WarmUpTill
555f7c1381 Add option to enable dock containing variable tab
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-11-10 18:45:06 +01:00
WarmUpTill
f0f8b0fd92 Add option to search in variable tab 2025-11-10 18:45:06 +01:00
WarmUpTill
661e83162f Cleanup 2025-11-10 18:45:06 +01:00
WarmUpTill
4606f80a9d Enable filtering in "Hotkey" action 2025-11-10 18:45:06 +01:00
WarmUpTill
9c109742fb Add option to change action state based on label and action type
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-11-05 20:49:24 +01:00
WarmUpTill
b8b0682aaf Add support for else actions to MacroSegmentSelection 2025-11-05 20:49:24 +01:00
WarmUpTill
f93175db77 Sort scenes alphabetically
Prior to implementing canvas suppor they were in the order they are in
the scenes dock.
After switching from obs_frontend_get_scene_names() to
obs_canvas_enum_scenes() this seems to not always be the case.
2025-11-05 20:49:24 +01:00
WarmUpTill
8f92ba3ffa Increase slide index selection maximum to 9999 2025-11-05 20:49:24 +01:00
WarmUpTill
0d56de11d1 Refactor to support loading macro properties on import 2025-11-05 20:49:24 +01:00
WarmUpTill
e1020a1909 Fix poad load steps being executed too frequently
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-10-30 20:48:33 +01:00
WarmUpTill
8b0bd4193b Fix temp var save / loading not working 2025-10-30 20:48:33 +01:00
WarmUpTill
d55bb6bc86 Fall back to obs_frontend_get_current_scene()
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
If there wasn't any scene change yet GetCurrentScene() would always
return nullptr and break various scene checks.
For example, this could happen when startup up a fresh OBS install for
the first time.
2025-10-29 12:26:44 +01:00
WarmUpTill
0583331bfd Fix scene selection not working without secondary canvases 2025-10-29 12:26:44 +01:00
WarmUpTill
6932de866d Refactor Twitch event server migration and reconnect handling
Some checks failed
debian-build / build (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
Check locale / ubuntu64 (push) Has been cancelled
This should avoid any events being lost due to server migration.
2025-10-28 19:20:52 +01:00
WarmUpTill
00db0cf7c4 CI: Refactor OpenSSL handling on MacOS to support MQTT SSL 2025-10-28 19:20:52 +01:00
WarmUpTill
e9baf27ca2 Add helper to find recent versions of OpenSSL on Windows 2025-10-28 19:20:52 +01:00
WarmUpTill
b1a5db0c9c Fix crash when switching macros after deleting one containing temp refs 2025-10-28 19:20:52 +01:00
WarmUpTill
8f3b868fd9 Limit projector action to main canvas and improve layout 2025-10-28 19:20:52 +01:00
WarmUpTill
b3bf89840b Add GetPath() 2025-10-28 19:20:52 +01:00
WarmUpTill
84f7d0d214 Add SSL support to MQTT connections
Also fixes crash on startup if SSL was used while there was no support
for encrypted connections yet
2025-10-28 19:20:52 +01:00
WarmUpTill
e1164c4fa3 Refactor help icon usage 2025-10-28 19:20:52 +01:00
WarmUpTill
4534b23bad Enable Windows and MacOS build with OBS versions older than 31.1.1 2025-10-28 19:20:52 +01:00
90 changed files with 2738 additions and 1031 deletions

View File

@@ -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

View File

@@ -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}

View File

@@ -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}
)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()
@@ -141,6 +146,8 @@ target_sources(
lib/macro/macro-ref.hpp
lib/macro/macro-run-button.cpp
lib/macro/macro-run-button.hpp
lib/macro/macro-search.cpp
lib/macro/macro-search.hpp
lib/macro/macro-segment-copy-paste.cpp
lib/macro/macro-segment-copy-paste.hpp
lib/macro/macro-segment-list.cpp

View File

@@ -180,24 +180,46 @@ function(_check_dependencies)
set(url ${url}/${version}/${file})
endif()
set(MAX_DOWNLOAD_RETRIES 3)
set(RETRY_DELAY 60) # seconds
if(NOT EXISTS "${dependencies_dir}/${file}")
message(STATUS "Downloading ${url}")
file(
DOWNLOAD "${url}" "${dependencies_dir}/${file}"
STATUS download_status
EXPECTED_HASH SHA256=${hash})
list(GET download_status 0 error_code)
list(GET download_status 1 error_message)
if(error_code GREATER 0)
message(STATUS "Downloading ${url} - Failure")
set(download_success FALSE)
foreach(i RANGE 1 ${MAX_DOWNLOAD_RETRIES})
message(STATUS "Attempt ${i}/${MAX_DOWNLOAD_RETRIES} for ${url}")
file(
DOWNLOAD "${url}" "${dependencies_dir}/${file}"
STATUS download_status
EXPECTED_HASH SHA256=${hash})
list(GET download_status 0 error_code)
list(GET download_status 1 error_message)
if(error_code EQUAL 0)
message(STATUS "Downloading ${url} - success on attempt ${i}")
set(download_success TRUE)
break()
else()
message(WARNING "Download failed (attempt ${i}): ${error_message}")
file(REMOVE "${dependencies_dir}/${file}")
if(NOT i EQUAL MAX_DOWNLOAD_RETRIES)
message(STATUS "Retrying in ${RETRY_DELAY} seconds...")
execute_process(COMMAND ${CMAKE_COMMAND} -E sleep ${RETRY_DELAY})
endif()
endif()
endforeach()
if(NOT download_success)
message(
FATAL_ERROR
"Unable to download ${url}, failed with error: ${error_message}")
file(REMOVE "${dependencies_dir}/${file}")
else()
message(STATUS "Downloading ${url} - done")
"Unable to download ${url} after ${MAX_DOWNLOAD_RETRIES} attempts")
endif()
message(STATUS "Downloading ${url} - done")
endif()
if(NOT EXISTS "${dependencies_dir}/${destination}")

View 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()

View File

@@ -43,7 +43,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailure="Display warni
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage="<html><body>Loading of the following plugin libraries was unsuccessful, which could result in some Advanced Scene Switcher functions not being available:%1Check the OBS logs for details.<br>This message can be disabled on the General tab.</body></html>"
AdvSceneSwitcher.generalTab.generalBehavior.warnCorruptedInstallMessage="The plugin installation seems to be corrupted and might crash!\nPlease make sure the plugin was installed correctly!"
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="Hide tabs which can be represented via macros"
AdvSceneSwitcher.generalTab.generalBehavior.disableMacroWidgetCache="Disable macro widget caching"
AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowMacroSearch="Always show macro search"
AdvSceneSwitcher.generalTab.matchBehavior="Match behavior"
AdvSceneSwitcher.generalTab.priority="Priority"
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
@@ -74,6 +74,7 @@ AdvSceneSwitcher.generalTab.setTransitionBy="When changing transitions:"
AdvSceneSwitcher.generalTab.transitionOverride="Set transition overrides"
AdvSceneSwitcher.generalTab.adjustActiveTransitionType="Change active transition type"
AdvSceneSwitcher.generalTab.transitionBehaviorSelectionError="At least one option must be enabled:\n\n - Use transition overrides\n\n - Change active transition type"
AdvSceneSwitcher.generalTab.uiBehavior="UI settings"
# Variables Tab
AdvSceneSwitcher.variableTab.title="Variables"
@@ -91,6 +92,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"
@@ -270,6 +277,13 @@ AdvSceneSwitcher.macroTab.tooltip.elseActionUpButton="Move selected action up in
AdvSceneSwitcher.macroTab.tooltip.elseActionDownButton="Move selected action down in the else section"
AdvSceneSwitcher.macroTab.tooltip.elseActionBottomButton="Move selected action to the bottom in the else section"
AdvSceneSwitcher.macroTab.tooltip.openMacroSettingsButton="Open macro settings"
AdvSceneSwitcher.macroTab.search.placeholder="Search ..."
AdvSceneSwitcher.macroTab.search.showSettings="Show search settings"
AdvSceneSwitcher.macroTab.search.name="Name"
AdvSceneSwitcher.macroTab.search.allSegments="Segment type"
AdvSceneSwitcher.macroTab.search.conditions="Condition type"
AdvSceneSwitcher.macroTab.search.actions="Action type"
AdvSceneSwitcher.macroTab.search.label="Segment label"
AdvSceneSwitcher.macroDock.pause="Pause"
AdvSceneSwitcher.macroDock.unpause="Unpause"
@@ -928,6 +942,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 +966,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 +1002,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 +1163,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 +1537,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:"
@@ -1585,7 +1615,11 @@ AdvSceneSwitcher.twitchToken.moderator.chat.settings.manage="Manage channel's ch
AdvSceneSwitcher.twitchToken.user.whispers.manage="Manage user's whispers."
AdvSceneSwitcher.twitchToken.chat.read="View live stream chat messages."
AdvSceneSwitcher.twitchToken.chat.edit="Send live stream chat messages."
AdvSceneSwitcher.twitchToken.validateTimestamps="Validate timestamps of received Twitch event messages. (Recommended)"
AdvSceneSwitcher.twitchToken.validateTimestamps="Validate timestamps of received Twitch event messages."
AdvSceneSwitcher.twitchToken.validateTimestamps.tooltip="Verifies that incoming Twitch messages have valid timestamps to prevent replayed or spoofed messages.\nDisabling this wont affect normal functionality, and you can try turning it off if you experience issues with the Twitch connection."
AdvSceneSwitcher.twitchToken.warnIfInvalid="Display warning if the token expired or is invalid."
AdvSceneSwitcher.twitchToken.warnIfInvalid.doNotShowAgain="Don't show again for this connection"
AdvSceneSwitcher.twitchToken.warnIfInvalid.message="The Twitch connection \"%1\" is invalid.\nOpen connection settings or ignore?"
AdvSceneSwitcher.twitch.selection.channel.open="Open channel"
AdvSceneSwitcher.twitch.selection.channel.open.tooltip.details="Open channel in external application handling the HTTPS protocol."
@@ -2152,6 +2186,8 @@ AdvSceneSwitcher.tempVar.run.process.none.description="When not waiting for the
AdvSceneSwitcher.tempVar.recording.durationSeconds="Recording duration"
AdvSceneSwitcher.tempVar.recording.durationSeconds.description="Recording duration in seconds.\nThis value does not change while the recording is paused and will be reset to zero if the recording is stopped."
AdvSceneSwitcher.tempVar.recording.lastSavePath="Last save path"
AdvSceneSwitcher.tempVar.recording.lastSavePath.description="The file path of the last saved recording."
AdvSceneSwitcher.tempVar.video.patternCount="Pattern count"
AdvSceneSwitcher.tempVar.video.patternCount.description="The number of times the given pattern has been found in a given video input frame."
@@ -2301,6 +2337,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"

View File

@@ -42,7 +42,6 @@ AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailure="プラグイ
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage="<html><body>次のプラグイン ライブラリの読み込みに失敗したため、一部の高機能シーンスイッチャーが利用できなくなる可能性があります:%1詳細については、OBS ログを確認してください。<br>このメッセージは、[全般] タブで無効にすることができます。 </body></html>"
AdvSceneSwitcher.generalTab.generalBehavior.warnCorruptedInstallMessage="プラグインが正しくインストールできておらずクラッシュする可能性があります!\nプラグインが正しくインストールされているか確認してください"
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="旧来型マクロ設定のタブを非表示にする(廃止予定タブ)"
AdvSceneSwitcher.generalTab.generalBehavior.disableMacroWidgetCache="マクロウィジェットのキャッシングを無効にする"
AdvSceneSwitcher.generalTab.matchBehavior="マッチした場合の動作(廃止予定の設定項目)"
AdvSceneSwitcher.generalTab.priority="優先順位"
AdvSceneSwitcher.generalTab.priority.description="切り替え方法の優先順位(優先順位が高いものが上)"

View File

@@ -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:"

View File

@@ -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="没有匹配项时:"

View File

@@ -68,365 +68,162 @@
<x>0</x>
<y>0</y>
<width>962</width>
<height>1057</height>
<height>1132</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_19">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_31">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="statusBox">
<property name="title">
<string>AdvSceneSwitcher.generalTab.status</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<layout class="QGridLayout" name="statusLayout">
<item row="4" column="1">
<widget class="QComboBox" name="autoStartEvent"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.autoStart.startup</string>
</property>
</widget>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_33">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.checkInterval</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="checkIntervalTooLowWarning">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="startupBehavior"/>
</item>
<item row="1" column="1">
<widget class="QWidget" name="placeholder3" native="true"/>
</item>
<item row="1" column="0">
<widget class="QWidget" name="placeholder" native="true"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_64">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.onStartup</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QWidget" name="placeholder2" native="true"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.hotkeytips</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="checkInterval">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="suffix">
<string notr="true">ms</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>20000</number>
</property>
<property name="value">
<number>300</number>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="autoStartSceneEnable">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.autoStart.scene</string>
</property>
</widget>
</item>
<item row="5" column="1">
<layout class="QHBoxLayout" name="autoStartSceneLayout">
<item>
<widget class="advss::SceneSelectionWidget" name="autoStartScene" native="true"/>
</item>
<item>
<widget class="advss::VariableLineEdit" name="autoStartSceneName"/>
</item>
<item>
<widget class="advss::RegexConfigWidget" name="autoStartSceneNameRegex" native="true"/>
</item>
</layout>
</item>
</layout>
<widget class="QGroupBox" name="statusBox">
<property name="title">
<string>AdvSceneSwitcher.generalTab.status</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<layout class="QGridLayout" name="statusLayout">
<item row="4" column="1">
<widget class="QComboBox" name="autoStartEvent"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.autoStart.startup</string>
</property>
</widget>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_33">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.checkInterval</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="checkIntervalTooLowWarning">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="generalSettingsBox">
<property name="title">
<string>AdvSceneSwitcher.generalTab.generalBehavior</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_32">
</item>
<item row="3" column="1">
<widget class="QComboBox" name="startupBehavior"/>
</item>
<item row="1" column="1">
<widget class="QWidget" name="placeholder3" native="true"/>
</item>
<item row="1" column="0">
<widget class="QWidget" name="placeholder" native="true"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_64">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.onStartup</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QWidget" name="placeholder2" native="true"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.hotkeytips</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="checkInterval">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="suffix">
<string notr="true">ms</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>20000</number>
</property>
<property name="value">
<number>300</number>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="autoStartSceneEnable">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.autoStart.scene</string>
</property>
</widget>
</item>
<item row="5" column="1">
<layout class="QHBoxLayout" name="autoStartSceneLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_52">
<item>
<widget class="QLabel" name="label_21">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.logLevel</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="logLevel"/>
</item>
<item>
<spacer name="horizontalSpacer_49">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
<widget class="advss::SceneSelectionWidget" name="autoStartScene" native="true"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_50">
<item>
<widget class="QCheckBox" name="saveWindowGeo">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_55">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
<widget class="advss::VariableLineEdit" name="autoStartSceneName"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="showTrayNotifications">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_48">
<item>
<widget class="QCheckBox" name="uiHintsDisable">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_52">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QCheckBox" name="disableComboBoxFilter">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.comboBoxFilterDisable</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_13">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QCheckBox" name="disableMacroWidgetCache">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.disableMacroWidgetCache</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_26">
<item>
<widget class="QCheckBox" name="warnPluginLoadFailure">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailure</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_19">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_24">
<item>
<widget class="QCheckBox" name="hideLegacyTabs">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_16">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
<widget class="advss::RegexConfigWidget" name="autoStartSceneNameRegex" native="true"/>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_18">
<item>
<widget class="QGroupBox" name="saveLoadBox">
<property name="title">
<string>AdvSceneSwitcher.generalTab.saveOrLoadsettings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_17">
<item>
<widget class="QPushButton" name="exportSettings">
<property name="text">
<string>AdvSceneSwitcher.generalTab.saveOrLoadsettings.export</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="importSettings">
<property name="text">
<string>AdvSceneSwitcher.generalTab.saveOrLoadsettings.import</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_6">
<widget class="QGroupBox" name="saveLoadBox">
<property name="title">
<string>AdvSceneSwitcher.generalTab.matchBehavior</string>
<string>AdvSceneSwitcher.generalTab.saveOrLoadsettings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_37">
<layout class="QVBoxLayout" name="verticalLayout_17">
<item>
<layout class="QHBoxLayout" name="cooldownLayout">
<widget class="QPushButton" name="exportSettings">
<property name="text">
<string>AdvSceneSwitcher.generalTab.saveOrLoadsettings.export</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="importSettings">
<property name="text">
<string>AdvSceneSwitcher.generalTab.saveOrLoadsettings.import</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="generalSettingsBox">
<property name="title">
<string>AdvSceneSwitcher.generalTab.generalBehavior</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_32">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_52">
<item>
<widget class="QCheckBox" name="enableCooldown">
<widget class="QLabel" name="label_21">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.cooldown</string>
<string>AdvSceneSwitcher.generalTab.generalBehavior.logLevel</string>
</property>
</widget>
</item>
<item>
<widget class="advss::DurationSelection" name="cooldownTime" native="true"/>
<widget class="QComboBox" name="logLevel"/>
</item>
<item>
<spacer name="horizontalSpacer_21">
<spacer name="horizontalSpacer_49">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -441,80 +238,62 @@
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_43">
<item>
<widget class="QRadioButton" name="noMatchDontSwitch">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.dontSwitch</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_45">
<item>
<widget class="QRadioButton" name="noMatchRandomSwitch">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchToRandom</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="noMatchSwitch">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchTo</string>
</property>
</widget>
</item>
<item>
<widget class="advss::SceneSelectionWidget" name="noMatchSwitchScene">
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="noMatchLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
<widget class="QCheckBox" name="showTrayNotifications">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="warnPluginLoadFailure">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailure</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_6">
<property name="title">
<string>AdvSceneSwitcher.generalTab.uiBehavior</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="saveWindowGeo">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="uiHintsDisable">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="disableComboBoxFilter">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.comboBoxFilterDisable</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="alwaysShowMacroSearch">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.alwaysShowMacroSearch</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="hideLegacyTabs">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs</string>
</property>
</widget>
</item>
</layout>
</widget>
@@ -562,6 +341,109 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="matchBehaviorBox">
<property name="title">
<string>AdvSceneSwitcher.generalTab.matchBehavior</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_37">
<item>
<layout class="QHBoxLayout" name="cooldownLayout">
<item>
<widget class="QCheckBox" name="enableCooldown">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.cooldown</string>
</property>
</widget>
</item>
<item>
<widget class="advss::DurationSelection" name="cooldownTime" native="true"/>
</item>
<item>
<spacer name="horizontalSpacer_21">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<widget class="QRadioButton" name="noMatchDontSwitch">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.dontSwitch</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QRadioButton" name="noMatchRandomSwitch">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchToRandom</string>
</property>
</widget>
</item>
<item row="1" column="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="noMatchSwitch">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchTo</string>
</property>
</widget>
</item>
<item>
<widget class="advss::SceneSelectionWidget" name="noMatchSwitchScene" native="true"/>
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="noMatchLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="priorityBox">
<property name="title">
@@ -576,15 +458,7 @@
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_10">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_41">
<item>
<widget class="QListWidget" name="priorityList"/>
</item>
</layout>
</item>
</layout>
<widget class="QListWidget" name="priorityList"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_32">
@@ -721,6 +595,55 @@
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="macroSearchLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="macroSearchText">
<property name="placeholderText">
<string>AdvSceneSwitcher.macroTab.search.placeholder</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="macroSearchClear">
<property name="toolTip">
<string>AdvSceneSwitcher.variableTab.clear</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="macroSearchType"/>
</item>
<item>
<widget class="advss::RegexConfigWidget" name="macroSearchRegex" native="true"/>
</item>
<item>
<widget class="QPushButton" name="macroSearchShowSettings">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.search.showSettings</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="advss::MacroTree" name="macros">
<property name="showDropIndicator" stdset="0">
@@ -4164,12 +4087,7 @@
<tabstop>autoStartSceneName</tabstop>
<tabstop>autoStartSceneNameRegex</tabstop>
<tabstop>logLevel</tabstop>
<tabstop>saveWindowGeo</tabstop>
<tabstop>showTrayNotifications</tabstop>
<tabstop>uiHintsDisable</tabstop>
<tabstop>disableComboBoxFilter</tabstop>
<tabstop>warnPluginLoadFailure</tabstop>
<tabstop>hideLegacyTabs</tabstop>
<tabstop>exportSettings</tabstop>
<tabstop>importSettings</tabstop>
<tabstop>noMatchDontSwitch</tabstop>

View File

@@ -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());
}

View File

@@ -65,7 +65,6 @@ public slots:
void on_showTrayNotifications_stateChanged(int state);
void on_uiHintsDisable_stateChanged(int state);
void on_disableComboBoxFilter_stateChanged(int state);
void on_disableMacroWidgetCache_stateChanged(int state);
void on_warnPluginLoadFailure_stateChanged(int state);
void on_hideLegacyTabs_stateChanged(int state);
void on_priorityUp_clicked();

View File

@@ -3,6 +3,7 @@
#include "filter-combo-box.hpp"
#include "layout-helpers.hpp"
#include "macro.hpp"
#include "macro-search.hpp"
#include "macro-settings.hpp"
#include "path-helpers.hpp"
#include "selection-helpers.hpp"
@@ -185,16 +186,6 @@ void AdvSceneSwitcher::on_disableComboBoxFilter_stateChanged(int state)
FilterComboBox::SetFilterBehaviourEnabled(!state);
}
void AdvSceneSwitcher::on_disableMacroWidgetCache_stateChanged(int state)
{
if (loading) {
return;
}
switcher->disableMacroWidgetCache = state;
MacroSegmentList::SetCachingEnabled(!state);
}
void AdvSceneSwitcher::on_warnPluginLoadFailure_stateChanged(int state)
{
if (loading) {
@@ -459,7 +450,7 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
LoadHotkeys(obj);
LoadUISettings(obj);
RunPostLoadSteps();
RunAndClearPostLoadSteps();
// Reset on startup and scene collection change
ResetLastOpenedTab();
@@ -531,8 +522,6 @@ void SwitcherData::SaveGeneralSettings(obs_data_t *obj)
obs_data_set_bool(obj, "disableHints", disableHints);
obs_data_set_bool(obj, "disableFilterComboboxFilter",
disableFilterComboboxFilter);
obs_data_set_bool(obj, "disableMacroWidgetCache",
disableMacroWidgetCache);
obs_data_set_bool(obj, "warnPluginLoadFailure", warnPluginLoadFailure);
obs_data_set_bool(obj, "hideLegacyTabs", hideLegacyTabs);
@@ -602,8 +591,6 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
disableHints = obs_data_get_bool(obj, "disableHints");
disableFilterComboboxFilter =
obs_data_get_bool(obj, "disableFilterComboboxFilter");
disableMacroWidgetCache =
obs_data_get_bool(obj, "disableMacroWidgetCache");
obs_data_set_default_bool(obj, "warnPluginLoadFailure", true);
warnPluginLoadFailure = obs_data_get_bool(obj, "warnPluginLoadFailure");
obs_data_set_default_bool(obj, "hideLegacyTabs", true);
@@ -927,9 +914,6 @@ void AdvSceneSwitcher::SetupGeneralTab()
switcher->disableFilterComboboxFilter);
FilterComboBox::SetFilterBehaviourEnabled(
!switcher->disableFilterComboboxFilter);
ui->disableMacroWidgetCache->setChecked(
switcher->disableMacroWidgetCache);
MacroSegmentList::SetCachingEnabled(!switcher->disableMacroWidgetCache);
ui->warnPluginLoadFailure->setChecked(switcher->warnPluginLoadFailure);
ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs);
@@ -1013,6 +997,19 @@ void AdvSceneSwitcher::SetupGeneralTab()
switcher->CheckAutoStart();
});
ui->alwaysShowMacroSearch->setChecked(
GetMacroSearchSettings().showAlways);
connect(ui->alwaysShowMacroSearch, &QCheckBox::stateChanged, this,
[this](int enabled) {
GetMacroSearchSettings().showAlways = enabled;
if (loading) {
return;
}
CheckMacroSearchVisibility();
});
// Set up status control
auto statusControl = new StatusControl(this, true);
ui->statusLayout->addWidget(statusControl->StatusPrefixLabel(), 1, 0);

View File

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

View File

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

View File

@@ -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 &macroActions = _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 &regex)
{
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();

View File

@@ -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;

View File

@@ -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 ==

View File

@@ -55,6 +55,7 @@ public:
RANDOM_NUMBER,
QUERY_JSON,
ARRAY_JSON,
COPY_VAR,
};
Action _action = Action::SET_VALUE;

View File

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

View File

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

View File

@@ -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) {

View File

@@ -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");

View File

@@ -418,8 +418,7 @@ void MacroEdit::PopulateMacroActions(Macro &m, uint32_t afterIdx)
ui->actionsList->SetVisibilityCheckEnable(false);
for (; afterIdx < actions.size(); afterIdx++) {
auto newEntry = new MacroActionEdit(this, &actions[afterIdx],
actions[afterIdx]->GetId());
auto newEntry = new MacroActionEdit(this, &actions[afterIdx]);
ui->actionsList->Add(newEntry);
}
@@ -444,8 +443,7 @@ void MacroEdit::PopulateMacroElseActions(Macro &m, uint32_t afterIdx)
ui->elseActionsList->SetVisibilityCheckEnable(false);
for (; afterIdx < actions.size(); afterIdx++) {
auto newEntry = new MacroActionEdit(this, &actions[afterIdx],
actions[afterIdx]->GetId());
auto newEntry = new MacroActionEdit(this, &actions[afterIdx]);
ui->elseActionsList->Add(newEntry);
}
@@ -472,8 +470,7 @@ void MacroEdit::PopulateMacroConditions(Macro &m, uint32_t afterIdx)
for (; afterIdx < conditions.size(); afterIdx++) {
auto newEntry = new MacroConditionEdit(
this, &conditions[afterIdx],
conditions[afterIdx]->GetId(), root);
this, &conditions[afterIdx], root);
ui->conditionsList->Add(newEntry);
root = false;
}
@@ -1080,11 +1077,10 @@ 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,
new MacroActionEdit(this, &macro->Actions()[idx], id));
idx, new MacroActionEdit(this, &macro->Actions()[idx]));
SetActionData(*macro);
}
HighlightAction(idx);
@@ -1383,11 +1379,11 @@ 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(
this, &macro->ElseActions()[idx], id));
idx,
new MacroActionEdit(this, &macro->ElseActions()[idx]));
SetElseActionData(*macro);
}
HighlightElseAction(idx);
@@ -1583,13 +1579,13 @@ 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(
idx,
new MacroConditionEdit(this, &macro->Conditions()[idx],
id, idx == 0));
idx == 0));
SetConditionData(*macro);
}
HighlightCondition(idx);

View File

@@ -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,87 @@ 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>> &macros,
Macro *macro)
{
if (!macro) {
return;
}
const auto iterate = [&macros](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;
}
Macro *GetMacroByName(const char *name)
{
for (const auto &m : GetTopLevelMacros()) {
if (m->Name() == name) {
return m.get();
}
}
return nullptr;
}
Macro *GetMacroByQString(const QString &name)
{
return GetMacroByName(name.toUtf8().constData());
}
std::weak_ptr<Macro> GetWeakMacroByName(const char *name)
{
for (const auto &m : GetTopLevelMacros()) {
if (m->Name() == name) {
return m;
}
}
return {};
}
std::optional<std::deque<std::shared_ptr<MacroAction>>>
GetMacroActions(Macro *macro)
{
@@ -17,6 +99,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 +235,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;
}

View File

@@ -1,6 +1,8 @@
#pragma once
#include "export-symbol-helper.hpp"
#include <QString>
#include <atomic>
#include <chrono>
#include <condition_variable>
@@ -18,10 +20,18 @@ 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();
Macro *GetMacroByName(const char *name);
Macro *GetMacroByQString(const QString &name);
std::weak_ptr<Macro> GetWeakMacroByName(const char *name);
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 +78,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

336
lib/macro/macro-search.cpp Normal file
View File

@@ -0,0 +1,336 @@
#include "macro-search.hpp"
#include "layout-helpers.hpp"
#include "macro.hpp"
#include "macro-action-factory.hpp"
#include "macro-condition-factory.hpp"
#include "macro-helpers.hpp"
#include "macro-signals.hpp"
#include "plugin-state-helpers.hpp"
#include "ui-helpers.hpp"
#include <obs.hpp>
#include <QComboBox>
#include <QLayout>
#include <QLineEdit>
#include <QPushButton>
namespace advss {
static bool searchEnabled = false;
static bool setup();
static bool setupDone = setup();
static QLayout *searchLayout = nullptr;
static QComboBox *searchType = nullptr;
static RegexConfigWidget *searchRegex = nullptr;
static QPushButton *showSettings = nullptr;
static std::function<void()> refreshFilter;
static void save(obs_data_t *data)
{
GetMacroSearchSettings().Save(data, "macroSearchSettings");
}
static void load(obs_data_t *data)
{
GetMacroSearchSettings().Load(data, "macroSearchSettings");
}
static bool setup()
{
AddSaveStep(save);
AddLoadStep(load);
return true;
}
void MacroSearchSettings::Save(obs_data_t *data, const char *name)
{
OBSDataAutoRelease settings = obs_data_create();
obs_data_set_bool(settings, "showAlways", showAlways);
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 MacroSearchSettings::Load(obs_data_t *data, const char *name)
{
OBSDataAutoRelease settings = obs_data_get_obj(data, name);
showAlways = obs_data_get_bool(settings, "showAlways");
searchType = static_cast<SearchType>(
obs_data_get_int(settings, "searchType"));
searchString = obs_data_get_string(settings, "searchString");
regex.Load(settings);
}
static void showAdvancedSearchSettings(bool show)
{
assert(searchType);
assert(searchRegex);
searchType->setVisible(show);
searchRegex->setVisible(show);
}
static bool shouldShowSearch()
{
static const int showSearchThreshold = 10;
return GetMacroSearchSettings().showAlways ||
GetTopLevelMacros().size() >= showSearchThreshold;
}
void CheckMacroSearchVisibility()
{
assert(searchLayout);
assert(searchType);
assert(searchRegex);
assert(showSettings);
searchEnabled = shouldShowSearch();
SetLayoutVisible(searchLayout, searchEnabled);
showAdvancedSearchSettings(false);
showSettings->setChecked(false);
refreshFilter();
}
void SetupMacroSearchWidgets(QLayout *searchLayoutArg, QLineEdit *searchText,
QPushButton *searchClear, QComboBox *searchTypeArg,
RegexConfigWidget *searchRegexArg,
QPushButton *showSettingsArg,
const std::function<void()> &refreshArg)
{
searchLayout = searchLayoutArg;
searchType = searchTypeArg;
searchRegex = searchRegexArg;
refreshFilter = refreshArg;
showSettings = showSettingsArg;
searchClear->setMaximumWidth(22);
SetButtonIcon(searchClear, GetThemeTypeName() == "Light"
? "theme:Light/close.svg"
: "theme:Dark/close.svg");
searchClear->setDisabled(GetMacroSearchSettings().searchString.empty());
QWidget::connect(searchClear, &QPushButton::clicked, searchClear,
[=]() {
searchText->setText("");
searchClear->setDisabled(true);
});
const auto ph = searchText->placeholderText();
const QFontMetrics fm(searchText->font());
const int width = fm.horizontalAdvance(ph);
// Add a little padding so the text isn't cramped
searchText->setMinimumWidth(width + 10);
searchText->setText(
QString::fromStdString(GetMacroSearchSettings().searchString));
QWidget::connect(searchText, &QLineEdit::textChanged, searchText,
[searchClear](const QString &text) {
GetMacroSearchSettings().searchString =
text.toStdString();
searchClear->setDisabled(text.isEmpty());
refreshFilter();
});
searchType->addItem(
obs_module_text("AdvSceneSwitcher.macroTab.search.name"),
static_cast<int>(MacroSearchSettings::SearchType::NAME));
searchType->addItem(
obs_module_text("AdvSceneSwitcher.macroTab.search.allSegments"),
static_cast<int>(
MacroSearchSettings::SearchType::ALL_SEGMENTS));
searchType->addItem(
obs_module_text("AdvSceneSwitcher.macroTab.search.conditions"),
static_cast<int>(MacroSearchSettings::SearchType::CONDITIONS));
searchType->addItem(
obs_module_text("AdvSceneSwitcher.macroTab.search.actions"),
static_cast<int>(MacroSearchSettings::SearchType::ACTIONS));
searchType->addItem(
obs_module_text("AdvSceneSwitcher.macroTab.search.label"),
static_cast<int>(MacroSearchSettings::SearchType::LABEL));
searchType->setCurrentIndex(searchType->findData(
static_cast<int>(GetMacroSearchSettings().searchType)));
QWidget::connect(
searchType, &QComboBox::currentIndexChanged, searchType, []() {
GetMacroSearchSettings().searchType =
static_cast<MacroSearchSettings::SearchType>(
searchType->currentData().toInt());
refreshFilter();
});
searchRegex->SetRegexConfig(GetMacroSearchSettings().regex);
QWidget::connect(searchRegex, &RegexConfigWidget::RegexConfigChanged,
searchRegex, [](const RegexConfig &regex) {
GetMacroSearchSettings().regex = regex;
refreshFilter();
});
showSettings->setCheckable(true);
showSettings->setMaximumWidth(11);
SetButtonIcon(showSettings, GetThemeTypeName() == "Light"
? ":/res/images/dots-vert.svg"
: "theme:Dark/dots-vert.svg");
QWidget::connect(showSettings, &QPushButton::toggled, showSettings,
showAdvancedSearchSettings);
QWidget::connect(MacroSignalManager::Instance(),
&MacroSignalManager::Rename, searchText,
[]() { refreshFilter(); });
CheckMacroSearchVisibility();
}
MacroSearchSettings &GetMacroSearchSettings()
{
static MacroSearchSettings settings;
return settings;
}
static bool stringMatches(const std::string &text)
{
const auto &settings = GetMacroSearchSettings();
const auto &regex = settings.regex;
const auto &searchString = settings.searchString;
if (regex.Enabled()) {
return regex.Matches(text, searchString);
}
return QString::fromStdString(text).contains(
QString::fromStdString(searchString), Qt::CaseInsensitive);
}
static bool segmentTypeMatches(MacroSegment *segment, bool isCondition)
{
if (!segment) {
return false;
}
const auto getNameFromId =
isCondition ? MacroConditionFactory::GetConditionName
: MacroActionFactory::GetActionName;
const auto name = getNameFromId(segment->GetId());
return stringMatches(obs_module_text(name.c_str()));
}
static bool segmentLabelMatches(MacroSegment *segment)
{
if (!segment) {
return false;
}
if (!segment->GetUseCustomLabel()) {
return false;
}
const auto label = segment->GetCustomLabel();
return stringMatches(label);
}
bool MacroMatchesSearchFilter(Macro *macro)
{
assert(macro);
if (!searchEnabled) {
return true;
}
const auto &settings = GetMacroSearchSettings();
if (settings.searchString.empty()) {
return true;
}
if (macro->IsGroup()) {
std::vector<std::shared_ptr<Macro>> subItems;
subItems.reserve(macro->GroupSize());
const auto &macros = GetTopLevelMacros();
for (auto it = macros.begin(); it < macros.end(); it++) {
if ((*it)->Name() != macro->Name()) {
continue;
}
for (uint32_t i = 1; i <= macro->GroupSize(); i++) {
subItems.emplace_back(*std::next(it, i));
}
break;
}
for (const auto &item : subItems) {
if (MacroMatchesSearchFilter(item.get())) {
return true;
}
}
return false;
}
switch (settings.searchType) {
case MacroSearchSettings::SearchType::NAME: {
const auto name = macro->Name();
return stringMatches(name);
}
case MacroSearchSettings::SearchType::ALL_SEGMENTS:
for (const auto &condition : macro->Conditions()) {
if (segmentTypeMatches(condition.get(), true)) {
return true;
}
}
for (const auto &action : macro->Actions()) {
if (segmentTypeMatches(action.get(), false)) {
return true;
}
}
for (const auto &action : macro->ElseActions()) {
if (segmentTypeMatches(action.get(), false)) {
return true;
}
}
return false;
case MacroSearchSettings::SearchType::CONDITIONS:
for (const auto &condition : macro->Conditions()) {
if (segmentTypeMatches(condition.get(), true)) {
return true;
}
}
return false;
case MacroSearchSettings::SearchType::ACTIONS:
for (const auto &action : macro->Actions()) {
if (segmentTypeMatches(action.get(), false)) {
return true;
}
}
for (const auto &action : macro->ElseActions()) {
if (segmentTypeMatches(action.get(), false)) {
return true;
}
}
return false;
case MacroSearchSettings::SearchType::LABEL:
for (const auto &condition : macro->Conditions()) {
if (segmentLabelMatches(condition.get())) {
return true;
}
}
for (const auto &action : macro->Actions()) {
if (segmentLabelMatches(action.get())) {
return true;
}
}
for (const auto &action : macro->ElseActions()) {
if (segmentLabelMatches(action.get())) {
return true;
}
}
return false;
default:
break;
}
// Unhandled search type
assert(false);
return true;
}
} // namespace advss

View File

@@ -0,0 +1,43 @@
#pragma once
#include "regex-config.hpp"
#include <functional>
#include <string>
class QComboBox;
class QLayout;
class QLineEdit;
class QPushButton;
namespace advss {
class Macro;
struct MacroSearchSettings {
void Save(obs_data_t *data, const char *name);
void Load(obs_data_t *data, const char *name);
enum class SearchType {
NAME = 0,
ALL_SEGMENTS = 10,
CONDITIONS = 20,
ACTIONS = 30,
LABEL = 40,
};
SearchType searchType = SearchType::NAME;
std::string searchString;
RegexConfig regex;
bool showAlways = false;
};
void CheckMacroSearchVisibility();
void SetupMacroSearchWidgets(QLayout *searchLayout, QLineEdit *searchText,
QPushButton *searchClear, QComboBox *searchType,
RegexConfigWidget *searchRegex,
QPushButton *showSettings,
const std::function<void()> &refresh);
MacroSearchSettings &GetMacroSearchSettings();
bool MacroMatchesSearchFilter(Macro *macro);
} // namespace advss

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -4,6 +4,7 @@
#include "macro-condition-edit.hpp"
#include "macro-export-import-dialog.hpp"
#include "macro-settings.hpp"
#include "macro-search.hpp"
#include "macro-signals.hpp"
#include "macro-tree.hpp"
#include "macro.hpp"
@@ -150,7 +151,7 @@ static void addGroupSubitems(std::vector<std::shared_ptr<Macro>> &macros,
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;
@@ -207,7 +208,7 @@ void AdvSceneSwitcher::RemoveMacro(std::shared_ptr<Macro> &macro)
// Don't cache widgets for about to be deleted macros
MacroSegmentList::SetCachingEnabled(false);
ui->macros->Remove(macro);
MacroSegmentList::SetCachingEnabled(!switcher->disableMacroWidgetCache);
MacroSegmentList::SetCachingEnabled(true);
MacroSignalManager::Instance()->Remove(name);
}
@@ -410,13 +411,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 +430,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 +449,10 @@ void AdvSceneSwitcher::ImportMacros()
for (const auto &macro : importedMacros) {
macro->PostLoad();
}
RunPostLoadSteps();
RunAndClearPostLoadSteps();
tempMacros.clear();
ui->macros->Reset(GetMacros(),
ui->macros->Reset(GetTopLevelMacros(),
GetGlobalMacroSettings()._highlightExecuted);
disableAddButtonHighlight();
}
@@ -610,17 +616,20 @@ setupToolBar(const std::initializer_list<std::initializer_list<QWidget *>>
auto toolbar = new QToolBar();
toolbar->setIconSize({16, 16});
QAction *lastSeperator = nullptr;
QAction *lastSeparator = nullptr;
for (const auto &widgetGroup : widgetGroups) {
for (const auto &widget : widgetGroup) {
if (!widget) {
continue;
}
toolbar->addWidget(widget);
}
lastSeperator = toolbar->addSeparator();
lastSeparator = toolbar->addSeparator();
}
if (lastSeperator) {
toolbar->removeAction(lastSeperator);
if (lastSeparator) {
toolbar->removeAction(lastSeparator);
}
// Prevent "extension" button from showing up
@@ -633,7 +642,9 @@ void AdvSceneSwitcher::SetupMacroTab()
{
ui->macros->installEventFilter(this);
if (GetMacros().size() == 0 && !switcher->disableHints) {
auto &macros = GetTopLevelMacros();
if (macros.size() == 0 && !switcher->disableHints) {
addPulse = HighlightWidget(ui->macroAdd, QColor(Qt::green));
}
@@ -641,8 +652,7 @@ void AdvSceneSwitcher::SetupMacroTab()
{ui->macroUp, ui->macroDown}});
ui->macroControlLayout->addWidget(macroControls);
ui->macros->Reset(GetMacros(),
GetGlobalMacroSettings()._highlightExecuted);
ui->macros->Reset(macros, GetGlobalMacroSettings()._highlightExecuted);
connect(ui->macros, SIGNAL(MacroSelectionChanged()), this,
SLOT(MacroSelectionChanged()));
ui->runMacro->SetMacroTree(ui->macros);
@@ -671,6 +681,12 @@ void AdvSceneSwitcher::SetupMacroTab()
switcher->macroListMacroEditSplitterPosition);
}
}
SetupMacroSearchWidgets(ui->macroSearchLayout, ui->macroSearchText,
ui->macroSearchClear, ui->macroSearchType,
ui->macroSearchRegex,
ui->macroSearchShowSettings,
[this]() { ui->macros->RefreshFilter(); });
}
void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
@@ -752,7 +768,7 @@ void AdvSceneSwitcher::CopyMacro()
newMacro->Load(data);
newMacro->PostLoad();
newMacro->SetName(name);
RunPostLoadSteps();
RunAndClearPostLoadSteps();
Macro::PrepareMoveToGroup(macro->Parent(), newMacro);
ui->macros->Add(newMacro, macro);

View File

@@ -1,5 +1,6 @@
#include "macro-tree.hpp"
#include "macro.hpp"
#include "macro-search.hpp"
#include "macro-signals.hpp"
#include "path-helpers.hpp"
#include "sync-helpers.hpp"
@@ -783,7 +784,8 @@ void MacroTree::Reset(std::deque<std::shared_ptr<Macro>> &macros,
bool highlight)
{
_highlight = highlight;
MacroTreeModel *mtm = new MacroTreeModel(this, macros);
auto mtm = new MacroTreeModel(this, macros);
setModel(mtm);
GetModel()->Reset(macros);
connect(selectionModel(),
@@ -869,7 +871,7 @@ void MacroTree::UpdateWidgets(bool force)
for (int i = 0; i < (int)mtm->_macros.size(); i++) {
std::shared_ptr<Macro> item = mtm->_macros[i];
MacroTreeItem *widget = GetItemWidget(i);
auto widget = GetItemWidget(i);
if (!widget) {
UpdateWidget(mtm->createIndex(i, 0, nullptr), item);
@@ -1184,7 +1186,7 @@ bool MacroTree::SelectionEmpty() const
bool MacroTree::GroupsExist() const
{
for (const auto &macro : GetMacros()) {
for (const auto &macro : GetTopLevelMacros()) {
if (macro->IsGroup()) {
return true;
}
@@ -1204,6 +1206,12 @@ void MacroTree::CollapseGroup(std::shared_ptr<Macro> item) const
mtm->CollapseGroup(item);
}
void MacroTree::RefreshFilter()
{
UpdateWidgets();
doItemsLayout();
}
void MacroTree::MoveItemBefore(const std::shared_ptr<Macro> &item,
const std::shared_ptr<Macro> &after) const
{
@@ -1218,7 +1226,7 @@ void MacroTree::MoveItemAfter(const std::shared_ptr<Macro> &item,
MacroTreeModel *MacroTree::GetModel() const
{
return reinterpret_cast<MacroTreeModel *>(model());
return qobject_cast<MacroTreeModel *>(model());
}
void MacroTree::Remove(std::shared_ptr<Macro> item) const
@@ -1306,7 +1314,7 @@ void MacroTree::UngroupSelectedGroups()
void MacroTree::ExpandAll()
{
for (const auto &macro : GetMacros()) {
for (const auto &macro : GetTopLevelMacros()) {
if (!macro->IsGroup()) {
continue;
}
@@ -1316,7 +1324,7 @@ void MacroTree::ExpandAll()
void MacroTree::CollapseAll()
{
for (const auto &macro : GetMacros()) {
for (const auto &macro : GetTopLevelMacros()) {
if (!macro->IsGroup()) {
continue;
}
@@ -1333,8 +1341,8 @@ void MacroTree::SelectionChangedHelper(const QItemSelection &,
inline MacroTreeItem *MacroTree::GetItemWidget(int idx) const
{
QWidget *widget = indexWidget(GetModel()->createIndex(idx, 0, nullptr));
return reinterpret_cast<MacroTreeItem *>(widget);
auto widget = indexWidget(GetModel()->createIndex(idx, 0, nullptr));
return qobject_cast<MacroTreeItem *>(widget);
}
void MacroTree::paintEvent(QPaintEvent *event)
@@ -1361,14 +1369,25 @@ MacroTreeDelegate::MacroTreeDelegate(QObject *parent)
QSize MacroTreeDelegate::sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
MacroTree *tree = qobject_cast<MacroTree *>(parent());
QWidget *item = tree->indexWidget(index);
auto tree = qobject_cast<MacroTree *>(parent());
auto widget = tree->indexWidget(index);
if (!item) {
if (!widget) {
return QStyledItemDelegate::sizeHint(option, index);
}
return QSize(item->sizeHint());
auto name = index.data(Qt::AccessibleTextRole).toString();
auto macro = GetMacroByQString(name);
if (!macro) {
return QStyledItemDelegate::sizeHint(option, index);
}
if (MacroMatchesSearchFilter(macro)) {
return QSize(widget->sizeHint());
}
return QSize(0, 0);
}
} // namespace advss

View File

@@ -142,6 +142,7 @@ public:
bool GroupsExist() const;
void ExpandGroup(std::shared_ptr<Macro> item) const;
void CollapseGroup(std::shared_ptr<Macro> item) const;
void RefreshFilter();
public slots:
void GroupSelectedItems();

View File

@@ -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 &macros = 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 &macros = 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 &macros = 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,41 +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) {
if (m->Name() == name) {
return m.get();
}
}
return nullptr;
}
Macro *GetMacroByQString(const QString &name)
{
return GetMacroByName(name.toUtf8().constData());
}
std::weak_ptr<Macro> GetWeakMacroByName(const char *name)
{
for (const auto &m : macros) {
if (m->Name() == name) {
return m;
}
}
return {};
}
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 +1272,8 @@ void InvalidateMacroTempVarValues()
std::shared_ptr<Macro> GetMacroWithInvalidConditionInterval()
{
auto &macros = GetTopLevelMacros();
if (macros.empty()) {
return {};
}

View File

@@ -9,8 +9,6 @@
#include "temp-variable.hpp"
#include <future>
#include <QString>
#include <QByteArray>
#include <string>
#include <deque>
#include <memory>
@@ -217,13 +215,9 @@ 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();
Macro *GetMacroByName(const char *name);
Macro *GetMacroByQString(const QString &name);
std::weak_ptr<Macro> GetWeakMacroByName(const char *name);
void InvalidateMacroTempVarValues();
std::shared_ptr<Macro> GetMacroWithInvalidConditionInterval();

View File

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

View File

@@ -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)

View File

@@ -163,7 +163,6 @@ public:
bool warnPluginLoadFailure = true;
bool disableHints = false;
bool disableFilterComboboxFilter = false;
bool disableMacroWidgetCache = false;
bool hideLegacyTabs = true;
bool saveWindowGeo = false;
QPoint windowPos = {};

View File

@@ -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());
}

View File

@@ -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;

View File

@@ -67,7 +67,7 @@ int GetCanvasCount()
return 1;
#else
static const auto enumCanvases = [](void *countPtr,
obs_canvas_t *canvas) -> bool {
obs_canvas_t *) -> bool {
auto count = static_cast<int *>(countPtr);
(*count)++;
return true;
@@ -145,6 +145,15 @@ OBSWeakSource GetActiveCanvasScene(const OBSWeakCanvas &weakCanvas)
}
#endif
OBSCanvasAutoRelease mainCanvas = obs_get_main_canvas();
// The frontend scene order does not necessarily match the order of the
// scenes in the canvas.
if (canvas == mainCanvas) {
OBSSourceAutoRelease scene = obs_frontend_get_current_scene();
return OBSGetWeakRef(scene);
}
static const auto enumCanvasScenes = [](void *scenePtr,
obs_source_t *source) -> bool {
auto scene = static_cast<OBSWeakSource *>(scenePtr);
@@ -181,6 +190,25 @@ OBSWeakSource GetSceneAtIndex(const OBSWeakCanvas &weakCanvas, int idx)
}
#endif
OBSCanvasAutoRelease mainCanvas = obs_get_main_canvas();
// The frontend scene order does not necessarily match the order of the
// scenes in the canvas.
// So, rely on obs_frontend_get_scenes() in case of the main canvas
// instead.
if (canvas == mainCanvas) {
struct obs_frontend_source_list scenes = {0};
obs_frontend_get_scenes(&scenes);
if (idx >= (int)scenes.sources.num) {
obs_frontend_source_list_free(&scenes);
return nullptr;
}
auto scene = OBSGetWeakRef(scenes.sources.array[idx]);
obs_frontend_source_list_free(&scenes);
return scene;
}
struct Data {
const int idx;
int currentIdx;
@@ -223,6 +251,29 @@ int GetIndexOfScene(const OBSWeakCanvas &weakCanvas, const OBSWeakSource &scene)
return 0;
}
#endif
OBSCanvasAutoRelease mainCanvas = obs_get_main_canvas();
// The frontend scene order does not necessarily match the order of the
// scenes in the canvas.
// So, rely on obs_frontend_get_scenes() in case of the main canvas
// instead.
if (canvas == mainCanvas) {
struct obs_frontend_source_list scenes = {0};
obs_frontend_get_scenes(&scenes);
OBSSourceAutoRelease sceneSource = OBSGetStrongRef(scene);
int idx = 0;
for (size_t i = 0; i < scenes.sources.num; i++) {
if (scenes.sources.array[i] == sceneSource) {
idx = i;
break;
}
}
obs_frontend_source_list_free(&scenes);
return idx;
}
struct Data {
int idx;
@@ -246,6 +297,12 @@ int GetIndexOfScene(const OBSWeakCanvas &weakCanvas, const OBSWeakSource &scene)
return data.idx;
}
OBSWeakCanvas GetMainCanvas()
{
OBSCanvasAutoRelease mainCanvas = obs_get_main_canvas();
return OBSGetWeakRef(mainCanvas);
}
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(31, 1, 0)
obs_canvas_t *obs_canvas_get_ref(obs_canvas_t *)

View File

@@ -42,6 +42,7 @@ EXPORT OBSWeakSource GetActiveCanvasScene(const OBSWeakCanvas &canvas);
EXPORT OBSWeakSource GetSceneAtIndex(const OBSWeakCanvas &weakCanvas, int idx);
EXPORT int GetIndexOfScene(const OBSWeakCanvas &weakCanvas,
const OBSWeakSource &scene);
EXPORT OBSWeakCanvas GetMainCanvas();
class CanvasSelection : public FilterComboBox {
Q_OBJECT

View File

@@ -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);

View File

@@ -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);

View File

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

View File

@@ -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()

View File

@@ -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()>);

View File

@@ -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,

View File

@@ -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);

View File

@@ -103,10 +103,11 @@ void SceneSelection::Load(obs_data_t *obj, const char *name,
OBSDataAutoRelease data =
obs_data_get_obj(obj, selectionSaveName.data());
if (obs_data_has_user_value(data, canvasSaveName.data())) {
_canvas = GetWeakCanvasByName(
obs_data_get_string(data, canvasSaveName.data()));
} else {
const char *canvasName =
obs_data_get_string(data, canvasSaveName.data());
_canvas = GetWeakCanvasByName(
obs_data_get_string(data, canvasSaveName.data()));
if (strcmp(canvasName, "") == 0) {
OBSCanvasAutoRelease mainCanvas = obs_get_main_canvas();
_canvas = obs_canvas_get_weak_canvas(mainCanvas);
obs_weak_canvas_release(_canvas);
@@ -239,22 +240,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 +335,7 @@ static QStringList getCanvasScenesList(obs_weak_canvas_t *weakCanvas)
QStringList list;
obs_canvas_enum_scenes(canvas, enumCanvasScenes, &list);
list.sort();
return list;
}
@@ -342,6 +348,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 +428,7 @@ SceneSelectionWidget::SceneSelectionWidget(QWidget *parent, bool variables,
layout->setContentsMargins(0, 0, 0, 0);
if (GetCanvasCount() <= 1) {
_canvas->hide();
LockToMainCanvas();
}
Resize();

View File

@@ -30,7 +30,7 @@ public:
private:
OBSWeakSource _scene;
OBSWeakCanvas _canvas;
OBSWeakCanvas _canvas = GetMainCanvas();
SceneGroup *_group = nullptr;
std::weak_ptr<Variable> _variable;
Type _type = Type::SCENE;
@@ -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;

View File

@@ -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()

View File

@@ -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>> &macros,
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 *> &macros)
{
@@ -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 &macro : GetMacros()) {
macros.reserve(topLevelMacros.size());
for (const auto &macro : 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 &macroShared : 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)

View File

@@ -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;
};

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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 &regex) {
_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());
}
@@ -270,36 +468,26 @@ static void setupTab(QTabWidget *tab)
}
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);
}
});
&VariableSignalManager::Add, tab,
[tab](const QString &) { 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

View File

@@ -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

View File

@@ -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()

View File

@@ -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(

View File

@@ -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;
};

View File

@@ -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

View File

@@ -137,31 +137,51 @@ bool MacroActionSwitchScene::WaitForTransition(OBSWeakSource &scene,
bool MacroActionSwitchScene::PerformAction()
{
OBSWeakSource sceneToSwitchTo;
OBSWeakCanvas canvas;
OBSWeakCanvas canvasToSwitchFor;
OBSCanvasAutoRelease mainCanvas = obs_get_main_canvas();
OBSWeakCanvas mainCanvasWeak = OBSGetWeakRef(mainCanvas);
const auto getActiveSceneHelper = [this](const OBSWeakCanvas &canvas) {
OBSWeakSource activeScene;
if (IsMainCanvas(canvas) && _sceneType == SceneType::PREVIEW) {
OBSSourceAutoRelease previewSource =
obs_frontend_get_current_preview_scene();
return OBSGetWeakRef(previewSource);
} else {
return GetActiveCanvasScene(canvas);
}
};
switch (_action) {
case Action::SCENE_NAME:
sceneToSwitchTo = _scene.GetScene();
canvas = _scene.GetCanvas();
canvasToSwitchFor = _scene.GetCanvas();
break;
case Action::SCENE_LIST_NEXT: {
const auto activeScene = GetActiveCanvasScene(_canvas);
const int currentIndex = GetIndexOfScene(_canvas, activeScene);
sceneToSwitchTo = GetSceneAtIndex(_canvas, currentIndex + 1);
canvas = _canvas;
canvasToSwitchFor = _canvas ? _canvas : mainCanvasWeak;
const auto activeScene =
getActiveSceneHelper(canvasToSwitchFor);
const int currentIndex =
GetIndexOfScene(canvasToSwitchFor, activeScene);
sceneToSwitchTo =
GetSceneAtIndex(canvasToSwitchFor, currentIndex + 1);
break;
}
case Action::SCENE_LIST_PREVIOUS: {
const auto activeScene = GetActiveCanvasScene(_canvas);
const int currentIndex = GetIndexOfScene(_canvas, activeScene);
sceneToSwitchTo = GetSceneAtIndex(_canvas, currentIndex - 1);
canvas = _canvas;
canvasToSwitchFor = _canvas ? _canvas : mainCanvasWeak;
const auto activeScene =
getActiveSceneHelper(canvasToSwitchFor);
const int currentIndex =
GetIndexOfScene(canvasToSwitchFor, activeScene);
sceneToSwitchTo =
GetSceneAtIndex(canvasToSwitchFor, currentIndex - 1);
break;
}
case Action::SCENE_LIST_INDEX: {
sceneToSwitchTo =
GetSceneAtIndex(_canvas, _index.GetValue() - 1);
canvas = _canvas;
canvasToSwitchFor = _canvas ? _canvas : mainCanvasWeak;
sceneToSwitchTo = GetSceneAtIndex(canvasToSwitchFor,
_index.GetValue() - 1);
break;
}
default:
@@ -176,11 +196,12 @@ bool MacroActionSwitchScene::PerformAction()
}
auto transition = _transition.GetTransition();
SwitchScene({sceneToSwitchTo, transition,
(int)(_duration.Milliseconds())},
obs_frontend_preview_program_mode_active(), canvas);
SwitchScene(
{sceneToSwitchTo, transition, (int)(_duration.Milliseconds())},
obs_frontend_preview_program_mode_active(), canvasToSwitchFor);
if (_blockUntilTransitionDone && sceneToSwitchTo &&
sceneToSwitchTo != GetCurrentScene() && IsMainCanvas(canvas)) {
sceneToSwitchTo != GetCurrentScene() &&
IsMainCanvas(canvasToSwitchFor)) {
return WaitForTransition(sceneToSwitchTo, transition);
}
return true;
@@ -240,7 +261,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");
@@ -392,6 +413,7 @@ void MacroActionSwitchSceneEdit::SetWidgetData() const
_sceneTypes->setCurrentIndex(_sceneTypes->findData(
static_cast<int>(_entryData->_sceneType)));
_scenes->SetScene(_entryData->_scene);
_canvas->SetCanvas(_entryData->_canvas);
_transitions->SetTransition(_entryData->_transition);
_duration->SetDuration(_entryData->_duration);
_blockUntilTransitionDone->setChecked(
@@ -431,7 +453,7 @@ void MacroActionSwitchSceneEdit::SceneSelectionCanvasChanged(
SetWidgetVisibility();
}
void MacroActionSwitchSceneEdit::ActionChanged(int value)
void MacroActionSwitchSceneEdit::ActionChanged(int)
{
GUARD_LOADING_AND_LOCK();
_entryData->_action = static_cast<MacroActionSwitchScene::Action>(
@@ -561,9 +583,17 @@ void MacroActionSwitchSceneEdit::SetWidgetVisibility()
{{"{{transitions}}", _transitions},
{"{{duration}}", _duration}});
const auto canvas = action == MacroActionSwitchScene::Action::SCENE_NAME
? _entryData->_scene.GetCanvas()
: _entryData->_canvas;
OBSWeakCanvas canvas;
if (action == MacroActionSwitchScene::Action::SCENE_NAME) {
canvas = _entryData->_scene.GetCanvas();
} else {
if (_entryData->_canvas) {
canvas = _entryData->_canvas;
} else {
OBSCanvasAutoRelease mainCanvas = obs_get_main_canvas();
canvas = OBSGetWeakRef(mainCanvas);
}
}
const bool isMainCanvas = IsMainCanvas(canvas);
SetLayoutVisible(_transitionLayout, isMainCanvas);
_blockUntilTransitionDone->setVisible(isMainCanvas);
@@ -580,7 +610,7 @@ void MacroActionSwitchSceneEdit::SetWidgetVisibility()
_notSupportedWarning->setText(
fmt.arg(QString::fromStdString(GetWeakCanvasName(canvas))));
_notSupportedWarning->setVisible(
!IsMainCanvas(canvas) &&
!isMainCanvas &&
_entryData->_sceneType ==
MacroActionSwitchScene::SceneType::PREVIEW);

View File

@@ -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();
}

View File

@@ -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;

View File

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

View File

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

View File

@@ -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) {

View File

@@ -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;
};

View File

@@ -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,

View File

@@ -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);

View File

@@ -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)

View File

@@ -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"

View File

@@ -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;

View File

@@ -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;

View File

@@ -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)

View File

@@ -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

View File

@@ -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;

View File

@@ -1,6 +1,7 @@
#include "token.hpp"
#include "twitch-helpers.hpp"
#include <help-icon.hpp>
#include <layout-helpers.hpp>
#include <log-helper.hpp>
#include <obs-module-helper.hpp>
@@ -163,7 +164,8 @@ TwitchToken::TwitchToken(const TwitchToken &other)
_userID(other._userID),
_tokenOptions(other._tokenOptions),
_eventSub(),
_validateEventSubTimestamps(other._validateEventSubTimestamps)
_validateEventSubTimestamps(other._validateEventSubTimestamps),
_warnIfInvalid(other._warnIfInvalid)
{
}
@@ -174,6 +176,7 @@ TwitchToken &TwitchToken::operator=(const TwitchToken &other)
_userID = other._userID;
_tokenOptions = other._tokenOptions;
_validateEventSubTimestamps = other._validateEventSubTimestamps;
_warnIfInvalid = other._warnIfInvalid;
return *this;
}
@@ -185,6 +188,9 @@ void TwitchToken::Load(obs_data_t *obj)
obs_data_set_default_bool(obj, "validateEventSubTimestamps", true);
_validateEventSubTimestamps =
obs_data_get_bool(obj, "validateEventSubTimestamps");
_warnIfInvalid = obs_data_has_user_value(obj, "warnIfInvalid")
? obs_data_get_bool(obj, "warnIfInvalid")
: true;
_tokenOptions.clear();
OBSDataArrayAutoRelease options = obs_data_get_array(obj, "options");
size_t count = obs_data_array_count(options);
@@ -203,6 +209,7 @@ void TwitchToken::Save(obs_data_t *obj) const
obs_data_set_string(obj, "userID", _userID.c_str());
obs_data_set_bool(obj, "validateEventSubTimestamps",
_validateEventSubTimestamps);
obs_data_set_bool(obj, "warnIfInvalid", _warnIfInvalid);
OBSDataArrayAutoRelease options = obs_data_array_create();
for (auto &option : _tokenOptions) {
OBSDataAutoRelease arrayObj = obs_data_create();
@@ -477,7 +484,9 @@ TwitchTokenSettingsDialog::TwitchTokenSettingsDialog(
_tokenStatus(new QLabel()),
_generalSettingsGrid(new QGridLayout()),
_validateTimestamps(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.twitchToken.validateTimestamps")))
"AdvSceneSwitcher.twitchToken.validateTimestamps"))),
_warnIfInvalid(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.twitchToken.warnIfInvalid")))
{
_showToken->setMaximumWidth(22);
_showToken->setFlat(true);
@@ -539,11 +548,18 @@ TwitchTokenSettingsDialog::TwitchTokenSettingsDialog(
scrollArea->setWidgetResizable(true);
scrollArea->setFrameShape(QFrame::NoFrame);
auto timestampsLayout = new QHBoxLayout();
timestampsLayout->addWidget(_validateTimestamps);
auto timestampHelp = new HelpIcon(obs_module_text(
"AdvSceneSwitcher.twitchToken.validateTimestamps.tooltip"));
timestampsLayout->addWidget(timestampHelp);
auto contentWidget = new QWidget(scrollArea);
auto layout = new QVBoxLayout(contentWidget);
layout->addLayout(_generalSettingsGrid);
layout->addWidget(optionsBox);
layout->addWidget(_validateTimestamps);
layout->addLayout(timestampsLayout);
layout->addWidget(_warnIfInvalid);
layout->setContentsMargins(0, 0, 0, 0);
scrollArea->setWidget(contentWidget);
@@ -569,6 +585,7 @@ TwitchTokenSettingsDialog::TwitchTokenSettingsDialog(
#ifndef VERIFY_TIMESTAMPS
_validateTimestamps->hide();
#endif
_warnIfInvalid->setChecked(settings._warnIfInvalid);
_currentToken = settings;
@@ -777,6 +794,8 @@ bool TwitchTokenSettingsDialog::AskForSettings(QWidget *parent,
settings._tokenOptions = dialog.GetEnabledOptions();
settings._validateEventSubTimestamps =
dialog._validateTimestamps->isChecked();
settings._warnIfInvalid = dialog._warnIfInvalid->isChecked();
if (settings._eventSub) {
settings._eventSub->EnableTimestampValidation(
settings._validateEventSubTimestamps);

View File

@@ -55,6 +55,8 @@ public:
std::shared_ptr<EventSub> GetEventSub();
bool ValidateTimestamps() const { return _validateEventSubTimestamps; }
bool IsValid(bool forceUpdate = false) const;
bool WarnIfInvalid() const { return _warnIfInvalid; }
void SetWarnIfInvalid(bool value) { _warnIfInvalid = value; }
size_t PermissionCount() const { return _tokenOptions.size(); }
private:
@@ -67,6 +69,7 @@ private:
std::set<TokenOption> _tokenOptions = TokenOption::GetAllTokenOptions();
std::shared_ptr<EventSub> _eventSub;
bool _validateEventSubTimestamps = false;
bool _warnIfInvalid = true;
static bool _setup;
@@ -131,6 +134,7 @@ private:
TwitchToken _currentToken;
std::unordered_map<std::string, QCheckBox *> _optionWidgets;
QCheckBox *_validateTimestamps;
QCheckBox *_warnIfInvalid;
QTimer _validationTimer;
};

View File

@@ -144,9 +144,14 @@ static void handleThrottling(const httplib::Result &response)
return;
}
blog(LOG_WARNING, "Twitch API access is throttled for %lld seconds!",
if (sleepDuration < std::chrono::seconds(1)) {
sleepDuration = std::chrono::seconds(1);
}
blog(LOG_WARNING,
"Twitch API access is throttled for %lld milliseconds!",
static_cast<long long int>(
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::duration_cast<std::chrono::milliseconds>(
sleepDuration)
.count()));
apiIsThrottling = true;

View File

@@ -1,24 +1,47 @@
#include "twitch-tab.hpp"
#include "obs-module-helper.hpp"
#include "plugin-state-helpers.hpp"
#include "sync-helpers.hpp"
#include "tab-helpers.hpp"
#include "token.hpp"
#include "ui-helpers.hpp"
#include <QDialogButtonBox>
#include <QTabWidget>
namespace advss {
static bool registerTab();
static bool setup();
static void setupTab(QTabWidget *);
static bool registerTabDone = registerTab();
static bool setupDone = setup();
static TwitchConnectionsTable *tabWidget = nullptr;
static bool registerTab()
static QStringList getInvalidTokens();
static bool setup()
{
AddSetupTabCallback("twitchConnectionTab",
TwitchConnectionsTable::Create, setupTab);
static const auto showInvalidWarnings = []() {
const auto invalidTokens = getInvalidTokens();
for (const auto &token : invalidTokens) {
QueueUITask(
[](void *tokenPtr) {
auto tokenName = static_cast<QString *>(
tokenPtr);
InvalidTokenDialog::ShowWarning(
*tokenName);
},
(void *)&token);
}
};
AddLoadStep([](obs_data_t *) {
AddPostLoadStep([]() { showInvalidWarnings(); });
});
return true;
}
@@ -188,6 +211,25 @@ static void updateConnectionStatus(QTableWidget *table)
}
}
static QStringList getInvalidTokens()
{
QStringList tokens;
for (const auto &t : GetTwitchTokens()) {
if (!t) {
continue;
}
auto token = std::static_pointer_cast<TwitchToken>(t);
if (!token->WarnIfInvalid() || token->IsValid(true)) {
continue;
}
tokens << QString::fromStdString(token->GetName());
}
return tokens;
}
static void setupTab(QTabWidget *tab)
{
if (GetTwitchTokens().empty()) {
@@ -222,6 +264,63 @@ static void setupTab(QTabWidget *tab)
updateConnectionStatus(tabWidget->Table());
}
});
const auto invalidTokens = getInvalidTokens();
for (const auto &token : invalidTokens) {
// Constructing the warning dialog in the constructor of the settings
// window might lead to a crash, so wait for the settings window
// constructor to complete
QTimer::singleShot(0, tab, [token]() {
InvalidTokenDialog::ShowWarning(token);
});
}
}
void InvalidTokenDialog::ShowWarning(const QString &tokenName)
{
auto weakToken = GetWeakTwitchTokenByQString(tokenName);
auto token = weakToken.lock();
if (!token) {
return;
}
auto dialog = new InvalidTokenDialog(tokenName);
dialog->setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
const bool ignore = dialog->exec() != DialogCode::Accepted;
token->SetWarnIfInvalid(!dialog->_doNotShowAgain->isChecked());
dialog->deleteLater();
if (ignore) {
return;
}
TwitchTokenSettingsDialog::AskForSettings(GetSettingsWindow(),
*token.get());
}
InvalidTokenDialog::InvalidTokenDialog(const QString &name)
: QDialog(GetSettingsWindow()),
_doNotShowAgain(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.twitchToken.warnIfInvalid.doNotShowAgain")))
{
auto buttons = new QDialogButtonBox(QDialogButtonBox::Ignore |
QDialogButtonBox::Open);
connect(buttons->button(QDialogButtonBox::Ignore),
&QPushButton::clicked, this, &QDialog::reject);
connect(buttons->button(QDialogButtonBox::Open), &QPushButton::clicked,
this, &QDialog::accept);
buttons->setCenterButtons(true);
const QString format(obs_module_text(
"AdvSceneSwitcher.twitchToken.warnIfInvalid.message"));
const QString message = format.arg(name);
auto layout = new QVBoxLayout;
layout->addWidget(new QLabel(message));
layout->addWidget(_doNotShowAgain);
layout->addWidget(buttons);
setLayout(layout);
}
} // namespace advss

View File

@@ -1,6 +1,9 @@
#pragma once
#include "resource-table.hpp"
#include <QCheckBox>
#include <QDialog>
namespace advss {
class TwitchConnectionsTable final : public ResourceTable {
@@ -17,4 +20,14 @@ private:
TwitchConnectionsTable(QTabWidget *parent = nullptr);
};
class InvalidTokenDialog : public QDialog {
Q_OBJECT
public:
static void ShowWarning(const QString &name);
private:
InvalidTokenDialog(const QString &name);
QCheckBox *_doNotShowAgain;
};
} // namespace advss

View File

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

View File

@@ -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()
{