Compare commits

..

53 Commits

Author SHA1 Message Date
WarmUpTill
17d9b73b9a Fix thread flood when Twitch event sub connection is lost
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-03-15 12:31:13 +01:00
WarmUpTill
d6185a6099 Add handling for Twitch API throttling 2025-03-15 12:31:13 +01:00
WarmUpTill
41c06b9edb Refactor Twitch condition event handling 2025-03-15 12:31:13 +01:00
WarmUpTill
d8807077da Add option to query reward information 2025-03-15 12:31:13 +01:00
WarmUpTill
34e125f82d Rework chache handling
* No longer provide multiple HTTP helper functions with the same name
* Default to cache not being used
* Explicitly use the cache for certain Twitch actions and conditions
* Clear caches on shutdown to avoid leaks being reported
2025-03-15 12:31:13 +01:00
WarmUpTill
d670b6d07e Move JSON temp var heper functions 2025-03-15 12:31:13 +01:00
WarmUpTill
7b0f985f18 Add macro properties for 'reward' object in redemption events 2025-03-15 12:31:13 +01:00
WarmUpTill
212be923f6 Add option to query user information 2025-03-15 12:31:13 +01:00
WarmUpTill
88fcb57e9f Cleanup 2025-03-15 12:31:13 +01:00
WarmUpTill
9b609c118f Fix disabling chat emote only mode attempting to send chat messages 2025-03-15 12:31:13 +01:00
WarmUpTill
37c398d37a Indicate that "Set to condition/action value" should be avoided 2025-03-15 12:31:13 +01:00
WarmUpTill
a51b7f6b13 Fix UI theming issues
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-03-11 22:12:05 +01:00
WarmUpTill
808fd84b83 Remove Qt5 support 2025-03-11 22:12:05 +01:00
WarmUpTill
57bcea15f5 cmake: Replace find_qt with find_package 2025-03-10 19:48:01 +01:00
WarmUpTill
4e51b56b9b Add game capture condition 2025-03-10 18:49:07 +01:00
WarmUpTill
ef5cf41d34 Add OBS 31 support 2025-03-10 18:49:07 +01:00
WarmUpTill
911e79ea6a Fix OCR controls showing in place of color controls
This happened when OCR support was disabled at build time
2025-03-10 18:33:13 +01:00
WarmUpTill
d586177de0 Add screenshot condition
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-03-09 20:57:51 +01:00
WarmUpTill
202c36646c Fix UI issues when opening ItemSettingsDialogs
Some widgets were displayed incorrectly if the parent of the dialog
window is not the main settings window
2025-03-09 20:05:07 +01:00
WarmUpTill
ab5102f5ca Fix typo 2025-03-09 20:05:07 +01:00
WarmUpTill
e612fb99f6 Cleanup
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
* Reorder action type selection
* Refactor
2025-03-06 21:30:03 +01:00
WarmUpTill
691e77a69a Add option to generate random number 2025-03-06 21:30:03 +01:00
Kaito Udagawa
930118c61f Call Setup() after Load()
Some checks are pending
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
Tesseract API depends on the languageCode and this must be reinitialized after Load
2025-03-06 08:43:54 +01:00
Kaito Udagawa
1e05f2f2ce Fix languageCode
LanguageCode is not respected by OCRParameters and settings properly.
2025-03-06 08:43:54 +01:00
WarmUpTill
ae25b4d023 Improve setting selection handling
Some checks are pending
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
Will now try to keep the current selection if possible.
This can be useful when switching between source of the same time.
2025-03-05 14:16:04 +01:00
WarmUpTill
af055a12a1 Cleanup 2025-03-05 14:16:04 +01:00
WarmUpTill
7de28eedd3 Improve settings button selection handling
Will now try to keep the current button selection if possible.
This can be useful when switching between source of the same time.
2025-03-05 14:16:04 +01:00
WarmUpTill
8dfe81f522 Display failure if Twitch token account name cannot be fetched
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-02-27 19:52:41 +01:00
WarmUpTill
a1d8ae291d Add option to hide empty name warning in ItemSettingsDialog 2025-02-27 19:52:41 +01:00
WarmUpTill
eb6989527d Add support for libproc2 version 4.0.5
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-02-10 18:12:08 +01:00
WarmUpTill
2986a5dd96 Fix crash when actions were copied into a action queue
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
The steps added via AddPostLoadStep() from the copy were never executed.
Thus they might be invalid when RunPostLoadSteps() is next called.

This could result in a crash when a new action or condition is added
after e.g. a "Filter" action's TempVariableRef was copied, which adds a
PostLoadStep to resolve the macro segment reference.
2025-02-04 08:26:04 +01:00
WarmUpTill
816ee9b244 Fix crash when changing Twitch condition type 2025-02-04 08:26:04 +01:00
WarmUpTill
51b53bf948 Fix script actions / conditions not working after stopping macro
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-01-30 09:39:50 +01:00
WarmUpTill
2c5121ee94 Prevent crash when macro contains invalid action or condition 2025-01-30 09:39:50 +01:00
WarmUpTill
6a58684854 Fix import of incomplete settings not resetting duration to zero
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-01-23 19:00:51 +01:00
WarmUpTill
0b622fdbed Fix crash when importing settings 2025-01-23 19:00:51 +01:00
WarmUpTill
3bce8e075f CI: Switch to and adapt to Ubuntu 24 runner
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-01-15 21:36:54 +01:00
WarmUpTill
f598d84ea1 Fix freeze due to ResizingPlainTextEdit constantly resizing incorrectly 2025-01-15 21:36:35 +01:00
WarmUpTill
d7a244e80e Fix transition condition not considering transition overrides
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
2024-12-31 12:47:20 +01:00
WarmUpTill
408002e96a Add support for OBS 31 in-tree builds
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
2024-12-12 21:41:21 +01:00
WarmUpTill
50da3d3210 Fix "Media" condition showing the incorrect macro property selection
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
2024-11-30 12:11:14 +01:00
WarmUpTill
9e13067769 Fix typo
Some checks failed
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
2024-11-23 17:11:05 +01:00
WarmUpTill
821e768e76 Reduce screenshot selection area to area to be checked
Some checks failed
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
debian-build / build (push) Has been cancelled
2024-11-22 20:52:15 +01:00
WarmUpTill
4d2179448b Rename files 2024-11-22 20:52:15 +01:00
WarmUpTill
40d9002470 Update scripting examples
* Add more comments
* Add example showcasing how to register and set temp vars
2024-11-22 20:36:36 +01:00
WarmUpTill
7f1e310ca6 Add support to register, deregister, and set temp vars from scripts 2024-11-22 20:36:36 +01:00
WarmUpTill
66d02f4683 Fix typos 2024-11-22 20:36:36 +01:00
Arash Partow
0aae1b9978 Update ExprTk to 0.0.3
Some checks are pending
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
2024-11-22 19:56:02 +01:00
WarmUpTill
db50822b05 Prepare to fix deprecation warning when using OBS 31
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
The following log message will be present in OBS 31 when
CheckIfTrayIsDisabled() is called:

DEPRECATION: obs_frontend_get_global_config is deprecated. Read from
global or user configuration explicitly instead.

It will be resolved when the minimum OBS version is bumped to OBS 31.
2024-11-02 08:28:52 +01:00
WarmUpTill
63a545b293 Fix "Date" condition returning true unexpectedly
This could happen if the plugin was stopped on the General tab and
restarted or the first time the plugin was started.
2024-11-02 08:28:52 +01:00
WarmUpTill
bc0497d2c9 Add MacroWasCheckedSinceLastStart() 2024-11-02 08:28:52 +01:00
WarmUpTill
37226a3a4c Fix macro group icon not being displayed correctly in OBS 31
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
2024-10-31 19:39:38 +01:00
WarmUpTill
9f15fbe47c Recursively search for source button in property groups
Some checks are pending
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
2024-10-30 18:29:23 +01:00
105 changed files with 12022 additions and 5181 deletions

View File

@@ -6,15 +6,6 @@
"autosort": true
},
"additional_commands": {
"find_qt": {
"flags": [],
"kwargs": {
"COMPONENTS": "+",
"COMPONENTS_WIN": "+",
"COMPONENTS_MACOS": "+",
"COMPONENTS_LINUX": "+"
}
},
"set_target_properties_obs": {
"pargs": 1,
"flags": [],

View File

@@ -20,6 +20,7 @@ body:
- macOS 13
- macOS 12
- macOS 11
- Ubuntu 24.04
- Ubuntu 23.10
- Ubuntu 22.04
- Other

View File

@@ -9,5 +9,5 @@ package 'libxtst-dev'
package 'libxss-dev'
package 'libopencv-dev'
package 'libtesseract-dev'
package 'libprocps-dev'
package 'libproc2-dev'
package 'libusb-1.0-0-dev'

View File

@@ -480,12 +480,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
popd
;;
linux)
# Hacky workaround to support libproc2 with Ubuntu 22 build environment
local lsb_version=$(lsb_release -r | cut -f 2 || true)
if [[ "${lsb_version}" == '22.04' ]] {
sudo apt install ${project_root}/build-aux/CI/linux/ubuntu22/libproc2-0_4.0.2-3_amd64.deb
sudo apt install ${project_root}/build-aux/CI/linux/ubuntu22/libproc2-dev_4.0.2-3_amd64.deb
}
# Nothing to do for now
;;
}
}

0
.github/test vendored
View File

View File

@@ -43,7 +43,7 @@ jobs:
# devscripts and libobs-dev are needed but they were already installed
# from check_libobs_revision and install_frontend_header sections.
sudo apt update
sudo apt install cmake debhelper libcurl4-openssl-dev libxss-dev libxtst-dev qtbase5-dev libopencv-dev libprocps-dev
sudo apt install build-essential cmake debhelper libcurl4-openssl-dev libxss-dev libxtst-dev qt6-base-dev libopencv-dev libproc2-dev
- name: build
run: |
debuild --no-lintian --no-sign

View File

@@ -10,7 +10,7 @@ env:
jobs:
check-event:
name: Check GitHub Event Data 🔎
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
defaults:
run:
shell: bash
@@ -177,7 +177,7 @@ jobs:
ubuntu-build:
name: Build for Ubuntu 🐧
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
needs: check-event
defaults:
run:
@@ -241,14 +241,14 @@ jobs:
- name: Upload Artifacts 📡
uses: actions/upload-artifact@v4
with:
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-22.04-x86_64-${{ needs.check-event.outputs.commitHash }}
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-24.04-x86_64-${{ needs.check-event.outputs.commitHash }}
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-x86_64*.*
- name: Upload debug symbol artifacts 🪲
uses: actions/upload-artifact@v4
if: ${{ fromJSON(needs.check-event.outputs.package) }}
with:
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-22.04-x86_64-${{ needs.check-event.outputs.commitHash }}-dbgsym
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-24.04-x86_64-${{ needs.check-event.outputs.commitHash }}-dbgsym
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-x86_64*-dbgsym.ddeb
windows-build:

View File

@@ -3,7 +3,7 @@ on:
workflow_call:
jobs:
clang-format:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
@@ -15,7 +15,7 @@ jobs:
failCondition: error
cmake-format:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:

View File

@@ -31,7 +31,7 @@ jobs:
create-release:
name: Create Release 🛫
if: github.ref_type == 'tag'
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
needs: build-project
defaults:
run:
@@ -77,7 +77,7 @@ jobs:
variants=(
'windows-x64;zip|exe'
'macos-universal;tar.xz|pkg'
'ubuntu-22.04-x86_64;tar.xz|deb|ddeb'
'ubuntu-24.04-x86_64;tar.xz|deb|ddeb'
'sources;tar.xz'
)

View File

@@ -20,6 +20,12 @@ if(BUILD_OUT_OF_TREE)
include(helpers)
endif()
# OBS 31 no longer defines find_qt so check if we need to include it for in-tree
# builds
if(NOT COMMAND find_qt)
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/helpers_common.cmake")
endif()
set(LIB_NAME "${PROJECT_NAME}-lib")
add_library(${PROJECT_NAME} MODULE)
add_library(${LIB_NAME} SHARED)
@@ -289,9 +295,9 @@ include(cmake/common/advss_helpers.cmake)
setup_obs_lib_dependency(${LIB_NAME})
setup_obs_lib_dependency(${PROJECT_NAME})
find_qt(COMPONENTS Widgets Core)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt::Core Qt::Widgets)
target_link_libraries(${LIB_NAME} PRIVATE Qt::Core Qt::Widgets)
find_package(Qt6 REQUIRED COMPONENTS Widgets Core)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Widgets)
target_link_libraries(${LIB_NAME} PRIVATE Qt6::Core Qt6::Widgets)
target_compile_options(
${PROJECT_NAME}
PRIVATE
@@ -407,7 +413,12 @@ else()
set(PROCESS_CONDITION_SUPPORTED 1)
endif()
if(PROCPS2_INCLUDE_DIR)
message(STATUS "${PROJECT_NAME} using libproc2")
find_package(PkgConfig REQUIRED)
pkg_check_modules(libproc2 IMPORTED_TARGET libproc2<4.0.5 QUIET)
if(libproc2_FOUND)
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS2_USE_INFO)
endif()
pkg_check_modules(libproc2 REQUIRED IMPORTED_TARGET libproc2)
set(PROC_INCLUDE_DIR "${PROCPS2_INCLUDE_DIR}")
target_compile_definitions(${LIB_NAME} PRIVATE PROCPS2_AVAILABLE)
set(PROCESS_CONDITION_SUPPORTED 1)

View File

@@ -7,7 +7,7 @@ Build-Depends: cmake,
libcurl4-openssl-dev,
libobs-dev,
libxtst-dev,
qtbase5-dev,
qt6-base-dev,
libxss-dev,
libopencv-dev
Standards-Version: 4.6.0

View File

@@ -3,7 +3,7 @@
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
export QT_SELECT = qt5
export QT_SELECT = qt6
%:
dh $@

View File

@@ -283,8 +283,8 @@ endfunction()
function(setup_advss_plugin target)
setup_obs_lib_dependency(${target})
find_qt(COMPONENTS Widgets Core)
target_link_libraries(${target} PRIVATE Qt::Core Qt::Widgets)
find_package(Qt6 REQUIRED COMPONENTS Widgets Core)
target_link_libraries(${target} PRIVATE Qt6::Core Qt6::Widgets)
set_target_properties(
${target}

View File

@@ -17,85 +17,6 @@ if(NOT QT_VERSION)
set_property(CACHE QT_VERSION PROPERTY STRINGS AUTO 5 6)
endif()
# find_qt: Macro to find best possible Qt version for use with the project:
macro(find_qt)
set(multiValueArgs COMPONENTS COMPONENTS_WIN COMPONENTS_MAC COMPONENTS_LINUX)
cmake_parse_arguments(find_qt "" "${oneValueArgs}" "${multiValueArgs}"
${ARGN})
# Do not use versionless targets in the first step to avoid Qt::Core being
# clobbered by later opportunistic find_package runs
set(QT_NO_CREATE_VERSIONLESS_TARGETS TRUE)
message(DEBUG "Start Qt version discovery...")
# Loop until _QT_VERSION is set or FATAL_ERROR aborts script execution early
while(NOT _QT_VERSION)
message(DEBUG "QT_VERSION set to ${QT_VERSION}")
if(QT_VERSION STREQUAL AUTO AND NOT qt_test_version)
set(qt_test_version 6)
elseif(NOT QT_VERSION STREQUAL AUTO)
set(qt_test_version ${QT_VERSION})
endif()
message(DEBUG "Attempting to find Qt${qt_test_version}")
find_package(
Qt${qt_test_version}
COMPONENTS Core
QUIET)
if(TARGET Qt${qt_test_version}::Core)
set(_QT_VERSION
${qt_test_version}
CACHE INTERNAL "")
message(STATUS "Qt version found: ${_QT_VERSION}")
unset(qt_test_version)
break()
elseif(QT_VERSION STREQUAL AUTO)
if(qt_test_version EQUAL 6)
message(WARNING "Qt6 was not found, falling back to Qt5")
set(qt_test_version 5)
continue()
endif()
endif()
message(FATAL_ERROR "Neither Qt6 nor Qt5 found.")
endwhile()
# Enable versionless targets for the remaining Qt components
set(QT_NO_CREATE_VERSIONLESS_TARGETS FALSE)
set(qt_components ${find_qt_COMPONENTS})
if(OS_WINDOWS)
list(APPEND qt_components ${find_qt_COMPONENTS_WIN})
elseif(OS_MACOS)
list(APPEND qt_components ${find_qt_COMPONENTS_MAC})
else()
list(APPEND qt_components ${find_qt_COMPONENTS_LINUX})
endif()
message(DEBUG "Trying to find Qt components ${qt_components}...")
find_package(Qt${_QT_VERSION} REQUIRED ${qt_components})
list(APPEND qt_components Core)
if("Gui" IN_LIST find_qt_COMPONENTS_LINUX)
list(APPEND qt_components "GuiPrivate")
endif()
# Check for versionless targets of each requested component and create if
# necessary
foreach(component IN LISTS qt_components)
message(DEBUG "Checking for target Qt::${component}")
if(NOT TARGET Qt::${component} AND TARGET Qt${_QT_VERSION}::${component})
add_library(Qt::${component} INTERFACE IMPORTED)
set_target_properties(
Qt::${component} PROPERTIES INTERFACE_LINK_LIBRARIES
Qt${_QT_VERSION}::${component})
endif()
set_property(TARGET Qt::${component} PROPERTY INTERFACE_COMPILE_FEATURES "")
endforeach()
endmacro()
# check_uuid: Helper function to check for valid UUID
function(check_uuid uuid_string return_value)
set(valid_uuid TRUE)

View File

@@ -769,6 +769,11 @@ AdvSceneSwitcher.condition.streamDeck.checkData="Check data field"
AdvSceneSwitcher.condition.streamDeck.startListen="Start listening"
AdvSceneSwitcher.condition.streamDeck.stopListen="Stop listening"
AdvSceneSwitcher.condition.streamDeck.pluginDownload="<html><head/><body><p>The Stream Deck plugin can be found <a href=\"https://github.com/WarmUpTill/advanced-scene-switcher-streamdeck-plugin/releases\"><span style=\" text-decoration: underline; color:#268bd2;\">here on GitHub</span></a>.</p></body></html>"
AdvSceneSwitcher.condition.gameCapture="Game capture"
AdvSceneSwitcher.condition.gameCapture.entry="{{sources}}hooked a game."
AdvSceneSwitcher.condition.screenshot="Screenshot"
AdvSceneSwitcher.condition.screenshot.entry="A screenshot was taken"
# Macro Actions
AdvSceneSwitcher.action.unknown="Unknown action"
@@ -1054,13 +1059,13 @@ AdvSceneSwitcher.action.http.type.post="POST"
AdvSceneSwitcher.action.http.entry.line1="Send{{method}}to{{url}}"
AdvSceneSwitcher.action.http.entry.line2="Timeout:{{timeout}}seconds"
AdvSceneSwitcher.action.variable="Variable"
AdvSceneSwitcher.action.variable.type.set="Set to fixed value"
AdvSceneSwitcher.action.variable.type.set="Set to value"
AdvSceneSwitcher.action.variable.type.append="Append"
AdvSceneSwitcher.action.variable.type.appendVar="Append variable"
AdvSceneSwitcher.action.variable.type.increment="Increment"
AdvSceneSwitcher.action.variable.type.decrement="Decrement"
AdvSceneSwitcher.action.variable.type.setActionValue="Set to action value"
AdvSceneSwitcher.action.variable.type.setConditionValue="Set to condition value"
AdvSceneSwitcher.action.variable.type.setActionValue="Set to action value (legacy)"
AdvSceneSwitcher.action.variable.type.setConditionValue="Set to condition value (legacy)"
AdvSceneSwitcher.action.variable.type.roundToInt="Round to nearest integer"
AdvSceneSwitcher.action.variable.type.subString="Set to substring of current value"
AdvSceneSwitcher.action.variable.type.findAndReplace="Find and replace in current value"
@@ -1078,6 +1083,7 @@ AdvSceneSwitcher.action.variable.type.truncateValue="Truncate current value"
AdvSceneSwitcher.action.variable.type.swapValues="Swap variable values"
AdvSceneSwitcher.action.variable.type.trim="Trim whitespace"
AdvSceneSwitcher.action.variable.type.changeCase="Change case"
AdvSceneSwitcher.action.variable.type.randomNumber="Generate random number"
AdvSceneSwitcher.action.variable.case.type.lowerCase="lowercase"
AdvSceneSwitcher.action.variable.case.type.upperCase="UPPERCASE"
AdvSceneSwitcher.action.variable.case.type.capitalized="Capitalized"
@@ -1092,6 +1098,7 @@ AdvSceneSwitcher.action.variable.findAndReplace.replace="Text to replace with"
AdvSceneSwitcher.action.variable.findAndReplace.replace.tooltip="Using \1, \2, etc. to represent first, second, etc. capture groups is supported."
AdvSceneSwitcher.action.variable.subString.begin="Beginning"
AdvSceneSwitcher.action.variable.subString.all="All"
AdvSceneSwitcher.action.variable.generateInteger="Only generate integers"
AdvSceneSwitcher.action.variable.invalidSelection="Invalid selection!"
AdvSceneSwitcher.action.variable.actionNoVariableSupport="Getting variable values from %1 actions is not supported!"
AdvSceneSwitcher.action.variable.conditionNoVariableSupport="Getting variable values from %1 conditions is not supported!"
@@ -1104,6 +1111,7 @@ AdvSceneSwitcher.action.variable.entry.substringRegex="Assign value of{{regexMat
AdvSceneSwitcher.action.variable.entry.findAndReplace="{{findStr}}{{findRegex}}{{replaceStr}}"
AdvSceneSwitcher.action.variable.entry.userInput.customPrompt="{{useCustomPrompt}}Use custom prompt{{inputPrompt}}"
AdvSceneSwitcher.action.variable.entry.userInput.placeholder="{{useInputPlaceholder}}Fill with placeholder{{inputPlaceholder}}"
AdvSceneSwitcher.action.variable.entry.randomNumber="Generate random number in range from{{randomNumberStart}}to{{randomNumberEnd}}"
AdvSceneSwitcher.action.projector="Projector"
AdvSceneSwitcher.action.projector.type.source="Source"
AdvSceneSwitcher.action.projector.type.scene="Scene"
@@ -1134,10 +1142,15 @@ AdvSceneSwitcher.action.twitch.type.clip.create="Create stream clip"
AdvSceneSwitcher.action.twitch.type.chat.announcement.send="Send chat announcement"
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable="Enable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="Disable chat's emote-only mode"
AdvSceneSwitcher.action.twitch.type.sendChatMessage="Send chat message"
AdvSceneSwitcher.action.twitch.type.chat.sendMessage="Send chat message"
AdvSceneSwitcher.action.twitch.type.user.getInfo="Get user information"
AdvSceneSwitcher.action.twitch.type.reward.getInfo="Get channel points reward information"
AdvSceneSwitcher.action.twitch.reward.toggleControl="Toggle reward name / variable selection control"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!"
AdvSceneSwitcher.action.twitch.entry.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}"
AdvSceneSwitcher.action.twitch.entry.chat="Using account{{account}}{{actions}}on{{channel}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}"
AdvSceneSwitcher.action.twitch.entry.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}"
AdvSceneSwitcher.action.twitch.entry.chat="Using account{{account}}{{actions}}on{{channel}}"
AdvSceneSwitcher.action.twitch.entry.user.getInfo="Using account{{account}}{{actions}}for{{userInfoQueryType}}{{userLogin}}{{userId}}"
AdvSceneSwitcher.action.twitch.entry.reward.getInfo="Using account{{account}}{{actions}}for channel{{channel}}{{pointsReward}}{{rewardVariable}}{{toggleRewardSelection}}"
AdvSceneSwitcher.action.twitch.title.title="Enter title"
AdvSceneSwitcher.action.twitch.marker.description="Describe marker"
AdvSceneSwitcher.action.twitch.clip.hasDelay="Add a slight delay before capturing the clip"
@@ -1147,6 +1160,8 @@ AdvSceneSwitcher.action.twitch.announcement.blue="Blue"
AdvSceneSwitcher.action.twitch.announcement.green="Green"
AdvSceneSwitcher.action.twitch.announcement.orange="Orange"
AdvSceneSwitcher.action.twitch.announcement.purple="Purple"
AdvSceneSwitcher.action.twitch.user.getInfo.queryType.id="User Id"
AdvSceneSwitcher.action.twitch.user.getInfo.queryType.login="User login"
AdvSceneSwitcher.action.clipboard="Clipboard"
AdvSceneSwitcher.action.clipboard.type.copy.text="Copy text"
AdvSceneSwitcher.action.clipboard.type.copy.image="Copy image"
@@ -1211,7 +1226,7 @@ AdvSceneSwitcher.validation.text.maxLength="Only %1 characters are allowed in th
AdvSceneSwitcher.variable.select="--select variable--"
AdvSceneSwitcher.variable.add="Add new variable"
AdvSceneSwitcher.variable.configure="Configure variable settings"
AdvSceneSwitcher.variable.invalid="Invalid varialbe selection"
AdvSceneSwitcher.variable.invalid="Invalid variable selection"
AdvSceneSwitcher.variable.name="Name:"
AdvSceneSwitcher.variable.value="Current value:"
AdvSceneSwitcher.variable.save="Save / load behavior"
@@ -1590,12 +1605,21 @@ AdvSceneSwitcher.tempVar.twitch.started_at.shoutoutReceived="Shoutout time"
AdvSceneSwitcher.tempVar.twitch.started_at.shoutoutReceived.description="The UTC timestamp (in RFC3339 format) of when the moderator sent the Shoutout."
AdvSceneSwitcher.tempVar.twitch.id.reward="Reward ID"
AdvSceneSwitcher.tempVar.twitch.id.redemption="Redemption ID"
AdvSceneSwitcher.tempVar.twitch.user_id.reward="User ID redeeming the reward"
AdvSceneSwitcher.tempVar.twitch.user_login.reward="User login redeeming the reward"
AdvSceneSwitcher.tempVar.twitch.user_name.reward="User name redeeming the reward"
AdvSceneSwitcher.tempVar.twitch.user_input.reward="User input for reward"
AdvSceneSwitcher.tempVar.twitch.reward.reward="Reward Information"
AdvSceneSwitcher.tempVar.twitch.reward.reward.description="Basic information about the reward that was redeemed, at the time it was redeemed."
AdvSceneSwitcher.tempVar.twitch.reward.reward="Reward Information (JSON)"
AdvSceneSwitcher.tempVar.twitch.reward.reward.description="Basic information about the reward that was redeemed in JSON form, at the time it was redeemed."
AdvSceneSwitcher.tempVar.twitch.reward.id.reward="Reward ID"
AdvSceneSwitcher.tempVar.twitch.reward.id.reward.description="The ID that uniquely identifies the reward."
AdvSceneSwitcher.tempVar.twitch.reward.title.reward="Reward title"
AdvSceneSwitcher.tempVar.twitch.reward.title.reward.description="The rewards title."
AdvSceneSwitcher.tempVar.twitch.reward.prompt.reward="Reward prompt"
AdvSceneSwitcher.tempVar.twitch.reward.prompt.reward.description="The prompt displayed to the viewer if user input is required."
AdvSceneSwitcher.tempVar.twitch.reward.cost.reward="Reward cost"
AdvSceneSwitcher.tempVar.twitch.reward.cost.reward.description="The rewards cost, in Channel Points."
AdvSceneSwitcher.tempVar.twitch.status.reward="Reward status"
AdvSceneSwitcher.tempVar.twitch.status.reward.description="Defaults to unfulfilled. Possible values are unknown, unfulfilled, fulfilled, and canceled."
AdvSceneSwitcher.tempVar.twitch.redeemed_at.reward="Redemption time"
@@ -1610,22 +1634,37 @@ AdvSceneSwitcher.tempVar.twitch.title.reward="Reward title"
AdvSceneSwitcher.tempVar.twitch.cost.reward="Reward cost"
AdvSceneSwitcher.tempVar.twitch.prompt.reward="Reward description"
AdvSceneSwitcher.tempVar.twitch.is_user_input_required.reward="Reward requires user input"
AdvSceneSwitcher.tempVar.twitch.should_redemptions_skip_request_queue.reward="Redemptions skip request queue"
AdvSceneSwitcher.tempVar.twitch.should_redemptions_skip_request_queue.reward.description="Should redemptions be set to fulfilled status immediately when redeemed and skip the request queue instead of the normal unfulfilled status."
AdvSceneSwitcher.tempVar.twitch.max_per_stream.reward="Reward maximum per stream"
AdvSceneSwitcher.tempVar.twitch.max_per_user_per_stream.reward="Reward maximum per user per stream"
AdvSceneSwitcher.tempVar.twitch.background_color.reward="Reward background color"
AdvSceneSwitcher.tempVar.twitch.image.reward="Reward image"
AdvSceneSwitcher.tempVar.twitch.image.reward="Reward image (JSON)"
AdvSceneSwitcher.tempVar.twitch.image.reward.description="Set of custom images of 1x, 2x and 4x sizes for the reward. Can be null if no images have been uploaded."
AdvSceneSwitcher.tempVar.twitch.default_image.reward="Reward default image"
AdvSceneSwitcher.tempVar.twitch.image.url_4x.reward="Reward image URL"
AdvSceneSwitcher.tempVar.twitch.image.url_4x.reward.description="The URL to the custom image for the reward. This field is null if the broadcaster didnt upload images."
AdvSceneSwitcher.tempVar.twitch.default_image.reward="Reward default image (JSON)"
AdvSceneSwitcher.tempVar.twitch.default_image.reward.description="Set of default images of 1x, 2x and 4x sizes for the reward."
AdvSceneSwitcher.tempVar.twitch.global_cooldown.reward="Reward cooldown"
AdvSceneSwitcher.tempVar.twitch.global_cooldown.reward.description="Whether a cooldown is enabled and what the cooldown is in seconds."
AdvSceneSwitcher.tempVar.twitch.cooldown_expires_at.reward="Reward cooldown expiration"
AdvSceneSwitcher.tempVar.twitch.cooldown_expires_at.reward.description="Timestamp of the cooldown expiration. null if the reward isnt on cooldown."
AdvSceneSwitcher.tempVar.twitch.default_image.url_4x.reward="Reward default image URL"
AdvSceneSwitcher.tempVar.twitch.default_image.url_4x.reward.description="The URL to the default image for the reward."
AdvSceneSwitcher.tempVar.twitch.redemptions_redeemed_current_stream.reward="Redemption count"
AdvSceneSwitcher.tempVar.twitch.redemptions_redeemed_current_stream.reward.description="The number of redemptions redeemed during the current live stream. Counts against the max_per_stream limit. null if the broadcasters stream isnt live or max_per_stream isnt enabled."
AdvSceneSwitcher.tempVar.twitch.redemptions_redeemed_current_stream.reward.description="The number of redemptions redeemed during the current live stream. Counts against the max_per_stream limit. null if the broadcasters stream isnt live or max_per_stream isnt enabled."
AdvSceneSwitcher.tempVar.twitch.should_redemptions_skip_request_queue.reward="Redemptions skip request queue"
AdvSceneSwitcher.tempVar.twitch.should_redemptions_skip_request_queue.reward.description="Should redemptions be set to fulfilled status immediately when redeemed and skip the request queue instead of the normal unfulfilled status."
AdvSceneSwitcher.tempVar.twitch.max_per_stream.reward="Maximum redemptions per stream (JSON)"
AdvSceneSwitcher.tempVar.twitch.max_per_stream.is_enabled.reward="Maximum redemptions per stream enabled"
AdvSceneSwitcher.tempVar.twitch.max_per_stream.is_enabled.reward.description="A Boolean value that determines whether the reward applies a limit on the number of redemptions allowed per live stream. Is true if the reward applies a limit."
AdvSceneSwitcher.tempVar.twitch.max_per_stream.max_per_stream.reward="Maximum redemptions per stream"
AdvSceneSwitcher.tempVar.twitch.max_per_stream.max_per_stream.reward.description="The maximum number of redemptions allowed per live stream."
AdvSceneSwitcher.tempVar.twitch.max_per_user_per_stream.reward="Reward maximum per user per stream (JSON)"
AdvSceneSwitcher.tempVar.twitch.max_per_user_per_stream.is_enabled.reward="Maximum redemptions per user per stream enabled"
AdvSceneSwitcher.tempVar.twitch.max_per_user_per_stream.is_enabled.reward.description="A Boolean value that determines whether the reward applies a limit on the number of redemptions allowed per user per live stream. Is true if the reward applies a limit."
AdvSceneSwitcher.tempVar.twitch.max_per_user_per_stream.max_per_user_per_stream.reward="Maximum redemptions per user per stream"
AdvSceneSwitcher.tempVar.twitch.max_per_user_per_stream.max_per_user_per_stream.reward.description="The settings used to determine whether to apply a maximum to the number of redemptions allowed per user per live stream."
AdvSceneSwitcher.tempVar.twitch.global_cooldown.reward="Global cooldown (JSON)"
AdvSceneSwitcher.tempVar.twitch.global_cooldown.reward.description="Whether a cooldown is enabled and what the cooldown is in seconds."
AdvSceneSwitcher.tempVar.twitch.global_cooldown.is_enabled.reward="Global cooldown enabled"
AdvSceneSwitcher.tempVar.twitch.global_cooldown.is_enabled.reward.description="A Boolean value that determines whether to apply a cooldown period. Is true if a cooldown period is enabled."
AdvSceneSwitcher.tempVar.twitch.global_cooldown.global_cooldown_seconds.reward="Global cooldown"
AdvSceneSwitcher.tempVar.twitch.global_cooldown.global_cooldown_seconds.reward.description="The cooldown period, in seconds."
AdvSceneSwitcher.tempVar.twitch.cooldown_expires_at.reward="Reward cooldown expiration"
AdvSceneSwitcher.tempVar.twitch.cooldown_expires_at.reward.description="Timestamp of the cooldown expiration. null if the reward isnt on cooldown."
AdvSceneSwitcher.tempVar.twitch.charity_name="Charity name"
AdvSceneSwitcher.tempVar.twitch.charity_description="Charity description"
@@ -1794,6 +1833,24 @@ AdvSceneSwitcher.tempVar.twitch.to_broadcaster_user_name.raid.description="The b
AdvSceneSwitcher.tempVar.twitch.viewers.raid="Raid participants"
AdvSceneSwitcher.tempVar.twitch.viewers.raid.description="The number of viewers in the raid."
AdvSceneSwitcher.tempVar.twitch.id.user.getInfo="User ID"
AdvSceneSwitcher.tempVar.twitch.id.user.getInfo.description="The user ID of the queried account."
AdvSceneSwitcher.tempVar.twitch.login.user.getInfo="User login"
AdvSceneSwitcher.tempVar.twitch.login.user.getInfo.description="The user login of the queried account."
AdvSceneSwitcher.tempVar.twitch.display_name.user.getInfo="User name"
AdvSceneSwitcher.tempVar.twitch.display_name.user.getInfo.description="The display name of the queried account."
AdvSceneSwitcher.tempVar.twitch.type.user.getInfo="User type"
AdvSceneSwitcher.tempVar.twitch.type.user.getInfo.description="The type of user. Possible values are:\n admin — Twitch administrator\n global_mod\n staff — Twitch staff\n \"\" — Normal user"
AdvSceneSwitcher.tempVar.twitch.broadcaster_type.user.getInfo="Broadcaster type"
AdvSceneSwitcher.tempVar.twitch.broadcaster_type.user.getInfo.description="The type of broadcaster. Possible values are:\n affiliate — An affiliate broadcaster\n partner — A partner broadcaster\n \"\" — A normal broadcaster"
AdvSceneSwitcher.tempVar.twitch.description.user.getInfo="Description"
AdvSceneSwitcher.tempVar.twitch.description.user.getInfo.description="The user's description of their channel."
AdvSceneSwitcher.tempVar.twitch.profile_image_url.user.getInfo="Profile image URL"
AdvSceneSwitcher.tempVar.twitch.offline_image_url.user.getInfo="Offline image URL"
AdvSceneSwitcher.tempVar.twitch.created_at.user.getInfo="Account creation date"
AdvSceneSwitcher.tempVar.twitch.created_at.user.getInfo.description="The UTC date and time that the user's account was created. The timestamp is in RFC3339 format."
AdvSceneSwitcher.tempVar.audio.output_volume="Output volume"
AdvSceneSwitcher.tempVar.audio.output_volume.description="The volume the audio source is outputting."
AdvSceneSwitcher.tempVar.audio.configured_volume="Configured volume"
@@ -1971,6 +2028,16 @@ AdvSceneSwitcher.tempVar.queue.size="Size"
AdvSceneSwitcher.tempVar.queue.running="Is running"
AdvSceneSwitcher.tempVar.queue.running.description="Returns \"true\" if the queue is started and \"false\" if it is stopped."
AdvSceneSwitcher.tempVar.screenshot.lastScreenshotPath="Last screenshot path"
AdvSceneSwitcher.tempVar.screenshot.lastScreenshotPath.description="The filesystem path to the last screenshot taken."
AdvSceneSwitcher.tempVar.gameCapture.title="Title"
AdvSceneSwitcher.tempVar.gameCapture.title.description="Window title of the application captured by the source."
AdvSceneSwitcher.tempVar.gameCapture.class="Class"
AdvSceneSwitcher.tempVar.gameCapture.class.description="Window class of the application captured by the source."
AdvSceneSwitcher.tempVar.gameCapture.executable="Executable"
AdvSceneSwitcher.tempVar.gameCapture.executable.description="Executable name of the application captured by the source."
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene"

View File

@@ -1089,10 +1089,10 @@ AdvSceneSwitcher.action.twitch.type.clip.create="ストリームクリップの
AdvSceneSwitcher.action.twitch.type.chat.announcement.send="チャット通知の送信"
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable="チャットのエモート専用モードを有効にする"
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="チャットのエモート専用モードを無効にする"
AdvSceneSwitcher.action.twitch.type.sendChatMessage="チャットメッセージを送る"
AdvSceneSwitcher.action.twitch.type.chat.sendMessage="チャットメッセージを送る"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Twitchアカウントを選択しないとカテゴリを選択できません"
; AdvSceneSwitcher.action.twitch.entry.default="On{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}"
AdvSceneSwitcher.action.twitch.entry.chat="{{channel}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}} でアカウント{{account}}{{actions}}を使用しています"
AdvSceneSwitcher.action.twitch.entry.chat="{{channel}}でアカウント{{account}}{{actions}}を使用しています"
AdvSceneSwitcher.action.twitch.title.title="タイトルを入力してください"
AdvSceneSwitcher.action.twitch.marker.description="マーカーの説明"
AdvSceneSwitcher.action.twitch.clip.hasDelay="クリップをキャプチャする前にわずかな遅延を追加します"

View File

@@ -1070,10 +1070,10 @@ AdvSceneSwitcher.action.twitch.type.clip.create="Criar clipe da transmissão"
AdvSceneSwitcher.action.twitch.type.chat.announcement.send="Enviar anúncio de chat"
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.enable="Habilitar modo apenas emotes no chat"
AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable="Desabilitar modo apenas emotes no chat"
AdvSceneSwitcher.action.twitch.type.sendChatMessage="Enviar mensagem de chat"
AdvSceneSwitcher.action.twitch.type.chat.sendMessage="Enviar mensagem de chat"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Não é possível selecionar categoria sem selecionar uma conta Twitch primeiro!"
AdvSceneSwitcher.action.twitch.entry.default="Em{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}"
AdvSceneSwitcher.action.twitch.entry.chat="Usando conta{{account}}{{actions}}em{{channel}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}"
AdvSceneSwitcher.action.twitch.entry.default="Em{{account}}{{actions}}{{streamTitle}}{{category}}{{markerDescription}}{{clipHasDelay}}{{duration}}{{announcementColor}}{{channel}}{{pointsReward}}"
AdvSceneSwitcher.action.twitch.entry.chat="Usando conta{{account}}{{actions}}em{{channel}}"
AdvSceneSwitcher.action.twitch.title.title="Digite o título"
AdvSceneSwitcher.action.twitch.marker.description="Descreva o marcador"
AdvSceneSwitcher.action.twitch.clip.hasDelay="Adicionar um leve atraso antes de capturar o clipe"

13156
deps/exprtk/exprtk.hpp vendored

File diff suppressed because it is too large Load Diff

View File

@@ -735,7 +735,7 @@ void OpenSettingsWindow()
}
}
void AdvSceneSwitcher::HighligthMacroSettingsButton(bool enable)
void AdvSceneSwitcher::HighlightMacroSettingsButton(bool enable)
{
static QObject *highlight = nullptr;
if ((highlight && enable) || (!highlight && !enable)) {
@@ -755,13 +755,13 @@ void AdvSceneSwitcher::HighligthMacroSettingsButton(bool enable)
highlight = HighlightWidget(ui->macroSettings, Qt::green);
}
void HighligthMacroSettingsButton(bool enable)
void HighlightMacroSettingsButton(bool enable)
{
auto window = GetSettingsWindow();
if (!window) {
return;
}
static_cast<AdvSceneSwitcher *>(window)->HighligthMacroSettingsButton(
static_cast<AdvSceneSwitcher *>(window)->HighlightMacroSettingsButton(
enable);
}

View File

@@ -97,7 +97,7 @@ public:
void SetConditionData(Macro &m);
void SwapActions(Macro *m, int pos1, int pos2);
void SwapConditions(Macro *m, int pos1, int pos2);
void HighligthMacroSettingsButton(bool enable = true);
void HighlightMacroSettingsButton(bool enable = true);
public slots:
void on_macroAdd_clicked();
@@ -386,6 +386,6 @@ private:
};
void OpenSettingsWindow();
void HighligthMacroSettingsButton(bool enable);
void HighlightMacroSettingsButton(bool enable);
} // namespace advss

View File

@@ -344,6 +344,10 @@ void AdvSceneSwitcher::on_importSettings_clicked()
return;
}
// We have to make sure to that no macro is currently being edited while
// the new settings are loaded
ui->macros->clearSelection();
std::lock_guard<std::mutex> lock(switcher->m);
switcher->LoadSettings(obj);
switcher->lastImportPath = path.toStdString();

View File

@@ -122,11 +122,7 @@ void SwitcherData::writeToStatusFile(const QString &msg)
QFile file(QString::fromStdString(fileIO.writePath));
if (file.open(QIODevice::ReadWrite)) {
QTextStream stream(&file);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
stream << msg << "\n";
#else
stream << msg << Qt::endl;
#endif
}
file.close();
}

View File

@@ -384,7 +384,11 @@ static void getProcessListProcps2(QStringList &processes)
return;
}
while ((stack = procps_pids_get_(info, PIDS_FETCH_TASKS_ONLY))) {
#ifdef PROCPS2_USE_INFO
auto cmd = PIDS_VAL(0, str, stack, info);
#else
auto cmd = PIDS_VAL(0, str, stack);
#endif
QString procName(cmd);
if (!procName.isEmpty() && !processes.contains(procName)) {
processes << procName;

View File

@@ -309,11 +309,11 @@ MacroActionMacroEdit::MacroActionMacroEdit(
_loading = false;
}
void HighligthMacroSettingsButton(bool enable);
void HighlightMacroSettingsButton(bool enable);
MacroActionMacroEdit::~MacroActionMacroEdit()
{
HighligthMacroSettingsButton(false);
HighlightMacroSettingsButton(false);
}
void MacroActionMacroEdit::UpdateEntryData()
@@ -480,7 +480,7 @@ void MacroActionMacroEdit::SetWidgetVisibility()
_inputs->setVisible(_entryData->_action ==
MacroActionMacro::Action::RUN &&
_entryData->_runOptions.setInputs);
HighligthMacroSettingsButton(_entryData->_action ==
HighlightMacroSettingsButton(_entryData->_action ==
MacroActionMacro::Action::RUN &&
_entryData->_runOptions.setInputs &&
!_inputs->HasInputsToSet());

View File

@@ -6,14 +6,17 @@
namespace advss {
MacroActionScript::MacroActionScript(Macro *m, const std::string &id,
const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &completionSignal)
MacroActionScript::MacroActionScript(
Macro *m, const std::string &id, const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignalName,
const std::string &completionSignalName,
const std::string &newInstanceSignalName,
const std::string &deletedInstanceSignalName)
: MacroAction(m),
MacroSegmentScript(defaultSettings, propertiesSignalName,
triggerSignal, completionSignal),
triggerSignalName, completionSignalName,
newInstanceSignalName, deletedInstanceSignalName),
_id(id)
{
}
@@ -89,4 +92,29 @@ void MacroActionScript::WaitForCompletion() const
}
}
void MacroActionScript::RegisterTempVarHelper(const std::string &variableId,
const std::string &name,
const std::string &helpText)
{
AddTempvar(variableId, name, helpText);
}
void MacroActionScript::DeregisterAllTempVarsHelper()
{
MacroSegment::SetupTempVars();
}
void MacroActionScript::SetTempVarValueHelper(const std::string &variableId,
const std::string &value)
{
MacroAction::SetTempVarValue(variableId, value);
}
void MacroActionScript::SetupTempVars()
{
// This just exists so MacroSegment::SetupTempVars() is not called.
// We want the ScriptHandler to handle the registration and clearing of
// the temp vars.
}
} // namespace advss

View File

@@ -10,8 +10,10 @@ public:
MacroActionScript(Macro *m, const std::string &id,
const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &signalComplete);
const std::string &triggerSignalName,
const std::string &completionSignalName,
const std::string &newInstanceSignalName,
const std::string &deletedInstanceSignalName);
MacroActionScript(const advss::MacroActionScript &);
bool PerformAction();
void LogAction() const;
@@ -22,6 +24,13 @@ public:
private:
void WaitForCompletion() const;
void RegisterTempVarHelper(const std::string &variableId,
const std::string &name,
const std::string &helpText);
void DeregisterAllTempVarsHelper();
void SetTempVarValueHelper(const std::string &variableId,
const std::string &value);
void SetupTempVars();
std::string _id = "";
};

View File

@@ -8,6 +8,8 @@
#include "source-helpers.hpp"
#include "utility.hpp"
#include <random>
namespace advss {
const std::string MacroActionVariable::id = "variable";
@@ -17,66 +19,6 @@ bool MacroActionVariable::_registered = MacroActionFactory::Register(
{MacroActionVariable::Create, MacroActionVariableEdit::Create,
"AdvSceneSwitcher.action.variable"});
const static std::map<MacroActionVariable::Type, std::string> actionTypes = {
{MacroActionVariable::Type::SET_FIXED_VALUE,
"AdvSceneSwitcher.action.variable.type.set"},
{MacroActionVariable::Type::APPEND,
"AdvSceneSwitcher.action.variable.type.append"},
{MacroActionVariable::Type::APPEND_VAR,
"AdvSceneSwitcher.action.variable.type.appendVar"},
{MacroActionVariable::Type::INCREMENT,
"AdvSceneSwitcher.action.variable.type.increment"},
{MacroActionVariable::Type::DECREMENT,
"AdvSceneSwitcher.action.variable.type.decrement"},
{MacroActionVariable::Type::SET_CONDITION_VALUE,
"AdvSceneSwitcher.action.variable.type.setConditionValue"},
{MacroActionVariable::Type::SET_ACTION_VALUE,
"AdvSceneSwitcher.action.variable.type.setActionValue"},
{MacroActionVariable::Type::ROUND_TO_INT,
"AdvSceneSwitcher.action.variable.type.roundToInt"},
{MacroActionVariable::Type::SUBSTRING,
"AdvSceneSwitcher.action.variable.type.subString"},
{MacroActionVariable::Type::FIND_AND_REPLACE,
"AdvSceneSwitcher.action.variable.type.findAndReplace"},
{MacroActionVariable::Type::MATH_EXPRESSION,
"AdvSceneSwitcher.action.variable.type.mathExpression"},
{MacroActionVariable::Type::USER_INPUT,
"AdvSceneSwitcher.action.variable.type.askForValue"},
{MacroActionVariable::Type::ENV_VARIABLE,
"AdvSceneSwitcher.action.variable.type.environmentVariable"},
{MacroActionVariable::Type::SCENE_ITEM_COUNT,
"AdvSceneSwitcher.action.variable.type.sceneItemCount"},
{MacroActionVariable::Type::STRING_LENGTH,
"AdvSceneSwitcher.action.variable.type.stringLength"},
{MacroActionVariable::Type::EXTRACT_JSON,
"AdvSceneSwitcher.action.variable.type.extractJson"},
{MacroActionVariable::Type::SET_TO_TEMPVAR,
"AdvSceneSwitcher.action.variable.type.setToTempvar"},
{MacroActionVariable::Type::SCENE_ITEM_NAME,
"AdvSceneSwitcher.action.variable.type.sceneItemName"},
{MacroActionVariable::Type::PAD,
"AdvSceneSwitcher.action.variable.type.padValue"},
{MacroActionVariable::Type::TRUNCATE,
"AdvSceneSwitcher.action.variable.type.truncateValue"},
{MacroActionVariable::Type::SWAP_VALUES,
"AdvSceneSwitcher.action.variable.type.swapValues"},
{MacroActionVariable::Type::TRIM,
"AdvSceneSwitcher.action.variable.type.trim"},
{MacroActionVariable::Type::CHANGE_CASE,
"AdvSceneSwitcher.action.variable.type.changeCase"},
};
const static std::map<MacroActionVariable::CaseType, std::string> caseTypes = {
{MacroActionVariable::CaseType::LOWER_CASE,
"AdvSceneSwitcher.action.variable.case.type.lowerCase"},
{MacroActionVariable::CaseType::UPPER_CASE,
"AdvSceneSwitcher.action.variable.case.type.upperCase"},
{MacroActionVariable::CaseType::CAPITALIZED,
"AdvSceneSwitcher.action.variable.case.type.capitalized"},
{MacroActionVariable::CaseType::START_CASE,
"AdvSceneSwitcher.action.variable.case.type.startCase"},
};
static void apppend(Variable &var, const std::string &value)
{
auto currentValue = var.Value();
@@ -253,6 +195,27 @@ void MacroActionVariable::SetToSceneItemName(Variable *var)
var->SetValue(data.name);
}
void MacroActionVariable::GenerateRandomNumber(Variable *var)
{
static std::random_device rd;
static std::mt19937 gen(rd());
double start = _randomNumberStart;
double end = _randomNumberEnd;
if (start > end) {
std::swap(start, end);
}
if (_generateInteger) {
std::uniform_int_distribution<int> dis(start, end);
var->SetValue(dis(gen));
} else {
std::uniform_real_distribution<double> dis(start, end);
var->SetValue(dis(gen));
}
}
struct AskForInputParams {
AskForInputParams(const QString &prompt_, const QString &placeholder_)
: prompt(prompt_),
@@ -313,14 +276,14 @@ bool MacroActionVariable::PerformAction()
return true;
}
switch (_type) {
case Type::SET_FIXED_VALUE:
switch (_action) {
case Action::SET_VALUE:
var->SetValue(_strValue);
break;
case Type::APPEND:
case Action::APPEND:
apppend(*var, _strValue);
break;
case Type::APPEND_VAR: {
case Action::APPEND_VAR: {
auto var2 = _variable2.lock();
if (!var2) {
return true;
@@ -328,14 +291,14 @@ bool MacroActionVariable::PerformAction()
apppend(*var, var2->Value());
break;
}
case Type::INCREMENT:
case Action::INCREMENT:
modifyNumValue(*var, _numValue.GetValue(), true);
break;
case Type::DECREMENT:
case Action::DECREMENT:
modifyNumValue(*var, _numValue.GetValue(), false);
break;
case Type::SET_CONDITION_VALUE:
case Type::SET_ACTION_VALUE: {
case Action::SET_CONDITION_VALUE:
case Action::SET_ACTION_VALUE: {
auto m = GetMacro();
if (!m) {
return true;
@@ -350,7 +313,7 @@ bool MacroActionVariable::PerformAction()
var->SetValue(segment->GetVariableValue());
break;
}
case Type::ROUND_TO_INT: {
case Action::ROUND_TO_INT: {
auto curValue = var->DoubleValue();
if (!curValue.has_value()) {
return true;
@@ -358,7 +321,7 @@ bool MacroActionVariable::PerformAction()
var->SetValue(std::to_string(int(std::round(*curValue))));
return true;
}
case Type::SUBSTRING: {
case Action::SUBSTRING: {
if (_subStringRegex.Enabled()) {
HandleRegexSubString(var.get());
return true;
@@ -366,15 +329,15 @@ bool MacroActionVariable::PerformAction()
HandleIndexSubString(var.get());
return true;
}
case Type::FIND_AND_REPLACE: {
case Action::FIND_AND_REPLACE: {
HandleFindAndReplace(var.get());
return true;
}
case Type::MATH_EXPRESSION: {
case Action::MATH_EXPRESSION: {
HandleMathExpression(var.get());
return true;
}
case Type::USER_INPUT: {
case Action::USER_INPUT: {
auto params = std::make_shared<AskForInputParams>(
_useCustomPrompt
? QString::fromStdString(_inputPrompt)
@@ -399,22 +362,22 @@ bool MacroActionVariable::PerformAction()
var->SetValue(*params->result);
return true;
}
case Type::ENV_VARIABLE: {
case Action::ENV_VARIABLE: {
auto value = std::getenv(_envVariableName.c_str());
if (value) {
var->SetValue(value);
}
return true;
}
case Type::SCENE_ITEM_COUNT: {
case Action::SCENE_ITEM_COUNT: {
var->SetValue(GetSceneItemCount(_scene.GetScene(false)));
return true;
}
case Type::STRING_LENGTH: {
case Action::STRING_LENGTH: {
var->SetValue(std::string(_strValue).length());
return true;
}
case Type::EXTRACT_JSON: {
case Action::EXTRACT_JSON: {
auto value = GetJsonField(var->Value(), _strValue);
if (!value.has_value()) {
return true;
@@ -422,7 +385,7 @@ bool MacroActionVariable::PerformAction()
var->SetValue(*value);
return true;
}
case Type::SET_TO_TEMPVAR: {
case Action::SET_TO_TEMPVAR: {
auto tempVar = _tempVar.GetTempVariable(GetMacro());
if (!tempVar) {
return true;
@@ -434,18 +397,18 @@ bool MacroActionVariable::PerformAction()
var->SetValue(*value);
return true;
}
case Type::SCENE_ITEM_NAME:
case Action::SCENE_ITEM_NAME:
SetToSceneItemName(var.get());
return true;
case Type::PAD:
case Action::PAD:
var->SetValue(pad(var->Value(), _direction, _stringLength,
_paddingChar));
return true;
case Type::TRUNCATE:
case Action::TRUNCATE:
var->SetValue(
truncate(var->Value(), _direction, _stringLength));
return true;
case Type::SWAP_VALUES: {
case Action::SWAP_VALUES: {
auto var2 = _variable2.lock();
if (!var2) {
return true;
@@ -456,15 +419,18 @@ bool MacroActionVariable::PerformAction()
var2->SetValue(tempValue);
return true;
}
case Type::TRIM: {
case Action::TRIM: {
var->SetValue(QString::fromStdString(var->Value())
.trimmed()
.toStdString());
return true;
}
case Type::CHANGE_CASE:
case Action::CHANGE_CASE:
HandleCaseChange(var.get());
return true;
case Action::RANDOM_NUMBER:
GenerateRandomNumber(var.get());
return true;
}
return true;
@@ -479,7 +445,7 @@ bool MacroActionVariable::Save(obs_data_t *obj) const
GetWeakVariableName(_variable2).c_str());
_strValue.Save(obj, "strValue");
_numValue.Save(obj, "numValue");
obs_data_set_int(obj, "condition", static_cast<int>(_type));
obs_data_set_int(obj, "condition", static_cast<int>(_action));
obs_data_set_int(obj, "segmentIdx", GetSegmentIndexValue());
_subStringStart.Save(obj, "subStringStart");
_subStringSize.Save(obj, "subStringSize");
@@ -502,6 +468,9 @@ bool MacroActionVariable::Save(obs_data_t *obj) const
_stringLength.Save(obj, "stringLength");
obs_data_set_int(obj, "paddingChar", _paddingChar);
obs_data_set_int(obj, "caseType", static_cast<int>(_caseType));
_randomNumberStart.Save(obj, "randomNumberStart");
_randomNumberEnd.Save(obj, "randomNumberEnd");
obs_data_set_bool(obj, "generateInteger", _generateInteger);
obs_data_set_int(obj, "version", 1);
@@ -516,7 +485,7 @@ bool MacroActionVariable::Load(obs_data_t *obj)
_variable2 = GetWeakVariableByName(
obs_data_get_string(obj, "variable2Name"));
_strValue.Load(obj, "strValue");
_type = static_cast<Type>(obs_data_get_int(obj, "condition"));
_action = static_cast<Action>(obs_data_get_int(obj, "condition"));
_segmentIdxLoadValue = obs_data_get_int(obj, "segmentIdx");
_subStringRegex.Load(obj);
_regexPattern = obs_data_get_string(obj, "regexPattern");
@@ -555,6 +524,10 @@ bool MacroActionVariable::Load(obs_data_t *obj)
_regexMatchIdx.Load(obj, "regexMatchIdx");
}
_randomNumberStart.Load(obj, "randomNumberStart");
_randomNumberEnd.Load(obj, "randomNumberEnd");
_generateInteger = obs_data_get_bool(obj, "generateInteger");
return true;
}
@@ -595,11 +568,11 @@ void MacroActionVariable::SetSegmentIndexValue(int value)
}
std::shared_ptr<MacroSegment> segment;
if (_type == Type::SET_CONDITION_VALUE) {
if (_action == Action::SET_CONDITION_VALUE) {
if (value < (int)m->Conditions().size()) {
segment = m->Conditions().at(value);
}
} else if (_type == Type::SET_ACTION_VALUE) {
} else if (_action == Action::SET_ACTION_VALUE) {
if (value < (int)m->Actions().size()) {
segment = m->Actions().at(value);
}
@@ -623,14 +596,14 @@ int MacroActionVariable::GetSegmentIndexValue() const
return -1;
}
if (_type == Type::SET_CONDITION_VALUE) {
if (_action == Action::SET_CONDITION_VALUE) {
auto it = std::find(m->Conditions().begin(),
m->Conditions().end(), segment);
if (it != m->Conditions().end()) {
return std::distance(m->Conditions().begin(), it);
}
return -1;
} else if (_type == Type::SET_ACTION_VALUE) {
} else if (_action == Action::SET_ACTION_VALUE) {
auto it = std::find(m->Actions().begin(), m->Actions().end(),
segment);
if (it != m->Actions().end()) {
@@ -665,16 +638,99 @@ void MacroActionVariable::DecrementCurrentSegmentVariableRef()
DecrementVariableRef(segment.get());
}
static inline void populateTypeSelection(QComboBox *list)
static inline void populateActionSelection(QComboBox *list)
{
for (const auto &[action, name] : actionTypes) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(action));
static const std::vector<std::pair<
std::variant<MacroActionVariable::Action, bool>, std::string>>
actions = {
{MacroActionVariable::Action::SET_VALUE,
"AdvSceneSwitcher.action.variable.type.set"},
{MacroActionVariable::Action::APPEND,
"AdvSceneSwitcher.action.variable.type.append"},
{MacroActionVariable::Action::PAD,
"AdvSceneSwitcher.action.variable.type.padValue"},
{MacroActionVariable::Action::TRUNCATE,
"AdvSceneSwitcher.action.variable.type.truncateValue"},
{MacroActionVariable::Action::SUBSTRING,
"AdvSceneSwitcher.action.variable.type.subString"},
{MacroActionVariable::Action::FIND_AND_REPLACE,
"AdvSceneSwitcher.action.variable.type.findAndReplace"},
{MacroActionVariable::Action::EXTRACT_JSON,
"AdvSceneSwitcher.action.variable.type.extractJson"},
{MacroActionVariable::Action::STRING_LENGTH,
"AdvSceneSwitcher.action.variable.type.stringLength"},
{MacroActionVariable::Action::TRIM,
"AdvSceneSwitcher.action.variable.type.trim"},
{MacroActionVariable::Action::CHANGE_CASE,
"AdvSceneSwitcher.action.variable.type.changeCase"},
{true, ""}, // Separator
{MacroActionVariable::Action::SET_TO_TEMPVAR,
"AdvSceneSwitcher.action.variable.type.setToTempvar"},
{MacroActionVariable::Action::SET_CONDITION_VALUE,
"AdvSceneSwitcher.action.variable.type.setConditionValue"},
{MacroActionVariable::Action::SET_ACTION_VALUE,
"AdvSceneSwitcher.action.variable.type.setActionValue"},
{true, ""}, // Separator
{MacroActionVariable::Action::MATH_EXPRESSION,
"AdvSceneSwitcher.action.variable.type.mathExpression"},
{MacroActionVariable::Action::INCREMENT,
"AdvSceneSwitcher.action.variable.type.increment"},
{MacroActionVariable::Action::DECREMENT,
"AdvSceneSwitcher.action.variable.type.decrement"},
{MacroActionVariable::Action::ROUND_TO_INT,
"AdvSceneSwitcher.action.variable.type.roundToInt"},
{MacroActionVariable::Action::RANDOM_NUMBER,
"AdvSceneSwitcher.action.variable.type.randomNumber"},
{true, ""}, // Separator
{MacroActionVariable::Action::USER_INPUT,
"AdvSceneSwitcher.action.variable.type.askForValue"},
{MacroActionVariable::Action::ENV_VARIABLE,
"AdvSceneSwitcher.action.variable.type.environmentVariable"},
{true, ""}, // Separator
{MacroActionVariable::Action::SCENE_ITEM_NAME,
"AdvSceneSwitcher.action.variable.type.sceneItemName"},
{MacroActionVariable::Action::SCENE_ITEM_COUNT,
"AdvSceneSwitcher.action.variable.type.sceneItemCount"},
{true, ""}, // Separator
{MacroActionVariable::Action::SWAP_VALUES,
"AdvSceneSwitcher.action.variable.type.swapValues"},
{MacroActionVariable::Action::APPEND_VAR,
"AdvSceneSwitcher.action.variable.type.appendVar"},
};
for (const auto &[action, name] : actions) {
if (std::holds_alternative<bool>(action)) {
list->insertSeparator(actions.size());
continue;
}
list->addItem(
obs_module_text(name.c_str()),
static_cast<int>(
std::get<MacroActionVariable::Action>(action)));
}
}
static inline void populateCaseTypeSelection(QComboBox *list)
{
const static std::map<MacroActionVariable::CaseType, std::string>
caseTypes = {
{MacroActionVariable::CaseType::LOWER_CASE,
"AdvSceneSwitcher.action.variable.case.type.lowerCase"},
{MacroActionVariable::CaseType::UPPER_CASE,
"AdvSceneSwitcher.action.variable.case.type.upperCase"},
{MacroActionVariable::CaseType::CAPITALIZED,
"AdvSceneSwitcher.action.variable.case.type.capitalized"},
{MacroActionVariable::CaseType::START_CASE,
"AdvSceneSwitcher.action.variable.case.type.startCase"},
};
for (const auto &[type, name] : caseTypes) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(type));
@@ -734,6 +790,13 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_stringLength(new VariableSpinBox(this)),
_paddingCharSelection(new SingleCharSelection()),
_caseType(new FilterComboBox(this)),
_randomNumberStart(new VariableDoubleSpinBox(this)),
_randomNumberEnd(new VariableDoubleSpinBox(this)),
_generateInteger(new QCheckBox(
obs_module_text(
"AdvSceneSwitcher.action.variable.generateInteger"),
this)),
_randomLayout(new QVBoxLayout()),
_entryLayout(new QHBoxLayout())
{
_numValue->setMinimum(-9999999999);
@@ -756,9 +819,13 @@ MacroActionVariableEdit::MacroActionVariableEdit(
QSizePolicy::Preferred);
_sceneItemIndex->setMinimum(1);
_stringLength->setMaximum(999);
populateTypeSelection(_actions);
populateActionSelection(_actions);
populateDirectionSelection(_direction);
populateCaseTypeSelection(_caseType);
_randomNumberStart->setMinimum(-9999999999);
_randomNumberStart->setMaximum(9999999999);
_randomNumberEnd->setMinimum(-9999999999);
_randomNumberEnd->setMaximum(9999999999);
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
this, SLOT(VariableChanged(const QString &)));
@@ -833,6 +900,18 @@ MacroActionVariableEdit::MacroActionVariableEdit(
SLOT(CharSelectionChanged(const QString &)));
QWidget::connect(_caseType, SIGNAL(currentIndexChanged(int)), this,
SLOT(CaseTypeChanged(int)));
QWidget::connect(
_randomNumberStart,
SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
this,
SLOT(RandomNumberStartChanged(const NumberVariable<double> &)));
QWidget::connect(
_randomNumberEnd,
SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
this,
SLOT(RandomNumberEndChanged(const NumberVariable<double> &)));
QWidget::connect(_generateInteger, SIGNAL(stateChanged(int)), this,
SLOT(GenerateIntegerChanged(int)));
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{variables}}", _variables},
@@ -861,6 +940,9 @@ MacroActionVariableEdit::MacroActionVariableEdit(
{"{{stringLength}}", _stringLength},
{"{{paddingCharSelection}}", _paddingCharSelection},
{"{{caseType}}", _caseType},
{"{{randomNumberStart}}", _randomNumberStart},
{"{{randomNumberEnd}}", _randomNumberEnd},
{"{{generateInteger}}", _generateInteger},
};
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.action.variable.entry.other"),
@@ -890,6 +972,14 @@ MacroActionVariableEdit::MacroActionVariableEdit(
"AdvSceneSwitcher.action.variable.entry.userInput.placeholder"),
_placeholderLayout, widgetPlaceholders);
auto randomLayout = new QHBoxLayout();
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.variable.entry.randomNumber"),
randomLayout, widgetPlaceholders);
_randomLayout->addLayout(randomLayout);
_randomLayout->addWidget(_generateInteger);
auto regexConfigLayout = new QHBoxLayout;
regexConfigLayout->addWidget(_substringRegex);
regexConfigLayout->addStretch();
@@ -908,6 +998,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
layout->addWidget(_mathExpressionResult);
layout->addLayout(_promptLayout);
layout->addLayout(_placeholderLayout);
layout->addLayout(_randomLayout);
setLayout(layout);
_entryData = entryData;
@@ -929,14 +1020,14 @@ void MacroActionVariableEdit::UpdateEntryData()
_variables->SetVariable(_entryData->_variable);
_variables2->SetVariable(_entryData->_variable2);
_actions->setCurrentIndex(
_actions->findData(static_cast<int>(_entryData->_type)));
_actions->findData(static_cast<int>(_entryData->_action)));
_strValue->setPlainText(_entryData->_strValue);
_numValue->SetValue(_entryData->_numValue);
_segmentIdx->SetValue(_entryData->GetSegmentIndexValue() + 1);
_segmentIdx->SetMacro(_entryData->GetMacro());
_segmentIdx->SetType(
_entryData->_type ==
MacroActionVariable::Type::SET_CONDITION_VALUE
_entryData->_action ==
MacroActionVariable::Action::SET_CONDITION_VALUE
? MacroSegmentSelection::Type::CONDITION
: MacroSegmentSelection::Type::ACTION);
_subStringStart->SetValue(_entryData->_subStringStart);
@@ -963,6 +1054,9 @@ void MacroActionVariableEdit::UpdateEntryData()
QChar::fromLatin1(_entryData->_paddingChar));
_caseType->setCurrentIndex(
_caseType->findData(static_cast<int>(_entryData->_caseType)));
_randomNumberStart->SetValue(_entryData->_randomNumberStart);
_randomNumberEnd->SetValue(_entryData->_randomNumberEnd);
_generateInteger->setChecked(_entryData->_generateInteger);
SetWidgetVisibility();
}
@@ -996,13 +1090,14 @@ void MacroActionVariableEdit::ActionChanged(int idx)
}
auto lock = LockContext();
_entryData->_type = static_cast<MacroActionVariable::Type>(
_entryData->_action = static_cast<MacroActionVariable::Action>(
_actions->itemData(idx).toInt());
if (_entryData->_type == MacroActionVariable::Type::SET_ACTION_VALUE) {
if (_entryData->_action ==
MacroActionVariable::Action::SET_ACTION_VALUE) {
_segmentIdx->SetType(MacroSegmentSelection::Type::ACTION);
} else if (_entryData->_type ==
MacroActionVariable::Type::SET_CONDITION_VALUE) {
} else if (_entryData->_action ==
MacroActionVariable::Action::SET_CONDITION_VALUE) {
_segmentIdx->SetType(MacroSegmentSelection::Type::CONDITION);
}
SetWidgetVisibility();
@@ -1054,10 +1149,10 @@ void MacroActionVariableEdit::SetSegmentValueError(const QString &text)
void MacroActionVariableEdit::UpdateSegmentVariableValue()
{
if (!_entryData ||
!(_entryData->_type ==
MacroActionVariable::Type::SET_CONDITION_VALUE ||
_entryData->_type ==
MacroActionVariable::Type::SET_ACTION_VALUE)) {
!(_entryData->_action ==
MacroActionVariable::Action::SET_CONDITION_VALUE ||
_entryData->_action ==
MacroActionVariable::Action::SET_ACTION_VALUE)) {
return;
}
@@ -1076,13 +1171,14 @@ void MacroActionVariableEdit::UpdateSegmentVariableValue()
}
std::shared_ptr<MacroSegment> segment;
if (_entryData->_type == MacroActionVariable::Type::SET_ACTION_VALUE) {
if (_entryData->_action ==
MacroActionVariable::Action::SET_ACTION_VALUE) {
const auto &actions = m->Actions();
if (index < (int)actions.size()) {
segment = actions.at(index);
}
} else if (_entryData->_type ==
MacroActionVariable::Type::SET_CONDITION_VALUE) {
} else if (_entryData->_action ==
MacroActionVariable::Action::SET_CONDITION_VALUE) {
const auto &conditions = m->Conditions();
if (index < (int)conditions.size()) {
segment = conditions.at(index);
@@ -1099,14 +1195,14 @@ void MacroActionVariableEdit::UpdateSegmentVariableValue()
std::string type;
QString fmt;
if (_entryData->_type ==
MacroActionVariable::Type::SET_ACTION_VALUE) {
if (_entryData->_action ==
MacroActionVariable::Action::SET_ACTION_VALUE) {
type = MacroActionFactory::GetActionName(
segment->GetId());
fmt = QString(obs_module_text(
"AdvSceneSwitcher.action.variable.actionNoVariableSupport"));
} else if (_entryData->_type ==
MacroActionVariable::Type::SET_CONDITION_VALUE) {
} else if (_entryData->_action ==
MacroActionVariable::Action::SET_CONDITION_VALUE) {
type = MacroConditionFactory::GetConditionName(
segment->GetId());
fmt = QString(obs_module_text(
@@ -1385,6 +1481,26 @@ void MacroActionVariableEdit::CaseTypeChanged(int index)
_caseType->itemData(index).toInt());
}
void MacroActionVariableEdit::RandomNumberStartChanged(
const NumberVariable<double> &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_randomNumberStart = value;
}
void MacroActionVariableEdit::RandomNumberEndChanged(
const NumberVariable<double> &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_randomNumberEnd = value;
}
void MacroActionVariableEdit::GenerateIntegerChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_generateInteger = value;
}
void MacroActionVariableEdit::SetWidgetVisibility()
{
if (!_entryData) {
@@ -1411,10 +1527,11 @@ void MacroActionVariableEdit::SetWidgetVisibility()
};
const char *layoutString = "";
if (_entryData->_type == MacroActionVariable::Type::PAD) {
if (_entryData->_action == MacroActionVariable::Action::PAD) {
layoutString = obs_module_text(
"AdvSceneSwitcher.action.variable.entry.pad");
} else if (_entryData->_type == MacroActionVariable::Type::TRUNCATE) {
} else if (_entryData->_action ==
MacroActionVariable::Action::TRUNCATE) {
layoutString = obs_module_text(
"AdvSceneSwitcher.action.variable.entry.truncate");
} else {
@@ -1429,64 +1546,70 @@ void MacroActionVariableEdit::SetWidgetVisibility()
ClearLayout(_entryLayout);
PlaceWidgets(layoutString, _entryLayout, widgetPlaceholders);
if (_entryData->_type == MacroActionVariable::Type::SET_FIXED_VALUE ||
_entryData->_type == MacroActionVariable::Type::APPEND ||
_entryData->_type == MacroActionVariable::Type::MATH_EXPRESSION ||
_entryData->_type == MacroActionVariable::Type::ENV_VARIABLE ||
_entryData->_type == MacroActionVariable::Type::STRING_LENGTH ||
_entryData->_type == MacroActionVariable::Type::EXTRACT_JSON) {
if (_entryData->_action == MacroActionVariable::Action::SET_VALUE ||
_entryData->_action == MacroActionVariable::Action::APPEND ||
_entryData->_action ==
MacroActionVariable::Action::MATH_EXPRESSION ||
_entryData->_action == MacroActionVariable::Action::ENV_VARIABLE ||
_entryData->_action == MacroActionVariable::Action::STRING_LENGTH ||
_entryData->_action == MacroActionVariable::Action::EXTRACT_JSON) {
RemoveStretchIfPresent(_entryLayout);
} else {
AddStretchIfNecessary(_entryLayout);
}
_variables2->setVisible(
_entryData->_type == MacroActionVariable::Type::APPEND_VAR ||
_entryData->_type == MacroActionVariable::Type::SWAP_VALUES);
_entryData->_action ==
MacroActionVariable::Action::APPEND_VAR ||
_entryData->_action ==
MacroActionVariable::Action::SWAP_VALUES);
_strValue->setVisible(
_entryData->_type ==
MacroActionVariable::Type::SET_FIXED_VALUE ||
_entryData->_type == MacroActionVariable::Type::APPEND ||
_entryData->_type == MacroActionVariable::Type::STRING_LENGTH ||
_entryData->_type == MacroActionVariable::Type::EXTRACT_JSON);
_entryData->_action == MacroActionVariable::Action::SET_VALUE ||
_entryData->_action == MacroActionVariable::Action::APPEND ||
_entryData->_action ==
MacroActionVariable::Action::STRING_LENGTH ||
_entryData->_action ==
MacroActionVariable::Action::EXTRACT_JSON);
_numValue->setVisible(
_entryData->_type == MacroActionVariable::Type::INCREMENT ||
_entryData->_type == MacroActionVariable::Type::DECREMENT);
_entryData->_action == MacroActionVariable::Action::INCREMENT ||
_entryData->_action == MacroActionVariable::Action::DECREMENT);
_segmentValueStatus->setVisible(
_entryData->_type ==
MacroActionVariable::Type::SET_ACTION_VALUE ||
_entryData->_type ==
MacroActionVariable::Type::SET_CONDITION_VALUE);
_entryData->_action ==
MacroActionVariable::Action::SET_ACTION_VALUE ||
_entryData->_action ==
MacroActionVariable::Action::SET_CONDITION_VALUE);
_segmentValue->setVisible(
_entryData->_type ==
MacroActionVariable::Type::SET_ACTION_VALUE ||
_entryData->_type ==
MacroActionVariable::Type::SET_CONDITION_VALUE);
_entryData->_action ==
MacroActionVariable::Action::SET_ACTION_VALUE ||
_entryData->_action ==
MacroActionVariable::Action::SET_CONDITION_VALUE);
_segmentIdx->setVisible(
_entryData->_type ==
MacroActionVariable::Type::SET_ACTION_VALUE ||
_entryData->_type ==
MacroActionVariable::Type::SET_CONDITION_VALUE);
_entryData->_action ==
MacroActionVariable::Action::SET_ACTION_VALUE ||
_entryData->_action ==
MacroActionVariable::Action::SET_CONDITION_VALUE);
SetLayoutVisible(_substringLayout,
_entryData->_type ==
MacroActionVariable::Type::SUBSTRING);
if (_entryData->_type == MacroActionVariable::Type::SUBSTRING) {
_entryData->_action ==
MacroActionVariable::Action::SUBSTRING);
if (_entryData->_action == MacroActionVariable::Action::SUBSTRING) {
bool showRegex = _entryData->_subStringRegex.Enabled();
SetLayoutVisible(_subStringIndexEntryLayout, !showRegex);
SetLayoutVisible(_subStringRegexEntryLayout, showRegex);
_regexPattern->setVisible(showRegex);
}
SetLayoutVisible(_findReplaceLayout,
_entryData->_type ==
MacroActionVariable::Type::FIND_AND_REPLACE);
_mathExpression->setVisible(_entryData->_type ==
MacroActionVariable::Type::MATH_EXPRESSION);
_entryData->_action ==
MacroActionVariable::Action::FIND_AND_REPLACE);
_mathExpression->setVisible(
_entryData->_action ==
MacroActionVariable::Action::MATH_EXPRESSION);
_mathExpressionResult->hide();
SetLayoutVisible(_promptLayout,
_entryData->_type ==
MacroActionVariable::Type::USER_INPUT);
_entryData->_action ==
MacroActionVariable::Action::USER_INPUT);
_inputPrompt->setVisible(
_entryData->_type == MacroActionVariable::Type::USER_INPUT &&
_entryData->_action ==
MacroActionVariable::Action::USER_INPUT &&
_entryData->_useCustomPrompt);
if (_entryData->_useCustomPrompt) {
RemoveStretchIfPresent(_promptLayout);
@@ -1495,13 +1618,16 @@ void MacroActionVariableEdit::SetWidgetVisibility()
}
SetLayoutVisible(
_placeholderLayout,
_entryData->_type == MacroActionVariable::Type::USER_INPUT &&
_entryData->_action ==
MacroActionVariable::Action::USER_INPUT &&
_entryData->_useCustomPrompt);
_useInputPlaceholder->setVisible(
_entryData->_type == MacroActionVariable::Type::USER_INPUT &&
_entryData->_action ==
MacroActionVariable::Action::USER_INPUT &&
_entryData->_useCustomPrompt);
_inputPlaceholder->setVisible(
_entryData->_type == MacroActionVariable::Type::USER_INPUT &&
_entryData->_action ==
MacroActionVariable::Action::USER_INPUT &&
_entryData->_useCustomPrompt &&
_entryData->_useInputPlaceholder);
if (_entryData->_useInputPlaceholder) {
@@ -1509,31 +1635,35 @@ void MacroActionVariableEdit::SetWidgetVisibility()
} else {
AddStretchIfNecessary(_placeholderLayout);
}
_envVariable->setVisible(_entryData->_type ==
MacroActionVariable::Type::ENV_VARIABLE);
_envVariable->setVisible(_entryData->_action ==
MacroActionVariable::Action::ENV_VARIABLE);
_scenes->setVisible(
_entryData->_type ==
MacroActionVariable::Type::SCENE_ITEM_COUNT ||
_entryData->_type ==
MacroActionVariable::Type::SCENE_ITEM_NAME);
_tempVars->setVisible(_entryData->_type ==
MacroActionVariable::Type::SET_TO_TEMPVAR);
_entryData->_action ==
MacroActionVariable::Action::SCENE_ITEM_COUNT ||
_entryData->_action ==
MacroActionVariable::Action::SCENE_ITEM_NAME);
_tempVars->setVisible(_entryData->_action ==
MacroActionVariable::Action::SET_TO_TEMPVAR);
_tempVarsHelp->setVisible(
_entryData->_type ==
MacroActionVariable::Type::SET_TO_TEMPVAR &&
_entryData->_action ==
MacroActionVariable::Action::SET_TO_TEMPVAR &&
!_entryData->_tempVar.HasValidID());
_sceneItemIndex->setVisible(_entryData->_type ==
MacroActionVariable::Type::SCENE_ITEM_NAME);
_sceneItemIndex->setVisible(
_entryData->_action ==
MacroActionVariable::Action::SCENE_ITEM_NAME);
_direction->setVisible(
_entryData->_type == MacroActionVariable::Type::PAD ||
_entryData->_type == MacroActionVariable::Type::TRUNCATE);
_entryData->_action == MacroActionVariable::Action::PAD ||
_entryData->_action == MacroActionVariable::Action::TRUNCATE);
_stringLength->setVisible(
_entryData->_type == MacroActionVariable::Type::PAD ||
_entryData->_type == MacroActionVariable::Type::TRUNCATE);
_paddingCharSelection->setVisible(_entryData->_type ==
MacroActionVariable::Type::PAD);
_caseType->setVisible(_entryData->_type ==
MacroActionVariable::Type::CHANGE_CASE);
_entryData->_action == MacroActionVariable::Action::PAD ||
_entryData->_action == MacroActionVariable::Action::TRUNCATE);
_paddingCharSelection->setVisible(_entryData->_action ==
MacroActionVariable::Action::PAD);
_caseType->setVisible(_entryData->_action ==
MacroActionVariable::Action::CHANGE_CASE);
SetLayoutVisible(_randomLayout,
_entryData->_action ==
MacroActionVariable::Action::RANDOM_NUMBER);
adjustSize();
updateGeometry();

View File

@@ -28,8 +28,8 @@ public:
int GetSegmentIndexValue() const;
void ResolveVariablesToFixedValues();
enum class Type {
SET_FIXED_VALUE,
enum class Action {
SET_VALUE,
APPEND,
APPEND_VAR,
INCREMENT,
@@ -52,9 +52,10 @@ public:
SWAP_VALUES,
TRIM,
CHANGE_CASE,
RANDOM_NUMBER,
};
Type _type = Type::SET_FIXED_VALUE;
Action _action = Action::SET_VALUE;
std::weak_ptr<Variable> _variable;
std::weak_ptr<Variable> _variable2;
StringVariable _strValue = "";
@@ -99,6 +100,9 @@ public:
};
CaseType _caseType = CaseType::LOWER_CASE;
DoubleVariable _randomNumberStart = 0;
DoubleVariable _randomNumberEnd = 100;
bool _generateInteger = true;
private:
void DecrementCurrentSegmentVariableRef();
@@ -108,6 +112,7 @@ private:
void HandleMathExpression(Variable *);
void HandleCaseChange(Variable *);
void SetToSceneItemName(Variable *);
void GenerateRandomNumber(Variable *);
std::weak_ptr<MacroSegment> _macroSegment;
int _segmentIdxLoadValue = -1;
@@ -161,6 +166,9 @@ private slots:
void StringLengthChanged(const NumberVariable<int> &);
void CharSelectionChanged(const QString &);
void CaseTypeChanged(int index);
void RandomNumberStartChanged(const NumberVariable<double> &);
void RandomNumberEndChanged(const NumberVariable<double> &);
void GenerateIntegerChanged(int);
signals:
void HeaderInfoChanged(const QString &);
@@ -206,6 +214,10 @@ private:
VariableSpinBox *_stringLength;
SingleCharSelection *_paddingCharSelection;
FilterComboBox *_caseType;
VariableDoubleSpinBox *_randomNumberStart;
VariableDoubleSpinBox *_randomNumberEnd;
QCheckBox *_generateInteger;
QVBoxLayout *_randomLayout;
QHBoxLayout *_entryLayout;
std::shared_ptr<MacroActionVariable> _entryData;

View File

@@ -8,10 +8,14 @@ namespace advss {
MacroConditionScript::MacroConditionScript(
Macro *m, const std::string &id, const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal, const std::string &completionSignal)
const std::string &triggerSignalName,
const std::string &completionSignalName,
const std::string &newInstanceSignalName,
const std::string &deletedInstanceSignalName)
: MacroCondition(m),
MacroSegmentScript(defaultSettings, propertiesSignalName,
triggerSignal, completionSignal),
triggerSignalName, completionSignalName,
newInstanceSignalName, deletedInstanceSignalName),
_id(id)
{
}
@@ -76,4 +80,29 @@ void MacroConditionScript::WaitForCompletion() const
}
}
void MacroConditionScript::RegisterTempVarHelper(const std::string &variableId,
const std::string &name,
const std::string &helpText)
{
AddTempvar(variableId, name, helpText);
}
void MacroConditionScript::DeregisterAllTempVarsHelper()
{
MacroSegment::SetupTempVars();
}
void MacroConditionScript::SetTempVarValueHelper(const std::string &variableId,
const std::string &value)
{
MacroCondition::SetTempVarValue(variableId, value);
}
void MacroConditionScript::SetupTempVars()
{
// This just exists so MacroSegment::SetupTempVars() is not called.
// We want the ScriptHandler to handle the registration and clearing of
// the temp vars.
}
} // namespace advss

View File

@@ -10,8 +10,10 @@ public:
MacroConditionScript(Macro *m, const std::string &id,
const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &signalComplete);
const std::string &triggerSignalName,
const std::string &completionSignalName,
const std::string &newInstanceSignalName,
const std::string &deletedInstanceSignalName);
MacroConditionScript(const advss::MacroConditionScript &);
bool CheckCondition();
bool Save(obs_data_t *obj) const;
@@ -20,6 +22,13 @@ public:
private:
void WaitForCompletion() const;
void RegisterTempVarHelper(const std::string &variableId,
const std::string &name,
const std::string &helpText);
void DeregisterAllTempVarsHelper();
void SetTempVarValueHelper(const std::string &variableId,
const std::string &value);
void SetupTempVars();
std::string _id = "";
};

View File

@@ -109,6 +109,14 @@ bool MacroWasPausedSince(
return macro ? macro->WasPausedSince(time) : false;
}
bool MacroWasCheckedSinceLastStart(Macro *macro)
{
if (!macro) {
return false;
}
return macro->LastConditionCheckTime().time_since_epoch().count() != 0;
}
void AddMacroHelperThread(Macro *macro, std::thread &&newThread)
{
if (!macro) {

View File

@@ -51,6 +51,7 @@ EXPORT bool MacroIsPaused(Macro *);
EXPORT bool
MacroWasPausedSince(Macro *,
const std::chrono::high_resolution_clock::time_point &);
EXPORT bool MacroWasCheckedSinceLastStart(Macro *);
EXPORT void AddMacroHelperThread(Macro *, std::thread &&);

View File

@@ -181,15 +181,7 @@ void MacroInputEdit::SetInputVariablesAndValues(
_variables = inputs;
_values = values;
if ((size_t)_values.size() < _variables._inputVariables.size()) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
for (int i = 0;
_variables._inputVariables.size() - (size_t)_values.size();
i++) {
_values.push_back({});
}
#else
_values.resize(_variables._inputVariables.size());
#endif
}
SetupWidgets();
}
@@ -199,7 +191,7 @@ bool MacroInputEdit::HasInputsToSet() const
return !_variables._inputVariables.empty();
}
void HighligthMacroSettingsButton(bool enable);
void HighlightMacroSettingsButton(bool enable);
void MacroInputEdit::SetupWidgets()
{

View File

@@ -29,6 +29,10 @@ static constexpr std::string_view defaultSettingsParam = "default_settings";
static constexpr std::string_view propertiesSignalParam =
"properties_signal_name";
static constexpr std::string_view triggerSignalParam = "trigger_signal_name";
static constexpr std::string_view newInstanceSignalNameParam =
"new_instance_signal_name";
static constexpr std::string_view deletedInstanceSignalNameParam =
"deleted_instance_signal_name";
static std::string getRegisterScriptSegmentDeclString(const char *funcName)
{
@@ -72,16 +76,37 @@ static const std::string deregisterScriptConditionDeclString =
/* Script variables */
static constexpr std::string_view valueParam = "value";
static constexpr std::string_view tempVarIdParam = "temp_var_id";
static constexpr std::string_view tempVarNameParam = "temp_var_name";
static constexpr std::string_view tempVarHelpParam = "temp_var_help";
static constexpr std::string_view getVariableValueFuncName =
"advss_get_variable_value";
static constexpr std::string_view setVariableValueFuncName =
"advss_set_variable_value";
static constexpr std::string_view registerTempVarFuncName =
"advss_register_temp_var";
static constexpr std::string_view deregisterAllTempVarsFuncName =
"advss_deregister_temp_vars";
static constexpr std::string_view setTempVarValueFuncName =
"advss_set_temp_var_value";
static const std::string getVariableValueDeclString =
std::string("bool ") + getVariableValueFuncName.data() + "(in string " +
nameParam.data() + ", out string " + valueParam.data() + ")";
static const std::string setVariableValueDeclString =
std::string("bool ") + setVariableValueFuncName.data() + "(in string " +
nameParam.data() + ", in string " + valueParam.data() + ")";
static const std::string registerTempVarDeclString =
std::string("bool ") + registerTempVarFuncName.data() + "(in string " +
tempVarIdParam.data() + ", in string " + tempVarNameParam.data() +
", in string " + tempVarHelpParam.data() + ", in int " +
GetInstanceIdParamName().data() + ")";
static const std::string deregisterAllTempVarsDeclString =
std::string("bool ") + deregisterAllTempVarsFuncName.data() +
"(in int " + GetInstanceIdParamName().data() + ")";
static const std::string setTempVarValueDeclString =
std::string("bool ") + setTempVarValueFuncName.data() + "(in string " +
tempVarIdParam.data() + ", in string " + valueParam.data() +
", in int " + GetInstanceIdParamName().data() + ")";
static bool setup();
static bool setupDone = setup();
@@ -103,13 +128,23 @@ static bool setup()
&ScriptHandler::GetVariableValue, nullptr);
proc_handler_add(ph, setVariableValueDeclString.c_str(),
&ScriptHandler::SetVariableValue, nullptr);
proc_handler_add(ph, registerTempVarDeclString.c_str(),
&ScriptHandler::RegisterTempVar, nullptr);
proc_handler_add(ph, deregisterAllTempVarsDeclString.c_str(),
&ScriptHandler::DeregisterAllTempVars, nullptr);
proc_handler_add(ph, setTempVarValueDeclString.c_str(),
&ScriptHandler::SetTempVarValue, nullptr);
return true;
}
static void replaceWhitespace(std::string &string)
static void replaceProblematicChars(std::string &string)
{
std::transform(string.begin(), string.end(), string.begin(), [](char c) {
return std::isspace(static_cast<unsigned char>(c)) ? '_' : c;
if (std::isspace(static_cast<unsigned char>(c)) || c == '(' ||
c == ')' || c == '[' || c == ']' || c == '{' || c == '}') {
return '_';
}
return c;
});
}
@@ -122,7 +157,7 @@ static std::string getTriggerSignal(const std::string &name,
const bool isAction)
{
std::string signal = name;
replaceWhitespace(signal);
replaceProblematicChars(signal);
signal += "_run";
signal += isAction ? "_action" : "_condition";
return signal;
@@ -140,12 +175,32 @@ static std::string getPropertiesSignal(const std::string &name,
const bool isAction)
{
std::string signal = name;
replaceWhitespace(signal);
replaceProblematicChars(signal);
signal += isAction ? "_action" : "_condition";
signal += "_get_properties";
return signal;
}
static std::string getNewInstanceSignal(const std::string &name,
const bool isAction)
{
std::string signal = name;
replaceProblematicChars(signal);
signal += isAction ? "_action" : "_condition";
signal += "_new_instance";
return signal;
}
static std::string getDeletedInstanceSignal(const std::string &name,
const bool isAction)
{
std::string signal = name;
replaceProblematicChars(signal);
signal += isAction ? "_action" : "_condition";
signal += "_deleted_instance";
return signal;
}
void ScriptHandler::RegisterScriptAction(void *, calldata_t *data)
{
const char *actionName;
@@ -177,14 +232,19 @@ void ScriptHandler::RegisterScriptAction(void *, calldata_t *data)
auto triggerSignalName = getTriggerSignal(actionName, true);
auto completionSignalName = getCompletionSignal(actionName, true);
auto propertiesSignalName = getPropertiesSignal(actionName, true);
auto newInstanceSignalName = getNewInstanceSignal(actionName, true);
auto deletedInstanceSignalName =
getDeletedInstanceSignal(actionName, true);
const auto createScriptAction =
[id, defaultSettings, propertiesSignalName, triggerSignalName,
completionSignalName](
completionSignalName, newInstanceSignalName,
deletedInstanceSignalName](
Macro *m) -> std::shared_ptr<MacroAction> {
return std::make_shared<MacroActionScript>(
m, id, defaultSettings, propertiesSignalName,
triggerSignalName, completionSignalName);
triggerSignalName, completionSignalName,
newInstanceSignalName, deletedInstanceSignalName);
};
if (!MacroActionFactory::Register(id, {createScriptAction,
MacroSegmentScriptEdit::Create,
@@ -202,9 +262,15 @@ void ScriptHandler::RegisterScriptAction(void *, calldata_t *data)
triggerSignalName.c_str());
calldata_set_string(data, propertiesSignalParam.data(),
propertiesSignalName.c_str());
calldata_set_string(data, newInstanceSignalNameParam.data(),
newInstanceSignalName.c_str());
calldata_set_string(data, deletedInstanceSignalNameParam.data(),
deletedInstanceSignalName.c_str());
_actions.emplace(id, ScriptSegmentType(id, propertiesSignalName,
triggerSignalName,
completionSignalName));
completionSignalName,
newInstanceSignalName,
deletedInstanceSignalName));
RETURN_SUCCESS();
}
@@ -279,14 +345,19 @@ void ScriptHandler::RegisterScriptCondition(void *, calldata_t *data)
auto triggerSignalName = getTriggerSignal(conditionName, false);
auto completionSignalName = getCompletionSignal(conditionName, false);
auto propertiesSignalName = getPropertiesSignal(conditionName, false);
auto newInstanceSignalName = getNewInstanceSignal(conditionName, false);
auto deletedInstanceSignalName =
getDeletedInstanceSignal(conditionName, false);
const auto createScriptCondition =
[id, defaultSettings, propertiesSignalName, triggerSignalName,
completionSignalName](
completionSignalName, newInstanceSignalName,
deletedInstanceSignalName](
Macro *m) -> std::shared_ptr<MacroCondition> {
return std::make_shared<MacroConditionScript>(
m, id, defaultSettings, propertiesSignalName,
triggerSignalName, completionSignalName);
triggerSignalName, completionSignalName,
newInstanceSignalName, deletedInstanceSignalName);
};
if (!MacroConditionFactory::Register(
id, {createScriptCondition, MacroSegmentScriptEdit::Create,
@@ -304,9 +375,15 @@ void ScriptHandler::RegisterScriptCondition(void *, calldata_t *data)
triggerSignalName.c_str());
calldata_set_string(data, propertiesSignalParam.data(),
propertiesSignalName.c_str());
calldata_set_string(data, newInstanceSignalNameParam.data(),
newInstanceSignalName.c_str());
calldata_set_string(data, deletedInstanceSignalNameParam.data(),
deletedInstanceSignalName.c_str());
_conditions.emplace(id, ScriptSegmentType(id, propertiesSignalName,
triggerSignalName,
completionSignalName));
completionSignalName,
newInstanceSignalName,
deletedInstanceSignalName));
RETURN_SUCCESS();
}
@@ -399,6 +476,92 @@ void ScriptHandler::SetVariableValue(void *, calldata_t *data)
RETURN_SUCCESS();
}
void ScriptHandler::RegisterTempVar(void *, calldata_t *data)
{
const char *variableId;
if (!calldata_get_string(data, tempVarIdParam.data(), &variableId) ||
strlen(variableId) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerTempVarFuncName.data(), tempVarIdParam.data());
RETURN_FAILURE();
}
const char *name;
if (!calldata_get_string(data, tempVarNameParam.data(), &name) ||
strlen(name) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerTempVarFuncName.data(), tempVarNameParam.data());
RETURN_FAILURE();
}
const char *helpText;
if (!calldata_get_string(data, tempVarHelpParam.data(), &helpText)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerTempVarFuncName.data(), tempVarHelpParam.data());
RETURN_FAILURE();
}
long long instanceId;
if (!calldata_get_int(data, GetInstanceIdParamName().data(),
&instanceId)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerTempVarFuncName.data(),
GetInstanceIdParamName().data());
RETURN_FAILURE();
}
std::lock_guard<std::mutex> lock(_mutex);
MacroSegmentScript::RegisterTempVar(variableId, name, helpText,
instanceId);
RETURN_SUCCESS();
}
void ScriptHandler::DeregisterAllTempVars(void *, calldata_t *data)
{
long long instanceId;
if (!calldata_get_int(data, GetInstanceIdParamName().data(),
&instanceId)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
deregisterAllTempVarsFuncName.data(),
GetInstanceIdParamName().data());
RETURN_FAILURE();
}
std::lock_guard<std::mutex> lock(_mutex);
MacroSegmentScript::DeregisterAllTempVars(instanceId);
RETURN_SUCCESS();
}
void ScriptHandler::SetTempVarValue(void *, calldata_t *data)
{
const char *variableId;
if (!calldata_get_string(data, tempVarIdParam.data(), &variableId) ||
strlen(variableId) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
setTempVarValueFuncName.data(), tempVarIdParam.data());
RETURN_FAILURE();
}
const char *variableValue;
if (!calldata_get_string(data, valueParam.data(), &variableValue) ||
strlen(variableValue) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
setTempVarValueFuncName.data(), valueParam.data());
RETURN_FAILURE();
}
long long instanceId;
if (!calldata_get_int(data, GetInstanceIdParamName().data(),
&instanceId)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
setTempVarValueFuncName.data(),
GetInstanceIdParamName().data());
RETURN_FAILURE();
}
std::lock_guard<std::mutex> lock(_mutex);
MacroSegmentScript::SetTempVarValue(variableId, variableValue,
instanceId);
RETURN_SUCCESS();
}
bool ScriptHandler::ActionIdIsValid(const std::string &id)
{
std::lock_guard<std::mutex> lock(_mutex);
@@ -424,24 +587,39 @@ static std::string signalNameToPropertiesSignalDecl(const std::string &name)
static std::string signalNameToCompletionSignalDecl(const std::string &name)
{
return std::string("bool ") + name + "(in int " +
GeCompletionIdParamName().data() + ")";
GetCompletionIdParamName().data() + ")";
}
ScriptSegmentType::ScriptSegmentType(const std::string &id,
const std::string &propertiesSignal,
const std::string &triggerSignal,
const std::string &completionSignal)
static std::string signalNameToInstanceSignalDecl(const std::string &name)
{
return std::string("void ") + name + "(out int " +
GetInstanceIdParamName().data() + ")";
}
ScriptSegmentType::ScriptSegmentType(
const std::string &id, const std::string &propertiesSignalName,
const std::string &triggerSignalName,
const std::string &completionSignalName,
const std::string &newInstanceSignalName,
const std::string &deletedInstanceSignalName)
: _id(id)
{
auto sh = obs_get_signal_handler();
signal_handler_add(
obs_get_signal_handler(),
signalNameToPropertiesSignalDecl(propertiesSignal).c_str());
sh,
signalNameToPropertiesSignalDecl(propertiesSignalName).c_str());
signal_handler_add(
obs_get_signal_handler(),
signalNameToTriggerSignalDecl(triggerSignal).c_str());
sh, signalNameToTriggerSignalDecl(triggerSignalName).c_str());
signal_handler_add(
obs_get_signal_handler(),
signalNameToCompletionSignalDecl(completionSignal).c_str());
sh,
signalNameToCompletionSignalDecl(completionSignalName).c_str());
signal_handler_add(
sh,
signalNameToInstanceSignalDecl(newInstanceSignalName).c_str());
signal_handler_add(
sh, signalNameToInstanceSignalDecl(deletedInstanceSignalName)
.c_str());
}
} // namespace advss

View File

@@ -10,9 +10,11 @@ class ScriptSegmentType {
public:
ScriptSegmentType() = delete;
ScriptSegmentType(const std::string &id,
const std::string &propertiesSignal,
const std::string &triggerSignal,
const std::string &completionSignal);
const std::string &propertiesSignalName,
const std::string &triggerSignalName,
const std::string &completionSignalName,
const std::string &newInstanceSignalName,
const std::string &deletedInstanceSignalName);
private:
std::string _id;
@@ -26,6 +28,9 @@ public:
static void RegisterScriptCondition(void *ctx, calldata_t *data);
static void DeregisterScriptCondition(void *ctx, calldata_t *data);
static void GetVariableValue(void *ctx, calldata_t *data);
static void RegisterTempVar(void *ctx, calldata_t *data);
static void DeregisterAllTempVars(void *ctx, calldata_t *data);
static void SetTempVarValue(void *ctx, calldata_t *data);
static void SetVariableValue(void *ctx, calldata_t *data);
static bool ActionIdIsValid(const std::string &id);
static bool ConditionIdIsValid(const std::string &id);
@@ -46,14 +51,19 @@ static constexpr std::string_view GetActionCompletionSignalParamName()
return "completion_signal_name";
}
static constexpr std::string_view GeCompletionIdParamName()
static constexpr std::string_view GetCompletionIdParamName()
{
return "completion_id";
}
static constexpr std::string_view GeResultSignalParamName()
static constexpr std::string_view GetResultSignalParamName()
{
return "result";
}
static constexpr std::string_view GetInstanceIdParamName()
{
return "instance_id";
}
} // namespace advss

View File

@@ -178,11 +178,7 @@ void MacroSegmentList::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton ||
event->button() == Qt::RightButton) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
_dragPosition = GetDragIndex(event->globalPos());
#else
_dragPosition = GetDragIndex(event->globalPosition().toPoint());
#endif
emit SelectionChanged(_dragPosition);
} else {
_dragPosition = -1;
@@ -300,11 +296,7 @@ void MacroSegmentList::dragMoveEvent(QDragMoveEvent *event)
return;
}
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
_dragCursorPos = (mapToGlobal(event->pos()));
#else
_dragCursorPos = (mapToGlobal(event->position().toPoint()));
#endif
CheckDropLine(_dragCursorPos);
}
@@ -450,16 +442,6 @@ void MacroSegmentList::dropEvent(QDropEvent *event)
{
HideLastDropLine();
auto widget = qobject_cast<QWidget *>(event->source());
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
if (widget && !widget->geometry().contains(event->pos()) &&
widgetIsInLayout(widget, _contentLayout)) {
int dropPosition = GetDropIndex(mapToGlobal(event->pos()));
if (dropPosition == -1) {
return;
}
emit Reorder(dropPosition, _dragPosition);
}
#else
if (widget &&
!widget->geometry().contains(event->position().toPoint()) &&
widgetIsInLayout(widget, _contentLayout)) {
@@ -470,7 +452,6 @@ void MacroSegmentList::dropEvent(QDropEvent *event)
}
emit Reorder(dropPosition, _dragPosition);
}
#endif
_dragPosition = -1;
}

View File

@@ -11,33 +11,106 @@
namespace advss {
static std::atomic_int completionIdCounter = 0;
static std::atomic_int instanceIdCounter = 0;
static std::mutex instanceMtx;
static std::vector<MacroSegmentScript *> instances{};
MacroSegmentScript::MacroSegmentScript(obs_data_t *defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &completionSignal)
MacroSegmentScript::MacroSegmentScript(
obs_data_t *defaultSettings, const std::string &propertiesSignalName,
const std::string &triggerSignalName,
const std::string &completionSignalName,
const std::string &newInstanceSignalName,
const std::string &deletedInstanceSignalName)
: _settings(obs_data_get_defaults(defaultSettings)),
_propertiesSignal(propertiesSignalName),
_triggerSignal(triggerSignal),
_completionSignal(completionSignal)
_triggerSignal(triggerSignalName),
_completionSignal(completionSignalName),
_newInstanceSignal(newInstanceSignalName),
_deletedInstanceSignal(deletedInstanceSignalName),
_instanceId(++instanceIdCounter)
{
signal_handler_connect(obs_get_signal_handler(),
completionSignal.c_str(),
completionSignalName.c_str(),
&MacroSegmentScript::CompletionSignalReceived,
this);
std::lock_guard<std::mutex> lock(instanceMtx);
instances.emplace_back(this);
SignalNewInstance();
}
MacroSegmentScript::MacroSegmentScript(const MacroSegmentScript &other)
: _settings(obs_data_create()),
_propertiesSignal(other._propertiesSignal),
_triggerSignal(other._triggerSignal),
_completionSignal(other._completionSignal)
_completionSignal(other._completionSignal),
_newInstanceSignal(other._newInstanceSignal),
_deletedInstanceSignal(other._deletedInstanceSignal),
_instanceId(++instanceIdCounter)
{
signal_handler_connect(obs_get_signal_handler(),
_completionSignal.c_str(),
&MacroSegmentScript::CompletionSignalReceived,
this);
obs_data_apply(_settings.Get(), other._settings.Get());
std::lock_guard<std::mutex> lock(instanceMtx);
instances.emplace_back(this);
SignalNewInstance();
}
MacroSegmentScript::~MacroSegmentScript()
{
auto data = calldata_create();
calldata_set_int(data, GetInstanceIdParamName().data(), _instanceId);
signal_handler_signal(obs_get_signal_handler(),
_deletedInstanceSignal.c_str(), data);
calldata_destroy(data);
std::lock_guard<std::mutex> lock(instanceMtx);
instances.erase(std::remove(instances.begin(), instances.end(), this),
instances.end());
}
void MacroSegmentScript::RegisterTempVar(const std::string &variableId,
const std::string &name,
const std::string &helpText,
int instanceId)
{
std::lock_guard<std::mutex> lock(instanceMtx);
for (auto instance : instances) {
if (instance->_instanceId != instanceId) {
continue;
}
instance->RegisterTempVarHelper(variableId, name, helpText);
break;
}
}
void MacroSegmentScript::DeregisterAllTempVars(int instanceId)
{
std::lock_guard<std::mutex> lock(instanceMtx);
for (auto instance : instances) {
if (instance->_instanceId != instanceId) {
continue;
}
instance->DeregisterAllTempVarsHelper();
}
}
void MacroSegmentScript::SetTempVarValue(const std::string &variableId,
const std::string &value,
int instanceId)
{
std::lock_guard<std::mutex> lock(instanceMtx);
for (auto instance : instances) {
if (instance->_instanceId != instanceId) {
continue;
}
instance->SetTempVarValueHelper(variableId, value);
}
}
bool MacroSegmentScript::Save(obs_data_t *obj) const
@@ -85,8 +158,10 @@ bool MacroSegmentScript::SendTriggerSignal()
auto data = calldata_create();
calldata_set_string(data, GetActionCompletionSignalParamName().data(),
_completionSignal.c_str());
calldata_set_int(data, GeCompletionIdParamName().data(), _completionId);
calldata_set_int(data, GetCompletionIdParamName().data(),
_completionId);
calldata_set_string(data, "settings", obs_data_get_json(GetSettings()));
calldata_set_int(data, GetInstanceIdParamName().data(), _instanceId);
signal_handler_signal(obs_get_signal_handler(), _triggerSignal.c_str(),
data);
calldata_destroy(data);
@@ -101,18 +176,18 @@ void MacroSegmentScript::CompletionSignalReceived(void *param, calldata_t *data)
{
auto segment = static_cast<MacroSegmentScript *>(param);
long long int id;
if (!calldata_get_int(data, GeCompletionIdParamName().data(), &id)) {
if (!calldata_get_int(data, GetCompletionIdParamName().data(), &id)) {
blog(LOG_WARNING,
"received completion signal without \"%s\" parameter",
GeCompletionIdParamName().data());
GetCompletionIdParamName().data());
return;
}
bool result;
if (!calldata_get_bool(data, GeResultSignalParamName().data(),
if (!calldata_get_bool(data, GetResultSignalParamName().data(),
&result)) {
blog(LOG_WARNING,
"received completion signal without \"%s\" parameter",
GeResultSignalParamName().data());
GetResultSignalParamName().data());
return;
}
if (id != segment->_completionId) {
@@ -122,6 +197,19 @@ void MacroSegmentScript::CompletionSignalReceived(void *param, calldata_t *data)
segment->_triggerResult = result;
}
void MacroSegmentScript::SignalNewInstance() const
{
std::thread signalNewInstanceThread([this]() {
auto data = calldata_create();
calldata_set_int(data, GetInstanceIdParamName().data(),
_instanceId);
signal_handler_signal(obs_get_signal_handler(),
_newInstanceSignal.c_str(), data);
calldata_destroy(data);
});
signalNewInstanceThread.detach();
}
obs_properties_t *MacroSegmentScriptEdit::GetProperties(void *obj)
{
auto segmentEdit = reinterpret_cast<MacroSegmentScriptEdit *>(obj);

View File

@@ -15,9 +15,20 @@ class MacroSegmentScript {
public:
MacroSegmentScript(obs_data_t *defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &completionSignal);
const std::string &triggerSignalName,
const std::string &completionSignalName,
const std::string &newInstanceSignalName,
const std::string &deletedInstanceSignalName);
MacroSegmentScript(const advss::MacroSegmentScript &);
virtual ~MacroSegmentScript();
static void RegisterTempVar(const std::string &variableId,
const std::string &name,
const std::string &helpText,
int instanceId);
static void DeregisterAllTempVars(int instanceId);
static void SetTempVarValue(const std::string &variableId,
const std::string &value, int instanceId);
protected:
bool Save(obs_data_t *obj) const;
@@ -31,15 +42,27 @@ protected:
double GetTimeoutSeconds() const { return _timeout.Seconds(); };
bool TriggerIsCompleted() const { return _triggerIsComplete; }
virtual void RegisterTempVarHelper(const std::string &variableId,
const std::string &name,
const std::string &helpText) = 0;
virtual void DeregisterAllTempVarsHelper() = 0;
virtual void SetTempVarValueHelper(const std::string &variableId,
const std::string &value) = 0;
const int64_t _instanceId;
private:
virtual void WaitForCompletion() const = 0;
static void CompletionSignalReceived(void *param, calldata_t *data);
void SignalNewInstance() const;
OBSDataAutoRelease _settings;
std::string _propertiesSignal = "";
std::string _triggerSignal = "";
std::string _completionSignal = "";
std::string _newInstanceSignal = "";
std::string _deletedInstanceSignal = "";
std::atomic_bool _triggerIsComplete = {false};
bool _triggerResult = false;
int64_t _completionId = 0;

View File

@@ -294,13 +294,8 @@ bool MacroSegmentEdit::eventFilter(QObject *obj, QEvent *ev)
QMouseEvent *newEvent = new QMouseEvent(
mouseEvent->type(),
_headerInfo->mapTo(this, mouseEvent->pos()),
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
mouseEvent->globalPos(),
#else
mouseEvent->globalPosition(),
#endif
mouseEvent->button(), mouseEvent->buttons(),
mouseEvent->modifiers());
mouseEvent->globalPosition(), mouseEvent->button(),
mouseEvent->buttons(), mouseEvent->modifiers());
QApplication::sendEvent(parentWidget(), newEvent);
}
return QWidget::eventFilter(obj, ev);

View File

@@ -178,6 +178,7 @@ void MacroTreeItem::Update(bool force)
QSizePolicy::Maximum);
_expand->setMaximumSize(10, 16);
_expand->setMinimumSize(10, 0);
_expand->setProperty("class", "checkbox-icon indicator-expand");
#ifdef __APPLE__
_expand->setAttribute(Qt::WA_LayoutUsesWidgetRect);
#endif
@@ -888,14 +889,7 @@ void MacroTree::dropEvent(QDropEvent *event)
QModelIndexList indices = selectedIndexes();
DropIndicatorPosition indicator = dropIndicatorPosition();
int row = indexAt(
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
event->position().toPoint()
#else
event->pos()
#endif
)
.row();
int row = indexAt(event->position().toPoint()).row();
bool emptyDrop = row == -1;
if (emptyDrop) {

View File

@@ -244,8 +244,13 @@ bool Macro::CheckConditions(bool ignorePause)
return false;
}
_stop = false;
_matched = false;
for (auto &condition : _conditions) {
if (!condition) {
continue;
}
if (_paused && !ignorePause) {
vblog(LOG_INFO, "Macro %s is paused", _name.c_str());
return false;
@@ -383,6 +388,9 @@ bool Macro::RunActionsHelper(
bool actionsExecutedSuccessfully = true;
for (auto &action : actions) {
if (!action) {
continue;
}
if (action->Enabled()) {
action->LogAction();
actionsExecutedSuccessfully =

View File

@@ -40,8 +40,8 @@ ActionQueueTable *ActionQueueTable::Create()
void ActionQueueTable::Add()
{
auto newQueue = std::make_shared<ActionQueue>();
auto accepted =
ActionQueueSettingsDialog::AskForSettings(this, *newQueue);
auto accepted = ActionQueueSettingsDialog::AskForSettings(
GetSettingsWindow(), *newQueue);
if (!accepted) {
return;
}
@@ -124,7 +124,7 @@ static void openSettingsDialog()
auto oldName = queue->Name();
bool accepted = ActionQueueSettingsDialog::AskForSettings(
tabWidget->Table(), *queue.get());
GetSettingsWindow(), *queue.get());
if (accepted && oldName != queue->Name()) {
ActionQueueSignalManager::Instance()->Rename(
QString::fromStdString(oldName),

View File

@@ -113,6 +113,7 @@ void ActionQueue::Add(const std::shared_ptr<MacroAction> &action)
action->Save(data);
copy->Load(data);
copy->PostLoad();
RunPostLoadSteps();
copy->ResolveVariablesToFixedValues();
_actions.emplace_back(copy);
} else {
@@ -179,7 +180,7 @@ ActionQueueSettingsDialog::ActionQueueSettingsDialog(QWidget *parent,
"AdvSceneSwitcher.actionQueues.select",
"AdvSceneSwitcher.actionQueues.add",
"AdvSceneSwitcher.actionQueues.nameNotAvailable",
parent),
true, parent),
_queueRunStatus(new QLabel()),
_startStopToggle(new QPushButton()),
_queueSize(new QLabel()),

View File

@@ -14,11 +14,7 @@ AutoUpdateTooltipLabel::AutoUpdateTooltipLabel(
&AutoUpdateTooltipLabel::UpdateTooltip);
}
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
void AutoUpdateTooltipLabel::enterEvent(QEnterEvent *event)
#else
void AutoUpdateTooltipLabel::enterEvent(QEvent *event)
#endif
{
_timer->start(_updateIntervalMs);
QLabel::enterEvent(event);

View File

@@ -17,11 +17,7 @@ public:
int updateIntervalMs = 300);
protected:
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
void enterEvent(QEnterEvent *event) override;
#else
void enterEvent(QEvent *event) override;
#endif
void leaveEvent(QEvent *event) override;
private slots:

View File

@@ -29,6 +29,7 @@ void Duration::Load(obs_data_t *obj, const char *name)
// TODO: remove this fallback
if (!data || !obs_data_has_user_value(data, "version") ||
obs_data_get_int(data, "version") != 1) {
_value = 0.;
bool usingDefaultArgs = strcmp("duration", name) == 0;
if (usingDefaultArgs) {
_value = obs_data_get_double(obj, "seconds");

View File

@@ -59,7 +59,10 @@ ItemSelection::ItemSelection(std::deque<std::shared_ptr<Item>> &items,
_conflictStr(conflict)
{
_modify->setMaximumWidth(22);
SetButtonIcon(_modify, ":/settings/images/settings/general.svg");
SetButtonIcon(_modify,
GetThemeTypeName() == "Light"
? ":/settings/images/settings/general.svg"
: "theme:Dark/settings/general.svg");
_modify->setFlat(true);
if (!configureTooltip.empty()) {
_modify->setToolTip(obs_module_text(configureTooltip.data()));
@@ -265,6 +268,7 @@ ItemSettingsDialog::ItemSettingsDialog(const Item &settings,
std::string_view select,
std::string_view add,
std::string_view nameConflict,
bool showNameEmptyWarning,
QWidget *parent)
: QDialog(parent),
_name(new QLineEdit()),
@@ -274,7 +278,8 @@ ItemSettingsDialog::ItemSettingsDialog(const Item &settings,
_items(items),
_selectStr(select),
_addStr(add),
_conflictStr(nameConflict)
_conflictStr(nameConflict),
_showNameEmptyWarning(showNameEmptyWarning)
{
setModal(true);
setWindowModality(Qt::WindowModality::WindowModal);

View File

@@ -45,7 +45,7 @@ public:
std::string_view addString = "AdvSceneSwitcher.item.add",
std::string_view conflictString =
"AdvSceneSwitcher.item.nameNotAvailable",
QWidget *parent = 0);
bool showEmptyNameWarning = true, QWidget *parent = 0);
virtual ~ItemSettingsDialog() = default;
private slots:

View File

@@ -92,7 +92,10 @@ RegexConfigWidget::RegexConfigWidget(QWidget *parent, bool showEnable)
_openSettings(new QToolButton()),
_enable(new QPushButton())
{
SetButtonIcon(_openSettings, ":/settings/images/settings/general.svg");
SetButtonIcon(_openSettings,
GetThemeTypeName() == "Light"
? ":/settings/images/settings/general.svg"
: "theme:Dark/settings/general.svg");
_openSettings->setToolTip(
obs_module_text("AdvSceneSwitcher.regex.configure"));

View File

@@ -94,7 +94,7 @@ void ResizingPlainTextEdit::AddHeightForScrollBar(bool addHeight)
return;
}
_hScrollBarAddedHeight = verticalScrollBar()->height();
_hScrollBarAddedHeight = horizontalScrollBar()->height();
setFixedHeight(height() + _hScrollBarAddedHeight);
}

View File

@@ -29,7 +29,7 @@ private:
T _value = {};
std::weak_ptr<Variable> _variable;
friend class GenericVaraiableSpinbox;
friend class GenericVariableSpinbox;
friend class VariableSpinBox;
friend class VariableDoubleSpinBox;
};

View File

@@ -5,8 +5,8 @@
namespace advss {
GenericVaraiableSpinbox::GenericVaraiableSpinbox(QWidget *parent,
bool wholeNumber)
GenericVariableSpinbox::GenericVariableSpinbox(QWidget *parent,
bool wholeNumber)
: QWidget(parent),
_fixedValueInt(new QSpinBox()),
_fixedValueDouble(new QDoubleSpinBox()),
@@ -16,7 +16,9 @@ GenericVaraiableSpinbox::GenericVaraiableSpinbox(QWidget *parent,
{
_toggleType->setCheckable(true);
_toggleType->setMaximumWidth(11);
SetButtonIcon(_toggleType, ":/res/images/dots-vert.svg");
SetButtonIcon(_toggleType, GetThemeTypeName() == "Light"
? ":/res/images/dots-vert.svg"
: "theme:Dark/dots-vert.svg");
QWidget::connect(_fixedValueInt, SIGNAL(valueChanged(int)), this,
SLOT(SetFixedValue(int)));
@@ -48,7 +50,7 @@ GenericVaraiableSpinbox::GenericVaraiableSpinbox(QWidget *parent,
SetVisibility();
}
void GenericVaraiableSpinbox::SetValue(const NumberVariable<int> &number)
void GenericVariableSpinbox::SetValue(const NumberVariable<int> &number)
{
_numberInt = number;
const QSignalBlocker b1(_toggleType);
@@ -59,7 +61,7 @@ void GenericVaraiableSpinbox::SetValue(const NumberVariable<int> &number)
SetVisibility();
}
void GenericVaraiableSpinbox::SetValue(const NumberVariable<double> &number)
void GenericVariableSpinbox::SetValue(const NumberVariable<double> &number)
{
_numberDouble = number;
const QSignalBlocker b1(_toggleType);
@@ -70,44 +72,44 @@ void GenericVaraiableSpinbox::SetValue(const NumberVariable<double> &number)
SetVisibility();
}
void GenericVaraiableSpinbox::DisableVariableSelection()
void GenericVariableSpinbox::DisableVariableSelection()
{
_hideTypeToggle = true;
_toggleType->hide();
ToggleTypeClicked(false);
}
void GenericVaraiableSpinbox::setMinimum(double value)
void GenericVariableSpinbox::setMinimum(double value)
{
_fixedValueInt->setMinimum(value);
_fixedValueDouble->setMinimum(value);
}
void GenericVaraiableSpinbox::setMaximum(double value)
void GenericVariableSpinbox::setMaximum(double value)
{
_fixedValueInt->setMaximum(value);
_fixedValueDouble->setMaximum(value);
}
void GenericVaraiableSpinbox::setPrefix(const QString &prefix)
void GenericVariableSpinbox::setPrefix(const QString &prefix)
{
_fixedValueInt->setPrefix(prefix);
_fixedValueDouble->setPrefix(prefix);
}
void GenericVaraiableSpinbox::setSuffix(const QString &suffix)
void GenericVariableSpinbox::setSuffix(const QString &suffix)
{
_fixedValueInt->setSuffix(suffix);
_fixedValueDouble->setSuffix(suffix);
}
void GenericVaraiableSpinbox::setSpecialValueText(const QString &text)
void GenericVariableSpinbox::setSpecialValueText(const QString &text)
{
_fixedValueInt->setSpecialValueText(text);
_fixedValueDouble->setSpecialValueText(text);
}
void GenericVaraiableSpinbox::SetFixedValue(int value)
void GenericVariableSpinbox::SetFixedValue(int value)
{
_numberInt._value = value;
const QSignalBlocker b(_fixedValueInt);
@@ -115,7 +117,7 @@ void GenericVaraiableSpinbox::SetFixedValue(int value)
EmitSignals();
}
void GenericVaraiableSpinbox::SetFixedValue(double value)
void GenericVariableSpinbox::SetFixedValue(double value)
{
_numberDouble._value = value;
const QSignalBlocker b(_fixedValueDouble);
@@ -123,14 +125,14 @@ void GenericVaraiableSpinbox::SetFixedValue(double value)
EmitSignals();
}
void GenericVaraiableSpinbox::VariableChanged(const QString &name)
void GenericVariableSpinbox::VariableChanged(const QString &name)
{
_numberInt._variable = GetWeakVariableByQString(name);
_numberDouble._variable = GetWeakVariableByQString(name);
EmitSignals();
}
void GenericVaraiableSpinbox::ToggleTypeClicked(bool useVariable)
void GenericVariableSpinbox::ToggleTypeClicked(bool useVariable)
{
_numberInt._type = useVariable ? NumberVariable<int>::Type::VARIABLE
: NumberVariable<int>::Type::FIXED_VALUE;
@@ -142,7 +144,7 @@ void GenericVaraiableSpinbox::ToggleTypeClicked(bool useVariable)
EmitSignals();
}
void GenericVaraiableSpinbox::EmitSignals()
void GenericVariableSpinbox::EmitSignals()
{
if (_wholeNumber) {
emit FixedValueChanged(_numberInt);
@@ -153,7 +155,7 @@ void GenericVaraiableSpinbox::EmitSignals()
}
}
void GenericVaraiableSpinbox::SetVisibility()
void GenericVariableSpinbox::SetVisibility()
{
if (_wholeNumber) {
_fixedValueDouble->hide();
@@ -164,7 +166,7 @@ void GenericVaraiableSpinbox::SetVisibility()
}
}
void GenericVaraiableSpinbox::SetVisibilityInt()
void GenericVariableSpinbox::SetVisibilityInt()
{
if (_numberInt.IsFixedType()) {
_fixedValueInt->show();
@@ -180,7 +182,7 @@ void GenericVaraiableSpinbox::SetVisibilityInt()
updateGeometry();
}
void GenericVaraiableSpinbox::SetVisibilityDouble()
void GenericVariableSpinbox::SetVisibilityDouble()
{
if (_numberDouble.IsFixedType()) {
_fixedValueDouble->show();
@@ -197,12 +199,12 @@ void GenericVaraiableSpinbox::SetVisibilityDouble()
}
VariableSpinBox::VariableSpinBox(QWidget *parent)
: GenericVaraiableSpinbox(parent, true)
: GenericVariableSpinbox(parent, true)
{
}
VariableDoubleSpinBox::VariableDoubleSpinBox(QWidget *parent)
: GenericVaraiableSpinbox(parent, false)
: GenericVariableSpinbox(parent, false)
{
}

View File

@@ -9,10 +9,10 @@
namespace advss {
class ADVSS_EXPORT GenericVaraiableSpinbox : public QWidget {
class ADVSS_EXPORT GenericVariableSpinbox : public QWidget {
Q_OBJECT
public:
GenericVaraiableSpinbox(QWidget *parent, bool wholeNumber);
GenericVariableSpinbox(QWidget *parent, bool wholeNumber);
void SetValue(const NumberVariable<int> &);
void SetValue(const NumberVariable<double> &);
void DisableVariableSelection();
@@ -59,7 +59,7 @@ private:
bool _hideTypeToggle = false;
};
class ADVSS_EXPORT VariableSpinBox : public GenericVaraiableSpinbox {
class ADVSS_EXPORT VariableSpinBox : public GenericVariableSpinbox {
Q_OBJECT
public:
@@ -68,7 +68,7 @@ public:
QSpinBox *SpinBox() { return _fixedValueInt; }
};
class ADVSS_EXPORT VariableDoubleSpinBox : public GenericVaraiableSpinbox {
class ADVSS_EXPORT VariableDoubleSpinBox : public GenericVariableSpinbox {
Q_OBJECT
public:

View File

@@ -43,8 +43,8 @@ VariableTable *VariableTable::Create()
void VariableTable::Add()
{
auto newVariable = std::make_shared<Variable>();
auto accepted =
VariableSettingsDialog::AskForSettings(this, *newVariable);
auto accepted = VariableSettingsDialog::AskForSettings(
GetSettingsWindow(), *newVariable);
if (!accepted) {
return;
}
@@ -184,7 +184,7 @@ static void openSettingsDialog()
auto oldName = variable->Name();
bool accepted = VariableSettingsDialog::AskForSettings(
tabWidget->Table(), *variable.get());
GetSettingsWindow(), *variable.get());
if (accepted && oldName != variable->Name()) {
VariableSignalManager::Instance()->Rename(
QString::fromStdString(oldName),

View File

@@ -137,7 +137,8 @@ VariableSettingsDialog::VariableSettingsDialog(QWidget *parent,
: ItemSettingsDialog(settings, variables,
"AdvSceneSwitcher.variable.select",
"AdvSceneSwitcher.variable.add",
"AdvSceneSwitcher.item.nameNotAvailable", parent),
"AdvSceneSwitcher.item.nameNotAvailable", true,
parent),
_value(new ResizingPlainTextEdit(this)),
_defaultValue(new ResizingPlainTextEdit(this)),
_save(new QComboBox())

View File

@@ -89,6 +89,8 @@ target_sources(
macro-condition-filter.hpp
macro-condition-folder.cpp
macro-condition-folder.hpp
macro-condition-game-capture.cpp
macro-condition-game-capture.hpp
macro-condition-hotkey.cpp
macro-condition-hotkey.hpp
macro-condition-idle.cpp
@@ -117,6 +119,8 @@ target_sources(
macro-condition-scene-visibility.hpp
macro-condition-scene.cpp
macro-condition-scene.hpp
macro-condition-screenshot.cpp
macro-condition-screenshot.hpp
macro-condition-slideshow.cpp
macro-condition-slideshow.hpp
macro-condition-source.cpp

View File

@@ -215,7 +215,7 @@ MacroActionFilterEdit::MacroActionFilterEdit(
_settingsString(new VariableTextEdit(this)),
_refreshSettingSelection(new QPushButton(
obs_module_text("AdvSceneSwitcher.action.filter.refresh"))),
_settingsButtons(new QComboBox())
_settingsButtons(new SourceSettingsButtonSelection(this))
{
_filters->setSizeAdjustPolicy(QComboBox::AdjustToContents);
@@ -253,8 +253,10 @@ MacroActionFilterEdit::MacroActionFilterEdit(
SLOT(SelectionChanged(const SourceSetting &)));
QWidget::connect(_refreshSettingSelection, SIGNAL(clicked()), this,
SLOT(RefreshVariableSourceSelectionValue()));
QWidget::connect(_settingsButtons, SIGNAL(currentIndexChanged(int)),
this, SLOT(ButtonChanged(int)));
QWidget::connect(_settingsButtons,
SIGNAL(SelectionChanged(const SourceSettingButton &)),
this,
SLOT(ButtonChanged(const SourceSettingButton &)));
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{sources}}", _sources},
@@ -305,25 +307,18 @@ void MacroActionFilterEdit::UpdateEntryData()
const auto filters =
_entryData->_filter.GetFilters(_entryData->_source);
OBSWeakSource firstFilter = filters.empty() ? nullptr : filters.at(0);
_filterSettings->SetSource(firstFilter);
_filterSettings->SetSetting(_entryData->_setting);
_filterSettings->SetSelection(firstFilter, _entryData->_setting);
_settingsInputMethods->setCurrentIndex(_settingsInputMethods->findData(
static_cast<int>(_entryData->_settingsInputMethod)));
_tempVars->SetVariable(_entryData->_tempVar);
_manualSettingValue->setPlainText(_entryData->_manualSettingValue);
PopulateSourceButtonSelection(_settingsButtons, firstFilter);
_settingsButtons->setCurrentText(
QString::fromStdString(_entryData->_button.ToString()));
_settingsButtons->SetSelection(firstFilter, _entryData->_button);
SetWidgetVisibility();
}
void MacroActionFilterEdit::SourceChanged(const SourceSelection &source)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_source = source;
}
@@ -337,11 +332,13 @@ void MacroActionFilterEdit::FilterChanged(const FilterSelection &filter)
auto lock = LockContext();
_entryData->_filter = filter;
}
const auto filters =
_entryData->_filter.GetFilters(_entryData->_source);
OBSWeakSource firstFilter = filters.empty() ? nullptr : filters.at(0);
_filterSettings->SetSource(firstFilter);
PopulateSourceButtonSelection(_settingsButtons, firstFilter);
_settingsButtons->SetSource(firstFilter);
SetWidgetVisibility();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
@@ -349,11 +346,7 @@ void MacroActionFilterEdit::FilterChanged(const FilterSelection &filter)
void MacroActionFilterEdit::ActionChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_action = static_cast<MacroActionFilter::Action>(value);
SetWidgetVisibility();
}
@@ -391,11 +384,7 @@ void MacroActionFilterEdit::GetSettingsClicked()
void MacroActionFilterEdit::SettingsStringChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_settingsString =
_settingsString->toPlainText().toStdString();
@@ -405,11 +394,7 @@ void MacroActionFilterEdit::SettingsStringChanged()
void MacroActionFilterEdit::SettingsInputMethodChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_settingsInputMethod =
static_cast<MacroActionFilter::SettingsInputMethod>(
_settingsInputMethods->itemData(idx).toInt());
@@ -418,31 +403,19 @@ void MacroActionFilterEdit::SettingsInputMethodChanged(int idx)
void MacroActionFilterEdit::SelectionChanged(const TempVariableRef &var)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_tempVar = var;
}
void MacroActionFilterEdit::SelectionChanged(const SourceSetting &setting)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_setting = setting;
}
void MacroActionFilterEdit::ManualSettingsValueChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_manualSettingValue =
_manualSettingValue->toPlainText().toStdString();
@@ -457,15 +430,10 @@ void MacroActionFilterEdit::RefreshVariableSourceSelectionValue()
_filterSettings->SetSource(filters.empty() ? nullptr : filters.at(0));
}
void MacroActionFilterEdit::ButtonChanged(int idx)
void MacroActionFilterEdit::ButtonChanged(const SourceSettingButton &button)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_button = qvariant_cast<SourceSettingButton>(
_settingsButtons->itemData(idx));
GUARD_LOADING_AND_LOCK();
_entryData->_button = button;
}
void MacroActionFilterEdit::SetWidgetVisibility()

View File

@@ -81,7 +81,7 @@ private slots:
void SelectionChanged(const SourceSetting &);
void ManualSettingsValueChanged();
void RefreshVariableSourceSelectionValue();
void ButtonChanged(int idx);
void ButtonChanged(const SourceSettingButton &);
signals:
void HeaderInfoChanged(const QString &);
@@ -100,7 +100,7 @@ private:
SourceSettingSelection *_filterSettings;
VariableTextEdit *_settingsString;
QPushButton *_refreshSettingSelection;
QComboBox *_settingsButtons;
SourceSettingsButtonSelection *_settingsButtons;
std::shared_ptr<MacroActionFilter> _entryData;
bool _loading = true;

View File

@@ -263,28 +263,12 @@ static inline void populateActionSelection(QComboBox *list)
}
}
static inline void populateDeinterlaceModeSelection(QComboBox *list)
template<class T>
static inline void populateModeSelection(QComboBox *list,
const std::map<T, std::string> &modes)
{
list->clear();
for (const auto &[value, name] : deinterlaceModes) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
}
static inline void populateDeinterlaceFieldOrderSelection(QComboBox *list)
{
list->clear();
for (const auto &[value, name] : deinterlaceFieldOrders) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
}
static inline void populateSettingsInputMethods(QComboBox *list)
{
list->clear();
for (const auto &[value, name] : inputMethods) {
for (const auto &[value, name] : modes) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
@@ -295,7 +279,7 @@ MacroActionSourceEdit::MacroActionSourceEdit(
: QWidget(parent),
_sources(new SourceSelectionWidget(this, QStringList(), true)),
_actions(new QComboBox()),
_settingsButtons(new QComboBox()),
_settingsButtons(new SourceSettingsButtonSelection(this)),
_settingsLayout(new QHBoxLayout()),
_settingsInputMethods(new QComboBox(this)),
_manualSettingValue(new VariableTextEdit(this, 5, 1, 1)),
@@ -315,16 +299,18 @@ MacroActionSourceEdit::MacroActionSourceEdit(
auto sources = GetSourceNames();
sources.sort();
_sources->SetSourceNameList(sources);
populateDeinterlaceModeSelection(_deinterlaceMode);
populateDeinterlaceFieldOrderSelection(_deinterlaceOrder);
populateSettingsInputMethods(_settingsInputMethods);
populateModeSelection(_deinterlaceMode, deinterlaceModes);
populateModeSelection(_deinterlaceOrder, deinterlaceFieldOrders);
populateModeSelection(_settingsInputMethods, inputMethods);
_refreshSettingSelection->setToolTip(obs_module_text(
"AdvSceneSwitcher.action.source.refresh.tooltip"));
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_settingsButtons, SIGNAL(currentIndexChanged(int)),
this, SLOT(ButtonChanged(int)));
QWidget::connect(_settingsButtons,
SIGNAL(SelectionChanged(const SourceSettingButton &)),
this,
SLOT(ButtonChanged(const SourceSettingButton &)));
QWidget::connect(_sources,
SIGNAL(SourceChanged(const SourceSelection &)), this,
SLOT(SourceChanged(const SourceSelection &)));
@@ -394,14 +380,11 @@ void MacroActionSourceEdit::UpdateEntryData()
return;
}
PopulateSourceButtonSelection(_settingsButtons,
_entryData->_source.GetSource());
const auto weakSource = _entryData->_source.GetSource();
_settingsButtons->SetSelection(weakSource, _entryData->_button);
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_sources->SetSource(_entryData->_source);
_sourceSettings->SetSource(_entryData->_source.GetSource());
_sourceSettings->SetSetting(_entryData->_setting);
_settingsButtons->setCurrentText(
QString::fromStdString(_entryData->_button.ToString()));
_sourceSettings->SetSelection(weakSource, _entryData->_setting);
_settingsString->setPlainText(_entryData->_settingsString);
_deinterlaceMode->setCurrentIndex(_deinterlaceMode->findData(
static_cast<int>(_entryData->_deinterlaceMode)));
@@ -425,9 +408,10 @@ void MacroActionSourceEdit::SourceChanged(const SourceSelection &source)
auto lock = LockContext();
_entryData->_source = source;
}
PopulateSourceButtonSelection(_settingsButtons,
_entryData->_source.GetSource());
_sourceSettings->SetSource(_entryData->_source.GetSource());
const auto weakSource = _entryData->_source.GetSource();
_sourceSettings->SetSource(weakSource);
_settingsButtons->SetSource(weakSource);
SetWidgetVisibility();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
@@ -435,24 +419,15 @@ void MacroActionSourceEdit::SourceChanged(const SourceSelection &source)
void MacroActionSourceEdit::ActionChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_action = static_cast<MacroActionSource::Action>(value);
SetWidgetVisibility();
}
void MacroActionSourceEdit::ButtonChanged(int idx)
void MacroActionSourceEdit::ButtonChanged(const SourceSettingButton &button)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_button = qvariant_cast<SourceSettingButton>(
_settingsButtons->itemData(idx));
GUARD_LOADING_AND_LOCK();
_entryData->_button = button;
}
void MacroActionSourceEdit::GetSettingsClicked()
@@ -479,11 +454,7 @@ void MacroActionSourceEdit::GetSettingsClicked()
void MacroActionSourceEdit::SettingsStringChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_settingsString =
_settingsString->toPlainText().toStdString();
@@ -493,22 +464,14 @@ void MacroActionSourceEdit::SettingsStringChanged()
void MacroActionSourceEdit::DeinterlaceModeChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_deinterlaceMode = static_cast<obs_deinterlace_mode>(
_deinterlaceMode->itemData(idx).toInt());
}
void MacroActionSourceEdit::DeinterlaceOrderChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_deinterlaceOrder =
static_cast<obs_deinterlace_field_order>(
_deinterlaceOrder->itemData(idx).toInt());
@@ -516,21 +479,13 @@ void MacroActionSourceEdit::DeinterlaceOrderChanged(int idx)
void MacroActionSourceEdit::SelectionChanged(const TempVariableRef &var)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_tempVar = var;
}
void MacroActionSourceEdit::SettingsInputMethodChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_settingsInputMethod =
static_cast<MacroActionSource::SettingsInputMethod>(
_settingsInputMethods->itemData(idx).toInt());
@@ -539,21 +494,13 @@ void MacroActionSourceEdit::SettingsInputMethodChanged(int idx)
void MacroActionSourceEdit::SelectionChanged(const SourceSetting &setting)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_setting = setting;
}
void MacroActionSourceEdit::ManualSettingsValueChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_manualSettingValue =
_manualSettingValue->toPlainText().toStdString();

View File

@@ -80,7 +80,7 @@ public:
private slots:
void SourceChanged(const SourceSelection &);
void ActionChanged(int value);
void ButtonChanged(int idx);
void ButtonChanged(const SourceSettingButton &);
void GetSettingsClicked();
void SettingsStringChanged();
void DeinterlaceModeChanged(int);
@@ -99,7 +99,7 @@ private:
SourceSelectionWidget *_sources;
QComboBox *_actions;
QComboBox *_settingsButtons;
SourceSettingsButtonSelection *_settingsButtons;
QHBoxLayout *_settingsLayout;
QComboBox *_settingsInputMethods;
VariableTextEdit *_manualSettingValue;

View File

@@ -257,7 +257,9 @@ void MacroActionStreamEdit::StringValueChanged()
void MacroActionStreamEdit::ShowPassword()
{
SetButtonIcon(_showPassword, ":res/images/visible.svg");
SetButtonIcon(_showPassword, GetThemeTypeName() == "Light"
? ":res/images/visible.svg"
: "theme:Dark/visible.svg");
_stringValue->setEchoMode(QLineEdit::Normal);
}

View File

@@ -123,27 +123,23 @@ MacroActionSystrayEdit::MacroActionSystrayEdit(
void MacroActionSystrayEdit::TitleChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_title = _title->text().toStdString();
}
void MacroActionSystrayEdit::IconPathChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_iconPath = text.toStdString();
}
void MacroActionSystrayEdit::CheckIfTrayIsDisabled()
{
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(31, 0, 0)
auto config = obs_frontend_get_user_config();
#else
auto config = obs_frontend_get_global_config();
#endif
if (!config) {
return;
}

View File

@@ -176,15 +176,21 @@ bool MacroConditionDate::CheckRegularDate(int64_t msSinceLastCheck)
bool MacroConditionDate::CheckCondition()
{
auto m = GetMacro();
if (!m) {
auto macro = GetMacro();
if (!macro) {
return false;
}
const auto timePassed = std::chrono::high_resolution_clock::now() -
LastMacroConditionCheckTime(m);
const auto now = std::chrono::high_resolution_clock::now();
const auto lastCheck = LastMacroConditionCheckTime(macro);
const auto timePassed = now - lastCheck;
const auto msSinceLastCheck =
std::chrono::duration_cast<std::chrono::milliseconds>(
timePassed);
MacroWasCheckedSinceLastStart(macro)
? std::chrono::duration_cast<std::chrono::milliseconds>(
timePassed)
: std::chrono::milliseconds(0);
if (_dayOfWeekCheck) {
return CheckDayOfWeek(msSinceLastCheck.count());

View File

@@ -420,8 +420,9 @@ void MacroConditionFilterEdit::UpdateEntryData()
_regex->SetRegexConfig(_entryData->_regex);
const auto filters =
_entryData->_filter.GetFilters(_entryData->_source);
_settingSelection->SetSource(filters.empty() ? nullptr : filters.at(0));
_settingSelection->SetSetting(_entryData->_setting);
_settingSelection->SetSelection(filters.empty() ? nullptr
: filters.at(0),
_entryData->_setting);
SetWidgetVisibility();
adjustSize();

View File

@@ -0,0 +1,197 @@
#include "macro-condition-game-capture.hpp"
#include "layout-helpers.hpp"
namespace advss {
const std::string MacroConditionGameCapture::id = "game_capture";
#ifdef _WIN32
bool MacroConditionGameCapture::_registered = MacroConditionFactory::Register(
MacroConditionGameCapture::id,
{MacroConditionGameCapture::Create,
MacroConditionGameCaptureEdit::Create,
"AdvSceneSwitcher.condition.gameCapture"});
#else
bool MacroConditionGameCapture::_registered =
false; // For now the 'game_capture' source only exists on Windows
#endif
bool MacroConditionGameCapture::CheckCondition()
{
auto source = OBSGetStrongRef(_source.GetSource());
if (source != _lastSource) {
SetupSignalHandler(source);
return false;
}
std::lock_guard<std::mutex> lock(_mtx);
if (_hooked) {
SetTempVarValue("title", _title);
SetTempVarValue("class", _class);
SetTempVarValue("executable", _executable);
}
return _hooked;
}
bool MacroConditionGameCapture::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
_source.Save(obj);
return true;
}
bool MacroConditionGameCapture::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_source.Load(obj);
SetupSignalHandler(OBSGetStrongRef(_source.GetSource()));
return true;
}
void MacroConditionGameCapture::GetCalldataInfo(calldata_t *cd)
{
const char *title = "";
if (!calldata_get_string(cd, "title", &title)) {
blog(LOG_WARNING, "%s failed to get title", __func__);
}
_title = title;
const char *className = "";
if (!calldata_get_string(cd, "class", &className)) {
blog(LOG_WARNING, "%s failed to get class", __func__);
}
_class = className;
const char *executable = "";
if (!calldata_get_string(cd, "executable", &executable)) {
blog(LOG_WARNING, "%s failed to get executable", __func__);
}
_executable = executable;
}
void MacroConditionGameCapture::HookedSignalReceived(void *data, calldata_t *cd)
{
auto condition = static_cast<MacroConditionGameCapture *>(data);
std::lock_guard<std::mutex> lock(condition->_mtx);
condition->GetCalldataInfo(cd);
condition->_hooked = true;
}
void MacroConditionGameCapture::UnhookedSignalReceived(void *data, calldata_t *)
{
auto condition = static_cast<MacroConditionGameCapture *>(data);
std::lock_guard<std::mutex> lock(condition->_mtx);
condition->_hooked = false;
}
void MacroConditionGameCapture::SetupTempVars()
{
MacroCondition::SetupTempVars();
AddTempvar(
"title",
obs_module_text("AdvSceneSwitcher.tempVar.gameCapture.title"),
obs_module_text(
"AdvSceneSwitcher.tempVar.gameCapture.title.description"));
AddTempvar(
"class",
obs_module_text("AdvSceneSwitcher.tempVar.gameCapture.class"),
obs_module_text(
"AdvSceneSwitcher.tempVar.gameCapture.class.description"));
AddTempvar(
"executable",
obs_module_text(
"AdvSceneSwitcher.tempVar.gameCapture.executable"),
obs_module_text(
"AdvSceneSwitcher.tempVar.gameCapture.executable.description"));
}
void MacroConditionGameCapture::SetupSignalHandler(obs_source_t *source)
{
signal_handler_t *sh = obs_source_get_signal_handler(source);
_hookSignal = OBSSignal(sh, "hooked", HookedSignalReceived, this);
_unhookSignal = OBSSignal(sh, "unhooked", UnhookedSignalReceived, this);
_lastSource = source;
SetupInitialState(source);
}
void MacroConditionGameCapture::SetupInitialState(obs_source_t *source)
{
std::lock_guard<std::mutex> lock(_mtx);
_hooked = false;
if (!source) {
return;
}
calldata_t cd;
calldata_init(&cd);
auto ph = obs_source_get_proc_handler(source);
if (!proc_handler_call(ph, "get_hooked", &cd)) {
blog(LOG_WARNING,
"%s failed to call proc_handler for 'get_hooked'",
__func__);
return;
}
if (!calldata_get_bool(&cd, "hooked", &_hooked)) {
blog(LOG_WARNING, "%s failed to get hooked state", __func__);
}
GetCalldataInfo(&cd);
calldata_free(&cd);
}
static QStringList getGameCaptureSourceNames()
{
auto sourceEnum = [](void *param, obs_source_t *source) -> bool /* -- */
{
QStringList *list = reinterpret_cast<QStringList *>(param);
if (strcmp(obs_source_get_unversioned_id(source),
"game_capture") == 0) {
*list << obs_source_get_name(source);
}
return true;
};
QStringList list;
obs_enum_sources(sourceEnum, &list);
return list;
}
MacroConditionGameCaptureEdit::MacroConditionGameCaptureEdit(
QWidget *parent, std::shared_ptr<MacroConditionGameCapture> entryData)
: QWidget(parent),
_sources(new SourceSelectionWidget(this, QStringList(), true))
{
auto sources = getGameCaptureSourceNames();
sources.sort();
_sources->SetSourceNameList(sources);
QWidget::connect(_sources,
SIGNAL(SourceChanged(const SourceSelection &)), this,
SLOT(SourceChanged(const SourceSelection &)));
auto layout = new QHBoxLayout;
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.gameCapture.entry"),
layout, {{"{{sources}}", _sources}});
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionGameCaptureEdit::SourceChanged(const SourceSelection &source)
{
GUARD_LOADING_AND_LOCK();
_entryData->_source = source;
}
void MacroConditionGameCaptureEdit::UpdateEntryData()
{
_sources->SetSource(_entryData->_source);
}
} // namespace advss

View File

@@ -0,0 +1,74 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "source-selection.hpp"
#include <QWidget>
#include <QComboBox>
namespace advss {
class MacroConditionGameCapture : public MacroCondition {
public:
MacroConditionGameCapture(Macro *m) : MacroCondition(m) {}
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetId() const { return id; };
static std::shared_ptr<MacroCondition> Create(Macro *m)
{
return std::make_shared<MacroConditionGameCapture>(m);
}
SourceSelection _source;
private:
static void HookedSignalReceived(void *data, calldata_t *);
static void UnhookedSignalReceived(void *data, calldata_t *);
void SetupTempVars();
void SetupSignalHandler(obs_source_t *source);
void SetupInitialState(obs_source_t *source);
void GetCalldataInfo(calldata_t *cd);
obs_source_t *_lastSource = nullptr;
OBSSignal _hookSignal;
OBSSignal _unhookSignal;
bool _hooked = false;
std::string _title = "";
std::string _class = "";
std::string _executable = "";
std::mutex _mtx;
static bool _registered;
static const std::string id;
};
class MacroConditionGameCaptureEdit : public QWidget {
Q_OBJECT
public:
MacroConditionGameCaptureEdit(
QWidget *parent,
std::shared_ptr<MacroConditionGameCapture> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionGameCaptureEdit(
parent,
std::dynamic_pointer_cast<MacroConditionGameCapture>(
cond));
}
private slots:
void SourceChanged(const SourceSelection &);
private:
SourceSelectionWidget *_sources;
std::shared_ptr<MacroConditionGameCapture> _entryData;
bool _loading = true;
};
} // namespace advss

View File

@@ -548,14 +548,14 @@ void MacroConditionMedia::MediaNext(void *data, calldata_t *)
void MacroConditionMedia::SetSourceType(SourceType t)
{
SetupTempVars();
_sourceType = t;
SetupTempVars();
}
void MacroConditionMedia::SetCheckType(CheckType t)
{
SetupTempVars();
_checkType = t;
SetupTempVars();
}
static void populateSateSelection(QComboBox *list, bool addLegacyEntries)

View File

@@ -0,0 +1,94 @@
#include "macro-condition-screenshot.hpp"
#include "layout-helpers.hpp"
#include <obs-frontend-api.h>
#include <thread>
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(29, 0, 0)
namespace advss {
const std::string MacroConditionScreenshot::id = "screenshot";
bool MacroConditionScreenshot::_registered = MacroConditionFactory::Register(
MacroConditionScreenshot::id,
{MacroConditionScreenshot::Create, MacroConditionScreenshotEdit::Create,
"AdvSceneSwitcher.condition.screenshot"});
static std::chrono::high_resolution_clock::time_point screenshotTakenTime{};
static bool setupScreenshotTakenEventHandler();
static bool setupDone = setupScreenshotTakenEventHandler();
static bool setupScreenshotTakenEventHandler()
{
static auto handleScreenshotEvent = [](enum obs_frontend_event event,
void *) {
switch (event) {
case OBS_FRONTEND_EVENT_SCREENSHOT_TAKEN:
screenshotTakenTime =
std::chrono::high_resolution_clock::now();
break;
default:
break;
};
};
obs_frontend_add_event_callback(handleScreenshotEvent, nullptr);
return true;
}
bool MacroConditionScreenshot::CheckCondition()
{
if (!_screenshotTimeInitialized) {
_screenshotTime = screenshotTakenTime;
_screenshotTimeInitialized = true;
return false;
}
auto lastScreenshotPath = obs_frontend_get_last_screenshot();
SetTempVarValue("lastScreenshotPath", lastScreenshotPath);
bfree(lastScreenshotPath);
bool newSaveOccurred = _screenshotTime != screenshotTakenTime;
_screenshotTime = screenshotTakenTime;
return newSaveOccurred;
}
bool MacroConditionScreenshot::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
return true;
}
bool MacroConditionScreenshot::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
return true;
}
void MacroConditionScreenshot::SetupTempVars()
{
MacroCondition::SetupTempVars();
AddTempvar(
"lastScreenshotPath",
obs_module_text(
"AdvSceneSwitcher.tempVar.screenshot.lastScreenshotPath"),
obs_module_text(
"AdvSceneSwitcher.tempVar.screenshot.lastScreenshotPath.description"));
}
MacroConditionScreenshotEdit::MacroConditionScreenshotEdit(
QWidget *parent, std::shared_ptr<MacroConditionScreenshot> entryData)
: QWidget(parent)
{
auto layout = new QHBoxLayout;
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.screenshot.entry"),
layout, {});
setLayout(layout);
_entryData = entryData;
}
} // namespace advss
#endif

View File

@@ -0,0 +1,55 @@
#pragma once
#include "macro-condition-edit.hpp"
#include <obs-config.h>
#include <QWidget>
#include <QComboBox>
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(29, 0, 0)
namespace advss {
class MacroConditionScreenshot : public MacroCondition {
public:
MacroConditionScreenshot(Macro *m) : MacroCondition(m) {}
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetId() const { return id; };
static std::shared_ptr<MacroCondition> Create(Macro *m)
{
return std::make_shared<MacroConditionScreenshot>(m);
}
private:
void SetupTempVars();
bool _screenshotTimeInitialized = false;
std::chrono::high_resolution_clock::time_point _screenshotTime = {};
static bool _registered;
static const std::string id;
};
class MacroConditionScreenshotEdit : public QWidget {
Q_OBJECT
public:
MacroConditionScreenshotEdit(
QWidget *parent,
std::shared_ptr<MacroConditionScreenshot> cond = nullptr);
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionScreenshotEdit(
parent,
std::dynamic_pointer_cast<MacroConditionScreenshot>(
cond));
}
private:
std::shared_ptr<MacroConditionScreenshot> _entryData;
};
} // namespace advss
#endif

View File

@@ -483,8 +483,8 @@ void MacroConditionSourceEdit::UpdateEntryData()
static_cast<int>(_entryData->GetCondition()));
_settings->setPlainText(_entryData->_settings);
_regex->SetRegexConfig(_entryData->_regex);
_settingSelection->SetSource(_entryData->_source.GetSource());
_settingSelection->SetSetting(_entryData->_setting);
_settingSelection->SetSelection(_entryData->_source.GetSource(),
_entryData->_setting);
_size->SetValue(_entryData->_size);
_sizeCompareMethods->setCurrentIndex(
static_cast<int>(_entryData->_comparision));

View File

@@ -111,27 +111,42 @@ bool MacroConditionTransition::CheckCondition()
return ret;
}
void MacroConditionTransition::ConnectToTransitionSignals()
void advss::MacroConditionTransition::AddTransitionSignals(
obs_source_t *transition)
{
const bool useFrontendTransitionSelection =
_transition.GetType() !=
TransitionSelection::Type::TRANSITION ||
(_condition == Condition::TRANSITION_SOURCE ||
_condition == Condition::TRANSITION_TARGET);
_signals.clear();
OBSSourceAutoRelease source =
useFrontendTransitionSelection
? obs_frontend_get_current_transition()
: obs_weak_source_get_source(
_transition.GetTransition());
signal_handler_t *sh = obs_source_get_signal_handler(source);
signal_handler_t *sh = obs_source_get_signal_handler(transition);
_signals.emplace_back(sh, "transition_start", TransitionStarted, this);
_signals.emplace_back(sh, "transition_stop", TransitionEnded, this);
_signals.emplace_back(sh, "transition_video_stop", TransitionVideoEnded,
this);
}
void MacroConditionTransition::ConnectToTransitionSignals()
{
_signals.clear();
const bool watchSingleTransitionType =
_transition.GetType() ==
TransitionSelection::Type::TRANSITION &&
!(_condition == Condition::TRANSITION_SOURCE ||
_condition == Condition::TRANSITION_TARGET);
if (watchSingleTransitionType) {
OBSSourceAutoRelease transition =
OBSGetStrongRef(_transition.GetTransition());
AddTransitionSignals(transition);
return;
}
obs_frontend_source_list transitions = {};
obs_frontend_get_transitions(&transitions);
for (size_t i = 0; i < transitions.sources.num; i++) {
obs_source_t *transition = transitions.sources.array[i];
AddTransitionSignals(transition);
}
obs_frontend_source_list_free(&transitions);
}
void MacroConditionTransition::TransitionStarted(void *data, calldata_t *cd)
{
auto *condition = static_cast<MacroConditionTransition *>(data);

View File

@@ -42,6 +42,7 @@ private:
static void TransitionEnded(void *data, calldata_t *);
static void TransitionVideoEnded(void *data, calldata_t *);
static void HandleFrontendEvent(enum obs_frontend_event event, void *);
void AddTransitionSignals(obs_source_t *);
std::vector<OBSSignal> _signals;

View File

@@ -332,7 +332,8 @@ WSConnectionSettingsDialog::WSConnectionSettingsDialog(
: ItemSettingsDialog(settings, connections,
"AdvSceneSwitcher.connection.select",
"AdvSceneSwitcher.connection.add",
"AdvSceneSwitcher.item.nameNotAvailable", parent),
"AdvSceneSwitcher.item.nameNotAvailable", true,
parent),
_useCustomURI(new QCheckBox()),
_customUri(new QLineEdit()),
_address(new QLineEdit()),
@@ -502,7 +503,9 @@ void WSConnectionSettingsDialog::SetStatus()
void WSConnectionSettingsDialog::ShowPassword()
{
SetButtonIcon(_showPassword, ":res/images/visible.svg");
SetButtonIcon(_showPassword, GetThemeTypeName() == "Light"
? ":res/images/visible.svg"
: "theme:Dark/visible.svg");
_password->setEchoMode(QLineEdit::Normal);
}

View File

@@ -698,7 +698,10 @@ SceneItemSelectionWidget::SceneItemSelectionWidget(QWidget *parent,
_nameConflictIndex->setSizeAdjustPolicy(QComboBox::AdjustToContents);
_changeType->setMaximumWidth(22);
SetButtonIcon(_changeType, ":/settings/images/settings/general.svg");
SetButtonIcon(_changeType,
GetThemeTypeName() == "Light"
? ":/settings/images/settings/general.svg"
: "theme:Dark/settings/general.svg");
_changeType->setFlat(true);
_changeType->setToolTip(obs_module_text(
"AdvSceneSwitcher.sceneItemSelection.configure"));

View File

@@ -29,20 +29,37 @@ std::string SourceSettingButton::ToString() const
return "[" + id + "] " + description;
}
std::vector<SourceSettingButton> GetSourceButtons(OBSWeakSource source)
static void getSourceButtonsHelper(obs_properties_t *sourceProperties,
std::vector<SourceSettingButton> &buttons)
{
OBSSourceAutoRelease s = obs_weak_source_get_source(source);
std::vector<SourceSettingButton> buttons;
obs_properties_t *sourceProperties = obs_source_properties(s);
auto it = obs_properties_first(sourceProperties);
do {
if (!it || obs_property_get_type(it) != OBS_PROPERTY_BUTTON) {
if (!it) {
continue;
}
auto type = obs_property_get_type(it);
if (type == OBS_PROPERTY_GROUP) {
auto group = obs_property_group_content(it);
getSourceButtonsHelper(group, buttons);
continue;
}
if (obs_property_get_type(it) != OBS_PROPERTY_BUTTON) {
continue;
}
SourceSettingButton button = {obs_property_name(it),
obs_property_description(it)};
buttons.emplace_back(button);
} while (obs_property_next(&it));
}
std::vector<SourceSettingButton> GetSourceButtons(OBSWeakSource source)
{
OBSSourceAutoRelease s = obs_weak_source_get_source(source);
std::vector<SourceSettingButton> buttons;
auto sourceProperties = obs_source_properties(s);
getSourceButtonsHelper(sourceProperties, buttons);
obs_properties_destroy(sourceProperties);
return buttons;
}
@@ -59,19 +76,46 @@ void PressSourceButton(const SourceSettingButton &button, obs_source_t *source)
obs_properties_destroy(sourceProperties);
}
void PopulateSourceButtonSelection(QComboBox *list, OBSWeakSource source)
SourceSettingsButtonSelection::SourceSettingsButtonSelection(QWidget *parent)
: FilterComboBox(parent)
{
list->clear();
connect(this, &FilterComboBox::currentIndexChanged, [this](int idx) {
emit SelectionChanged(
qvariant_cast<SourceSettingButton>(itemData(idx)));
});
}
void SourceSettingsButtonSelection::SetSource(const OBSWeakSource &source,
bool restorePreviousSelection)
{
const auto previousSelection = currentText();
PopulateSelection(source);
if (restorePreviousSelection) {
setCurrentIndex(findText(previousSelection));
}
}
void SourceSettingsButtonSelection::SetSelection(
const OBSWeakSource &source, const SourceSettingButton &button)
{
SetSource(source, false);
setCurrentText(QString::fromStdString(button.ToString()));
}
void SourceSettingsButtonSelection::PopulateSelection(
const OBSWeakSource &source)
{
const QSignalBlocker b(this);
clear();
auto buttons = GetSourceButtons(source);
if (buttons.empty()) {
list->addItem(
obs_module_text("AdvSceneSwitcher.noSettingsButtons"));
addItem(obs_module_text("AdvSceneSwitcher.noSettingsButtons"));
}
for (const auto &button : buttons) {
QVariant value;
value.setValue(button);
list->addItem(QString::fromStdString(button.ToString()), value);
addItem(QString::fromStdString(button.ToString()), value);
}
}

View File

@@ -1,12 +1,16 @@
#pragma once
#include "filter-combo-box.hpp"
#include <obs.hpp>
#include <QComboBox>
#include <qmetatype.h>
#include <string>
#include <vector>
namespace advss {
class SourceSelection;
class FilterSelection;
struct SourceSettingButton {
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
@@ -18,7 +22,23 @@ struct SourceSettingButton {
std::vector<SourceSettingButton> GetSourceButtons(OBSWeakSource source);
void PressSourceButton(const SourceSettingButton &button, obs_source_t *source);
void PopulateSourceButtonSelection(QComboBox *list, OBSWeakSource source);
class SourceSettingsButtonSelection : public FilterComboBox {
Q_OBJECT
public:
SourceSettingsButtonSelection(QWidget *parent = nullptr);
void SetSource(const OBSWeakSource &source,
bool restorePreviousSelection = true);
void SetSelection(const OBSWeakSource &source,
const SourceSettingButton &button);
signals:
void SelectionChanged(const SourceSettingButton &);
private:
void PopulateSelection(const OBSWeakSource &source);
};
} // namespace advss

View File

@@ -249,14 +249,21 @@ SourceSettingSelection::SourceSettingSelection(QWidget *parent)
setLayout(layout);
}
void SourceSettingSelection::SetSource(const OBSWeakSource &source)
void SourceSettingSelection::SetSource(const OBSWeakSource &source,
bool restorePreviousSelection)
{
_settings->clear();
const auto previousSelection = _settings->currentData();
Populate(source);
if (restorePreviousSelection) {
_settings->setCurrentIndex(
_settings->findData(previousSelection));
}
}
void SourceSettingSelection::SetSetting(const SourceSetting &setting)
void SourceSettingSelection::SetSelection(const OBSWeakSource &source,
const SourceSetting &setting)
{
SetSource(source, false);
QVariant variant;
variant.setValue(setting);
_settings->setCurrentIndex(_settings->findData(variant));
@@ -281,6 +288,7 @@ void SourceSettingSelection::SelectionIdxChanged(int idx)
void SourceSettingSelection::Populate(const OBSWeakSource &source)
{
_settings->clear();
OBSSourceAutoRelease s = obs_weak_source_get_source(source);
auto settings = GetSoruceSettings(s);
for (const auto &setting : settings) {

View File

@@ -40,8 +40,9 @@ class SourceSettingSelection : public QWidget {
public:
SourceSettingSelection(QWidget *parent = nullptr);
void SetSource(const OBSWeakSource &);
void SetSetting(const SourceSetting &);
void SetSource(const OBSWeakSource &,
bool restorePreviousSelection = true);
void SetSelection(const OBSWeakSource &source, const SourceSetting &);
private slots:
void SelectionIdxChanged(int);

View File

@@ -40,7 +40,7 @@ void WSConnectionsTable::Add()
{
auto newConnection = std::make_shared<WSConnection>();
auto accepted = WSConnectionSettingsDialog::AskForSettings(
this, *newConnection);
GetSettingsWindow(), *newConnection);
if (!accepted) {
return;
}
@@ -143,7 +143,7 @@ static void openSettingsDialog()
auto oldName = connection->Name();
bool accepted = WSConnectionSettingsDialog::AskForSettings(
tabWidget->Table(), *connection.get());
GetSettingsWindow(), *connection.get());
if (accepted && oldName != connection->Name()) {
ConnectionSelectionSignalManager::Instance()->Rename(
QString::fromStdString(oldName),

View File

@@ -139,7 +139,7 @@ void TwitchCategorySelection::SelectionChanged(int index)
{
TwitchCategory category{itemData(index).toInt(),
currentText().toStdString()};
emit CategoreyChanged(category);
emit CategoryChanged(category);
}
std::map<QString, int> CategoryGrabber::_categoryMap = {};
@@ -193,7 +193,7 @@ void CategoryGrabber::Search(const std::string &)
httplib::Params params = {{"first", "100"},
{"after", cursor},
{"query", _searchString}};
auto response = SendGetRequest(*_token, uri, path, params);
auto response = SendGetRequest(*_token, uri, path, params, true);
while (response.status == 200 && !_stop) {
cursor = ParseReply(response.data);
@@ -203,7 +203,7 @@ void CategoryGrabber::Search(const std::string &)
params = {{"first", "100"},
{"after", cursor},
{"query", _searchString}};
response = SendGetRequest(*_token, uri, path, params);
response = SendGetRequest(*_token, uri, path, params, true);
emit CategoryCountUpdated(_categoryMap.size() - startCount);
}
}
@@ -217,7 +217,7 @@ void CategoryGrabber::GetAll()
static std::string cursor;
httplib::Params params = {{"first", "100"}, {"after", cursor}};
auto response = SendGetRequest(*_token, uri, path, params);
auto response = SendGetRequest(*_token, uri, path, params, true);
while (response.status == 200 && !_stop) {
cursor = ParseReply(response.data);
@@ -225,7 +225,7 @@ void CategoryGrabber::GetAll()
break; // End of category list
}
params = {{"first", "100"}, {"after", cursor}};
response = SendGetRequest(*_token, uri, path, params);
response = SendGetRequest(*_token, uri, path, params, true);
emit CategoryCountUpdated(_categoryMap.size());
}
}
@@ -364,8 +364,8 @@ TwitchCategoryWidget::TwitchCategoryWidget(QWidget *parent)
_manualSearch(new TwitchCategorySearchButton(this))
{
QWidget::connect(_selection,
SIGNAL(CategoreyChanged(const TwitchCategory &)), this,
SIGNAL(CategoreyChanged(const TwitchCategory &)));
SIGNAL(CategoryChanged(const TwitchCategory &)), this,
SIGNAL(CategoryChanged(const TwitchCategory &)));
auto layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);

View File

@@ -78,7 +78,7 @@ private slots:
void UpdateCategoryList();
signals:
void CategoreyChanged(const TwitchCategory &);
void CategoryChanged(const TwitchCategory &);
protected:
void showPopup() override;
@@ -125,7 +125,7 @@ public:
void SetToken(const std::weak_ptr<TwitchToken> &);
signals:
void CategoreyChanged(const TwitchCategory &);
void CategoryChanged(const TwitchCategory &);
private:
TwitchCategorySelection *_selection;

View File

@@ -1,4 +1,5 @@
#include "channel-selection.hpp"
#include "log-helper.hpp"
#include "twitch-helpers.hpp"
#include "token.hpp"
@@ -36,7 +37,7 @@ std::string TwitchChannel::GetUserID(const TwitchToken &token) const
}
auto res = SendGetRequest(token, "https://api.twitch.tv",
"/helix/users", {{"login", _name}});
"/helix/users", {{"login", _name}}, true);
if (res.status == 400) {
userIDCache[_name] = "invalid";

View File

@@ -196,12 +196,12 @@ std::string EventSub::AddEventSubscribtion(std::shared_ptr<TwitchToken> token,
return "";
}
std::lock_guard<std::mutex> lock(eventSub->_subscriptionMtx);
std::unique_lock<std::mutex> lock(eventSub->_subscriptionMtx);
if (!eventSub->_connected) {
std::thread t([eventSub]() { eventSub->Connect(); });
t.detach();
vblog(LOG_INFO, "Twitch EventSub connect started for %s",
token->GetName().c_str());
lock.unlock();
eventSub->Connect();
return "";
}

View File

@@ -3,6 +3,7 @@
#include <log-helper.hpp>
#include <layout-helpers.hpp>
#include <ui-helpers.hpp>
namespace advss {
@@ -21,6 +22,18 @@ void MacroActionTwitch::ResolveVariablesToFixedValues()
_announcementMessage.ResolveVariables();
_channel.ResolveVariables();
_chatMessage.ResolveVariables();
_userLogin.ResolveVariables();
_userId.ResolveVariables();
_useVariableForRewardSelection = false;
auto token = _token.lock();
if (token) {
ResolveVariableSelectionToRewardId(token);
_pointsReward.title = _lastResolvedRewardTitle;
_pointsReward.id = _lastResolvedRewardId;
} else {
_pointsReward.id = "";
_pointsReward.id = "";
}
}
std::shared_ptr<MacroAction> MacroActionTwitch::Create(Macro *m)
@@ -58,7 +71,11 @@ const static std::map<MacroActionTwitch::Action, std::string> actionTypes = {
{MacroActionTwitch::Action::CHAT_EMOTE_ONLY_DISABLE,
"AdvSceneSwitcher.action.twitch.type.chat.emoteOnly.disable"},
{MacroActionTwitch::Action::SEND_CHAT_MESSAGE,
"AdvSceneSwitcher.action.twitch.type.sendChatMessage"},
"AdvSceneSwitcher.action.twitch.type.chat.sendMessage"},
{MacroActionTwitch::Action::USER_GET_INFO,
"AdvSceneSwitcher.action.twitch.type.user.getInfo"},
{MacroActionTwitch::Action::POINTS_REWARD_GET_INFO,
"AdvSceneSwitcher.action.twitch.type.reward.getInfo"},
};
const static std::map<MacroActionTwitch::AnnouncementColor, std::string>
@@ -264,6 +281,228 @@ void MacroActionTwitch::SendChatMessage(
_chatConnection->SendChatMessage(_chatMessage);
}
void MacroActionTwitch::GetUserInfo(const std::shared_ptr<TwitchToken> &token)
{
httplib::Params params;
switch (_userInfoQueryType) {
case UserInfoQueryType::ID:
params.insert({"id", std::to_string(_userId)});
break;
case UserInfoQueryType::LOGIN:
params.insert({"login", _userLogin});
break;
default:
break;
}
auto result = SendGetRequest(*token, "https://api.twitch.tv",
"/helix/users", params, true);
if (result.status != 200) {
blog(LOG_INFO, "Failed get user info! (%d)\n", result.status);
return;
}
OBSDataArrayAutoRelease array = obs_data_get_array(result.data, "data");
size_t count = obs_data_array_count(array);
if (count == 0) {
blog(LOG_WARNING, "%s did not return any data!", __func__);
return;
}
OBSDataAutoRelease data = obs_data_array_item(array, 0);
SetJsonTempVars(data, [this](const char *id, const char *value) {
SetTempVarValue(id, value);
});
}
bool MacroActionTwitch::ResolveVariableSelectionToRewardId(
const std::shared_ptr<TwitchToken> &token)
{
if (!token) {
return false;
}
auto variable = _rewardVariable.lock();
if (!variable) {
return false;
}
const auto rewardTitle = variable->Value();
if (rewardTitle.empty()) {
return false;
}
if (_lastResolvedRewardTitle == rewardTitle) {
return true;
}
const auto rewards = GetPointsRewardsForChannel(token, _channel);
if (!rewards) {
return false;
}
for (const auto &reward : *rewards) {
if (reward.title == rewardTitle) {
_lastResolvedRewardId = reward.id;
_lastResolvedRewardTitle = reward.title;
return true;
}
}
return false;
}
void MacroActionTwitch::GetRewardInfo(const std::shared_ptr<TwitchToken> &token)
{
if (_useVariableForRewardSelection &&
!ResolveVariableSelectionToRewardId(token)) {
if (VerboseLoggingEnabled()) {
auto variable = _rewardVariable.lock();
if (!variable) {
blog(LOG_WARNING,
"failed to resolve variable reward name to reward id (invalid selection)");
return;
}
blog(LOG_WARNING,
"failed to resolve variable reward name '%s' to reward id",
variable->Value().c_str());
return;
}
return;
}
httplib::Params params = {
{"broadcaster_id", token->GetUserID()},
{"id", _useVariableForRewardSelection ? _lastResolvedRewardId
: _pointsReward.id},
};
auto result = SendGetRequest(*token, "https://api.twitch.tv",
"/helix/channel_points/custom_rewards",
params, true);
if (result.status != 200) {
blog(LOG_INFO, "Failed get reward info! (%d)\n", result.status);
return;
}
OBSDataArrayAutoRelease array = obs_data_get_array(result.data, "data");
size_t count = obs_data_array_count(array);
if (count == 0) {
blog(LOG_WARNING, "%s did not return any data!", __func__);
return;
}
OBSDataAutoRelease data = obs_data_array_item(array, 0);
SetJsonTempVars(data, [this](const char *id, const char *value) {
SetTempVarValue(id, value);
});
OBSDataAutoRelease image = obs_data_get_obj(data, "image");
SetTempVarValue("image.url_4x", obs_data_get_string(image, "url_4x"));
OBSDataAutoRelease default_image =
obs_data_get_obj(data, "default_image");
SetTempVarValue("default_image.url_4x",
obs_data_get_string(default_image, "url_4x"));
OBSDataAutoRelease max_per_stream_setting =
obs_data_get_obj(data, "max_per_stream_setting");
SetTempVarValue("max_per_stream.is_enabled",
obs_data_get_bool(max_per_stream_setting,
"is_enabled"));
SetTempVarValue("max_per_stream.max_per_stream",
std::to_string(obs_data_get_int(max_per_stream_setting,
"max_per_stream")));
OBSDataAutoRelease max_per_user_per_stream_setting =
obs_data_get_obj(data, "max_per_user_per_stream_setting");
SetTempVarValue("max_per_user_per_stream.is_enabled",
obs_data_get_bool(max_per_user_per_stream_setting,
"is_enabled"));
SetTempVarValue(
"max_per_user_per_stream.max_per_user_per_stream",
std::to_string(obs_data_get_int(max_per_user_per_stream_setting,
"max_per_user_per_stream")));
OBSDataAutoRelease global_cooldown_setting =
obs_data_get_obj(data, "global_cooldown_setting");
SetTempVarValue("global_cooldown.is_enabled",
obs_data_get_bool(global_cooldown_setting,
"is_enabled"));
SetTempVarValue(
"max_per_user_per_stream.global_cooldown_seconds",
std::to_string(obs_data_get_int(max_per_user_per_stream_setting,
"global_cooldown_seconds")));
}
static std::string tryTranslate(const std::string &testString)
{
auto translated = obs_module_text(testString.c_str());
if (testString != translated) {
return translated;
}
return "";
}
void MacroActionTwitch::SetupTempVars()
{
MacroAction::SetupTempVars();
auto setupTempVarHelper = [&](const std::string &id,
const std::string &extra = "") {
std::string name = tryTranslate(
"AdvSceneSwitcher.tempVar.twitch." + id + extra);
std::string description =
tryTranslate("AdvSceneSwitcher.tempVar.twitch." + id +
extra + ".description");
AddTempvar(id, name.empty() ? id : name, description);
};
switch (_action) {
case Action::POINTS_REWARD_GET_INFO:
setupTempVarHelper("title", ".reward");
setupTempVarHelper("prompt", ".reward");
setupTempVarHelper("cost", ".reward");
setupTempVarHelper("background_color", ".reward");
setupTempVarHelper("is_enabled", ".reward");
setupTempVarHelper("is_user_input_required", ".reward");
setupTempVarHelper("is_paused", ".reward");
setupTempVarHelper("is_in_stock", ".reward");
setupTempVarHelper("should_redemptions_skip_request_queue",
".reward");
setupTempVarHelper("redemptions_redeemed_current_stream",
".reward");
setupTempVarHelper("cooldown_expires_at", ".reward");
setupTempVarHelper("max_per_stream.is_enabled", ".reward");
setupTempVarHelper("max_per_stream.max_per_stream", ".reward");
setupTempVarHelper("max_per_user_per_stream.is_enabled",
".reward");
setupTempVarHelper(
"max_per_user_per_stream.max_per_user_per_stream",
".reward");
setupTempVarHelper("global_cooldown.is_enabled", ".reward");
setupTempVarHelper("global_cooldown.global_cooldown_seconds",
".reward");
setupTempVarHelper("image.url_4x", ".reward");
setupTempVarHelper("default_image.url_4x", ".reward");
break;
case Action::USER_GET_INFO:
setupTempVarHelper("id", ".user.getInfo");
setupTempVarHelper("login", ".user.getInfo");
setupTempVarHelper("display_name", ".user.getInfo");
setupTempVarHelper("type", ".user.getInfo");
setupTempVarHelper("broadcaster_type", ".user.getInfo");
setupTempVarHelper("description", ".user.getInfo");
setupTempVarHelper("profile_image_url", ".user.getInfo");
setupTempVarHelper("offline_image_url", ".user.getInfo");
setupTempVarHelper("created_at", ".user.getInfo");
break;
default:
break;
}
}
bool MacroActionTwitch::PerformAction()
{
auto token = _token.lock();
@@ -298,9 +537,16 @@ bool MacroActionTwitch::PerformAction()
break;
case Action::CHAT_EMOTE_ONLY_DISABLE:
SetChatEmoteOnlyMode(token, false);
break;
case MacroActionTwitch::Action::SEND_CHAT_MESSAGE:
SendChatMessage(token);
break;
case MacroActionTwitch::Action::USER_GET_INFO:
GetUserInfo(token);
break;
case MacroActionTwitch::Action::POINTS_REWARD_GET_INFO:
GetRewardInfo(token);
break;
default:
break;
}
@@ -338,6 +584,15 @@ bool MacroActionTwitch::Save(obs_data_t *obj) const
static_cast<int>(_announcementColor));
_channel.Save(obj);
_chatMessage.Save(obj, "chatMessage");
obs_data_set_int(obj, "userInfoQueryType",
static_cast<int>(_userInfoQueryType));
_userLogin.Save(obj, "userLogin");
_userId.Save(obj, "userId");
_pointsReward.Save(obj);
obs_data_set_string(obj, "rewardVariable",
GetWeakVariableName(_rewardVariable).c_str());
obs_data_set_bool(obj, "useVariableForRewardSelection",
_useVariableForRewardSelection);
return true;
}
@@ -357,6 +612,16 @@ bool MacroActionTwitch::Load(obs_data_t *obj)
obs_data_get_int(obj, "announcementColor"));
_channel.Load(obj);
_chatMessage.Load(obj, "chatMessage");
_userInfoQueryType = static_cast<UserInfoQueryType>(
obs_data_get_int(obj, "userInfoQueryType"));
_userLogin.Load(obj, "userLogin");
_userId.Load(obj, "userId");
_pointsReward.Load(obj);
_rewardVariable = GetWeakVariableByName(
obs_data_get_string(obj, "rewardVariable"));
_useVariableForRewardSelection =
obs_data_get_bool(obj, "useVariableForRewardSelection");
SetAction(static_cast<Action>(obs_data_get_int(obj, "action")));
return true;
@@ -366,117 +631,139 @@ void MacroActionTwitch::SetAction(Action action)
{
_action = action;
ResetChatConnection();
SetupTempVars();
}
bool MacroActionTwitch::ActionIsSupportedByToken()
{
static const std::unordered_map<Action, TokenOption> requiredOption = {
{Action::CHANNEL_INFO_TITLE_SET, {"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_CATEGORY_SET,
{"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_LANGUAGE_SET,
{"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_DELAY_SET, {"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_BRANDED_CONTENT_ENABLE,
{"channel:manage:broadcast"}},
{Action::CHANNEL_INFO_BRANDED_CONTENT_DISABLE,
{"channel:manage:broadcast"}},
{Action::RAID_START, {"channel:manage:raids"}},
{Action::RAID_END, {"channel:manage:raids"}},
{Action::SHOUTOUT_SEND, {"moderator:manage:shoutouts"}},
{Action::POLL_END, {"channel:manage:polls"}},
{Action::PREDICTION_END, {"channel:manage:predictions"}},
{Action::SHIELD_MODE_START, {"moderator:manage:shield_mode"}},
{Action::SHIELD_MODE_END, {"moderator:manage:shield_mode"}},
{Action::POINTS_REWARD_ENABLE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_DISABLE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_PAUSE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_UNPAUSE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_TITLE_SET,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_PROMPT_SET,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_COST_SET,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_USER_INPUT_REQUIRE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_USER_INPUT_UNREQUIRE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_COOLDOWN_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_COOLDOWN_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_QUEUE_SKIP_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_QUEUE_SKIP_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_STREAM_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_STREAM_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_USER_ENABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_MAX_PER_USER_DISABLE,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_DELETE, {"channel:manage:redemptions"}},
{Action::POINTS_REWARD_REDEMPTION_CANCEL,
{"channel:manage:redemptions"}},
{Action::POINTS_REWARD_REDEMPTION_FULFILL,
{"channel:manage:redemptions"}},
{Action::USER_BAN, {"moderator:manage:banned_users"}},
{Action::USER_UNBAN, {"moderator:manage:banned_users"}},
{Action::USER_BLOCK, {"user:manage:blocked_users"}},
{Action::USER_UNBLOCK, {"user:manage:blocked_users"}},
{Action::USER_MODERATOR_ADD, {"channel:manage:moderators"}},
{Action::USER_MODERATOR_DELETE, {"channel:manage:moderators"}},
{Action::USER_VIP_ADD, {"channel:manage:vips"}},
{Action::USER_VIP_DELETE, {"channel:manage:vips"}},
{Action::COMMERCIAL_START, {"channel:edit:commercial"}},
{Action::COMMERCIAL_SNOOZE, {"channel:manage:ads"}},
{Action::MARKER_CREATE, {"channel:manage:broadcast"}},
{Action::CLIP_CREATE, {"clips:edit"}},
{Action::CHAT_ANNOUNCEMENT_SEND,
{"moderator:manage:announcements"}},
{Action::CHAT_EMOTE_ONLY_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_EMOTE_ONLY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_FOLLOWER_ONLY_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_FOLLOWER_ONLY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SUBSCRIBER_ONLY_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SUBSCRIBER_ONLY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SLOW_MODE_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_SLOW_MODE_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_NON_MODERATOR_DELAY_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_NON_MODERATOR_DELAY_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_UNIQUE_MODE_ENABLE,
{"moderator:manage:chat_settings"}},
{Action::CHAT_UNIQUE_MODE_DISABLE,
{"moderator:manage:chat_settings"}},
{Action::WHISPER_SEND, {"user:manage:whispers"}},
{Action::SEND_CHAT_MESSAGE, {"chat:edit"}},
};
static const std::unordered_map<Action, std::vector<TokenOption>>
requiredOption = {
{Action::CHANNEL_INFO_TITLE_SET,
{{"channel:manage:broadcast"}}},
{Action::CHANNEL_INFO_CATEGORY_SET,
{{"channel:manage:broadcast"}}},
{Action::CHANNEL_INFO_LANGUAGE_SET,
{{"channel:manage:broadcast"}}},
{Action::CHANNEL_INFO_DELAY_SET,
{{"channel:manage:broadcast"}}},
{Action::CHANNEL_INFO_BRANDED_CONTENT_ENABLE,
{{"channel:manage:broadcast"}}},
{Action::CHANNEL_INFO_BRANDED_CONTENT_DISABLE,
{{"channel:manage:broadcast"}}},
{Action::RAID_START, {{"channel:manage:raids"}}},
{Action::RAID_END, {{"channel:manage:raids"}}},
{Action::SHOUTOUT_SEND,
{{"moderator:manage:shoutouts"}}},
{Action::POLL_END, {{"channel:manage:polls"}}},
{Action::PREDICTION_END,
{{"channel:manage:predictions"}}},
{Action::SHIELD_MODE_START,
{{"moderator:manage:shield_mode"}}},
{Action::SHIELD_MODE_END,
{{"moderator:manage:shield_mode"}}},
{Action::POINTS_REWARD_ENABLE,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_DISABLE,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_PAUSE,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_UNPAUSE,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_TITLE_SET,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_PROMPT_SET,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_COST_SET,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_USER_INPUT_REQUIRE,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_USER_INPUT_UNREQUIRE,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_COOLDOWN_ENABLE,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_COOLDOWN_DISABLE,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_QUEUE_SKIP_ENABLE,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_QUEUE_SKIP_DISABLE,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_MAX_PER_STREAM_ENABLE,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_MAX_PER_STREAM_DISABLE,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_MAX_PER_USER_ENABLE,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_MAX_PER_USER_DISABLE,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_DELETE,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_REDEMPTION_CANCEL,
{{"channel:manage:redemptions"}}},
{Action::POINTS_REWARD_REDEMPTION_FULFILL,
{{"channel:manage:redemptions"}}},
{Action::USER_BAN, {{"moderator:manage:banned_users"}}},
{Action::USER_UNBAN,
{{"moderator:manage:banned_users"}}},
{Action::USER_BLOCK, {{"user:manage:blocked_users"}}},
{Action::USER_UNBLOCK, {{"user:manage:blocked_users"}}},
{Action::USER_MODERATOR_ADD,
{{"channel:manage:moderators"}}},
{Action::USER_MODERATOR_DELETE,
{{"channel:manage:moderators"}}},
{Action::USER_VIP_ADD, {{"channel:manage:vips"}}},
{Action::USER_VIP_DELETE, {{"channel:manage:vips"}}},
{Action::COMMERCIAL_START,
{{"channel:edit:commercial"}}},
{Action::COMMERCIAL_SNOOZE, {{"channel:manage:ads"}}},
{Action::MARKER_CREATE, {{"channel:manage:broadcast"}}},
{Action::CLIP_CREATE, {{"clips:edit"}}},
{Action::CHAT_ANNOUNCEMENT_SEND,
{{"moderator:manage:announcements"}}},
{Action::CHAT_EMOTE_ONLY_ENABLE,
{{"moderator:manage:chat_settings"}}},
{Action::CHAT_EMOTE_ONLY_DISABLE,
{{"moderator:manage:chat_settings"}}},
{Action::CHAT_FOLLOWER_ONLY_ENABLE,
{{"moderator:manage:chat_settings"}}},
{Action::CHAT_FOLLOWER_ONLY_DISABLE,
{{"moderator:manage:chat_settings"}}},
{Action::CHAT_SUBSCRIBER_ONLY_ENABLE,
{{"moderator:manage:chat_settings"}}},
{Action::CHAT_SUBSCRIBER_ONLY_DISABLE,
{{"moderator:manage:chat_settings"}}},
{Action::CHAT_SLOW_MODE_ENABLE,
{{"moderator:manage:chat_settings"}}},
{Action::CHAT_SLOW_MODE_DISABLE,
{{"moderator:manage:chat_settings"}}},
{Action::CHAT_NON_MODERATOR_DELAY_ENABLE,
{{"moderator:manage:chat_settings"}}},
{Action::CHAT_NON_MODERATOR_DELAY_DISABLE,
{{"moderator:manage:chat_settings"}}},
{Action::CHAT_UNIQUE_MODE_ENABLE,
{{"moderator:manage:chat_settings"}}},
{Action::CHAT_UNIQUE_MODE_DISABLE,
{{"moderator:manage:chat_settings"}}},
{Action::WHISPER_SEND, {{"user:manage:whispers"}}},
{Action::SEND_CHAT_MESSAGE, {{"chat:edit"}}},
{Action::USER_GET_INFO, {}},
{Action::POINTS_REWARD_GET_INFO,
{{"channel:read:redemptions"},
{"channel:manage:redemptions"}}}};
auto token = _token.lock();
if (!token) {
return false;
}
auto option = requiredOption.find(_action);
assert(option != requiredOption.end());
if (option == requiredOption.end()) {
auto it = requiredOption.find(_action);
assert(it != requiredOption.end());
if (it == requiredOption.end()) {
return false;
}
return token->OptionIsEnabled(option->second);
const auto &[_, options] = *it;
return token->AnyOptionIsEnabled(options);
}
void MacroActionTwitch::ResetChatConnection()
@@ -507,6 +794,18 @@ static inline void populateAnnouncementColorSelection(QComboBox *list)
}
}
static void populateUserQueryInfoTypeSelection(QComboBox *list)
{
list->addItem(
obs_module_text(
"AdvSceneSwitcher.action.twitch.user.getInfo.queryType.id"),
static_cast<int>(MacroActionTwitch::UserInfoQueryType::ID));
list->addItem(
obs_module_text(
"AdvSceneSwitcher.action.twitch.user.getInfo.queryType.login"),
static_cast<int>(MacroActionTwitch::UserInfoQueryType::LOGIN));
}
MacroActionTwitchEdit::MacroActionTwitchEdit(
QWidget *parent, std::shared_ptr<MacroActionTwitch> entryData)
: QWidget(parent),
@@ -523,7 +822,13 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
_announcementMessage(new VariableTextEdit(this)),
_announcementColor(new QComboBox(this)),
_channel(new TwitchChannelSelection(this)),
_chatMessage(new VariableTextEdit(this))
_chatMessage(new VariableTextEdit(this)),
_userInfoQueryType(new QComboBox(this)),
_userLogin(new VariableLineEdit(this)),
_userId(new VariableSpinBox(this)),
_pointsReward(new TwitchPointsRewardWidget(this)),
_rewardVariable(new VariableSelection(this)),
_toggleRewardSelection(new QPushButton())
{
SetWidgetProperties();
SetWidgetSignalConnections();
@@ -566,14 +871,11 @@ void MacroActionTwitchEdit::ActionChanged(int idx)
void MacroActionTwitchEdit::TwitchTokenChanged(const QString &token)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_token = GetWeakTwitchTokenByQString(token);
_category->SetToken(_entryData->_token);
_channel->SetToken(_entryData->_token);
_pointsReward->SetToken(_entryData->_token);
_entryData->ResetChatConnection();
SetWidgetVisibility();
@@ -618,62 +920,38 @@ void MacroActionTwitchEdit::CheckToken()
void MacroActionTwitchEdit::StreamTitleChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_streamTitle = _streamTitle->text().toStdString();
}
void MacroActionTwitchEdit::CategoreyChanged(const TwitchCategory &category)
void MacroActionTwitchEdit::CategoryChanged(const TwitchCategory &category)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_category = category;
}
void MacroActionTwitchEdit::MarkerDescriptionChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_markerDescription =
_markerDescription->text().toStdString();
}
void MacroActionTwitchEdit::ClipHasDelayChanged(int state)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_clipHasDelay = state;
}
void MacroActionTwitchEdit::DurationChanged(const Duration &duration)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_duration = duration;
}
void MacroActionTwitchEdit::AnnouncementMessageChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_announcementMessage =
_announcementMessage->toPlainText().toStdString();
@@ -683,11 +961,7 @@ void MacroActionTwitchEdit::AnnouncementMessageChanged()
void MacroActionTwitchEdit::AnnouncementColorChanged(int index)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_announcementColor =
static_cast<MacroActionTwitch::AnnouncementColor>(index);
}
@@ -708,6 +982,18 @@ void MacroActionTwitchEdit::SetWidgetProperties()
populateActionSelection(_actions);
populateAnnouncementColorSelection(_announcementColor);
populateUserQueryInfoTypeSelection(_userInfoQueryType);
_userId->setMaximum(999999999);
_toggleRewardSelection->setCheckable(true);
_toggleRewardSelection->setMaximumWidth(11);
SetButtonIcon(_toggleRewardSelection,
GetThemeTypeName() == "Light"
? ":/res/images/dots-vert.svg"
: "theme:Dark/dots-vert.svg");
_toggleRewardSelection->setToolTip(obs_module_text(
"AdvSceneSwitcher.action.twitch.reward.toggleControl"));
}
void MacroActionTwitchEdit::SetWidgetSignalConnections()
@@ -721,8 +1007,8 @@ void MacroActionTwitchEdit::SetWidgetSignalConnections()
QWidget::connect(_streamTitle, SIGNAL(editingFinished()), this,
SLOT(StreamTitleChanged()));
QWidget::connect(_category,
SIGNAL(CategoreyChanged(const TwitchCategory &)), this,
SLOT(CategoreyChanged(const TwitchCategory &)));
SIGNAL(CategoryChanged(const TwitchCategory &)), this,
SLOT(CategoryChanged(const TwitchCategory &)));
QWidget::connect(_markerDescription, SIGNAL(editingFinished()), this,
SLOT(MarkerDescriptionChanged()));
QObject::connect(_clipHasDelay, SIGNAL(stateChanged(int)), this,
@@ -738,6 +1024,23 @@ void MacroActionTwitchEdit::SetWidgetSignalConnections()
SLOT(ChannelChanged(const TwitchChannel &)));
QWidget::connect(_chatMessage, SIGNAL(textChanged()), this,
SLOT(ChatMessageChanged()));
QWidget::connect(_userInfoQueryType, SIGNAL(currentIndexChanged(int)),
this, SLOT(UserInfoQueryTypeChanged(int)));
QWidget::connect(_userLogin, SIGNAL(editingFinished()), this,
SLOT(UserLoginChanged()));
QWidget::connect(
_userId,
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this, SLOT(UserIdChanged(const NumberVariable<int> &)));
QWidget::connect(
_pointsReward,
SIGNAL(PointsRewardChanged(const TwitchPointsReward &)), this,
SLOT(PointsRewardChanged(const TwitchPointsReward &)));
QWidget::connect(_rewardVariable,
SIGNAL(SelectionChanged(const QString &)), this,
SLOT(RewardVariableChanged(const QString &)));
QWidget::connect(_toggleRewardSelection, SIGNAL(toggled(bool)), this,
SLOT(ToggleRewardSelection(bool)));
}
void MacroActionTwitchEdit::SetWidgetVisibility()
@@ -754,7 +1057,9 @@ void MacroActionTwitchEdit::SetWidgetVisibility()
_entryData->GetAction() ==
MacroActionTwitch::Action::RAID_END ||
_entryData->GetAction() ==
MacroActionTwitch::Action::SEND_CHAT_MESSAGE);
MacroActionTwitch::Action::SEND_CHAT_MESSAGE ||
_entryData->GetAction() ==
MacroActionTwitch::Action::POINTS_REWARD_GET_INFO);
_duration->setVisible(_entryData->GetAction() ==
MacroActionTwitch::Action::COMMERCIAL_START);
_markerDescription->setVisible(
@@ -770,6 +1075,29 @@ void MacroActionTwitchEdit::SetWidgetVisibility()
MacroActionTwitch::Action::CHAT_ANNOUNCEMENT_SEND);
_chatMessage->setVisible(_entryData->GetAction() ==
MacroActionTwitch::Action::SEND_CHAT_MESSAGE);
_userInfoQueryType->setVisible(
_entryData->GetAction() ==
MacroActionTwitch::Action::USER_GET_INFO);
_userLogin->setVisible(
_entryData->GetAction() ==
MacroActionTwitch::Action::USER_GET_INFO &&
_entryData->_userInfoQueryType ==
MacroActionTwitch::UserInfoQueryType::LOGIN);
_userId->setVisible(_entryData->GetAction() ==
MacroActionTwitch::Action::USER_GET_INFO &&
_entryData->_userInfoQueryType ==
MacroActionTwitch::UserInfoQueryType::ID);
_pointsReward->setVisible(
_entryData->GetAction() ==
MacroActionTwitch::Action::POINTS_REWARD_GET_INFO &&
!_entryData->_useVariableForRewardSelection);
_rewardVariable->setVisible(
_entryData->GetAction() ==
MacroActionTwitch::Action::POINTS_REWARD_GET_INFO &&
_entryData->_useVariableForRewardSelection);
_toggleRewardSelection->setVisible(
_entryData->GetAction() ==
MacroActionTwitch::Action::POINTS_REWARD_GET_INFO);
if (_entryData->GetAction() ==
MacroActionTwitch::Action::CHANNEL_INFO_TITLE_SET ||
@@ -788,46 +1116,104 @@ void MacroActionTwitchEdit::SetWidgetVisibility()
void MacroActionTwitchEdit::ChannelChanged(const TwitchChannel &channel)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_channel = channel;
_pointsReward->SetChannel(channel);
_entryData->ResetChatConnection();
}
void MacroActionTwitchEdit::ChatMessageChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_chatMessage = _chatMessage->toPlainText().toStdString();
adjustSize();
updateGeometry();
}
void MacroActionTwitchEdit::UserInfoQueryTypeChanged(int idx)
{
GUARD_LOADING_AND_LOCK();
_entryData->_userInfoQueryType =
static_cast<MacroActionTwitch::UserInfoQueryType>(
_userInfoQueryType->itemData(idx).toInt());
SetWidgetVisibility();
}
void MacroActionTwitchEdit::UserLoginChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_userLogin = _userLogin->text().toStdString();
}
void MacroActionTwitchEdit::RewardVariableChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK()
_entryData->_rewardVariable = GetWeakVariableByQString(text);
}
void MacroActionTwitchEdit::ToggleRewardSelection(bool)
{
GUARD_LOADING_AND_LOCK();
_entryData->_useVariableForRewardSelection =
!_entryData->_useVariableForRewardSelection;
SetWidgetVisibility();
}
void MacroActionTwitchEdit::UserIdChanged(const NumberVariable<int> &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_userId = value;
}
void MacroActionTwitchEdit::PointsRewardChanged(const TwitchPointsReward &reward)
{
GUARD_LOADING_AND_LOCK();
_entryData->_pointsReward = reward;
}
void MacroActionTwitchEdit::SetWidgetLayout()
{
const std::vector<QWidget *> widgets{
_tokens, _actions, _streamTitle,
_category, _markerDescription, _clipHasDelay,
_duration, _announcementColor, _channel};
const std::vector<QWidget *> widgets{_tokens,
_actions,
_streamTitle,
_category,
_markerDescription,
_clipHasDelay,
_duration,
_announcementColor,
_channel,
_userInfoQueryType,
_userLogin,
_userId,
_pointsReward,
_rewardVariable,
_toggleRewardSelection};
for (auto widget : widgets) {
_layout->removeWidget(widget);
}
ClearLayout(_layout);
auto layoutText =
_entryData->GetAction() ==
MacroActionTwitch::Action::SEND_CHAT_MESSAGE
? obs_module_text(
"AdvSceneSwitcher.action.twitch.entry.chat")
: obs_module_text(
"AdvSceneSwitcher.action.twitch.entry.default");
const char *layoutText;
switch (_entryData->GetAction()) {
case MacroActionTwitch::Action::SEND_CHAT_MESSAGE:
layoutText = obs_module_text(
"AdvSceneSwitcher.action.twitch.entry.chat");
break;
case MacroActionTwitch::Action::USER_GET_INFO:
layoutText = obs_module_text(
"AdvSceneSwitcher.action.twitch.entry.user.getInfo");
break;
case MacroActionTwitch::Action::POINTS_REWARD_GET_INFO:
layoutText = obs_module_text(
"AdvSceneSwitcher.action.twitch.entry.reward.getInfo");
break;
default:
layoutText = obs_module_text(
"AdvSceneSwitcher.action.twitch.entry.default");
break;
}
PlaceWidgets(layoutText, _layout,
{{"{{account}}", _tokens},
{"{{actions}}", _actions},
@@ -837,7 +1223,14 @@ void MacroActionTwitchEdit::SetWidgetLayout()
{"{{clipHasDelay}}", _clipHasDelay},
{"{{duration}}", _duration},
{"{{announcementColor}}", _announcementColor},
{"{{channel}}", _channel}});
{"{{channel}}", _channel},
{"{{userInfoQueryType}}", _userInfoQueryType},
{"{{userLogin}}", _userLogin},
{"{{userId}}", _userId},
{"{{pointsReward}}", _pointsReward},
{"{{rewardVariable}}", _rewardVariable},
{"{{toggleRewardSelection}}", _toggleRewardSelection}});
_layout->setContentsMargins(0, 0, 0, 0);
}
@@ -862,6 +1255,14 @@ void MacroActionTwitchEdit::UpdateEntryData()
_channel->SetToken(_entryData->_token);
_channel->SetChannel(_entryData->_channel);
_chatMessage->setPlainText(_entryData->_chatMessage);
_userInfoQueryType->setCurrentIndex(_userInfoQueryType->findData(
static_cast<int>(_entryData->_userInfoQueryType)));
_userLogin->setText(_entryData->_userLogin);
_userId->SetValue(_entryData->_userId);
_pointsReward->SetToken(_entryData->_token);
_pointsReward->SetChannel(_entryData->_channel);
_pointsReward->SetPointsReward(_entryData->_pointsReward);
_rewardVariable->SetVariable(_entryData->_rewardVariable);
SetWidgetVisibility();
}

View File

@@ -54,6 +54,9 @@ public:
// Reward add
POINTS_REWARD_ADD = 600, // TODO
// Get reward information
POINTS_REWARD_GET_INFO = 650,
// Reward update
POINTS_REWARD_ENABLE = 710,
POINTS_REWARD_DISABLE = 711,
@@ -134,6 +137,9 @@ public:
CHANNEL_SCHEDULE_SEGMENT_DELETE = 3500, // TODO
SEND_CHAT_MESSAGE = 5000,
// Get user info
USER_GET_INFO = 6000
};
enum class AnnouncementColor {
@@ -146,6 +152,8 @@ public:
enum class RedemptionStatus { CANCELED, FULFILLED };
enum class UserInfoQueryType { ID, LOGIN };
bool PerformAction();
void LogAction() const;
bool Save(obs_data_t *obj) const;
@@ -169,6 +177,12 @@ public:
AnnouncementColor _announcementColor = AnnouncementColor::PRIMARY;
TwitchChannel _channel;
StringVariable _chatMessage;
UserInfoQueryType _userInfoQueryType = UserInfoQueryType::LOGIN;
StringVariable _userLogin = "user login";
IntVariable _userId = 0;
TwitchPointsReward _pointsReward;
std::weak_ptr<Variable> _rewardVariable;
bool _useVariableForRewardSelection = false;
private:
void SetStreamTitle(const std::shared_ptr<TwitchToken> &) const;
@@ -181,10 +195,20 @@ private:
bool enable) const;
void StartRaid(const std::shared_ptr<TwitchToken> &);
void SendChatMessage(const std::shared_ptr<TwitchToken> &);
void GetUserInfo(const std::shared_ptr<TwitchToken> &);
void GetRewardInfo(const std::shared_ptr<TwitchToken> &);
bool ResolveVariableSelectionToRewardId(
const std::shared_ptr<TwitchToken> &);
void SetupTempVars();
Action _action = Action::CHANNEL_INFO_TITLE_SET;
std::shared_ptr<TwitchChatConnection> _chatConnection;
std::string _lastResolvedRewardTitle;
std::string _lastResolvedRewardId;
static bool _registered;
static const std::string id;
};
@@ -210,7 +234,7 @@ private slots:
void TwitchTokenChanged(const QString &);
void CheckToken();
void StreamTitleChanged();
void CategoreyChanged(const TwitchCategory &);
void CategoryChanged(const TwitchCategory &);
void MarkerDescriptionChanged();
void ClipHasDelayChanged(int state);
void DurationChanged(const Duration &);
@@ -218,6 +242,12 @@ private slots:
void AnnouncementColorChanged(int index);
void ChannelChanged(const TwitchChannel &);
void ChatMessageChanged();
void UserInfoQueryTypeChanged(int);
void UserLoginChanged();
void UserIdChanged(const NumberVariable<int> &);
void PointsRewardChanged(const TwitchPointsReward &);
void RewardVariableChanged(const QString &);
void ToggleRewardSelection(bool);
signals:
void HeaderInfoChanged(const QString &);
@@ -246,6 +276,12 @@ private:
QComboBox *_announcementColor;
TwitchChannelSelection *_channel;
VariableTextEdit *_chatMessage;
QComboBox *_userInfoQueryType;
VariableLineEdit *_userLogin;
VariableSpinBox *_userId;
TwitchPointsRewardWidget *_pointsReward;
VariableSelection *_rewardVariable;
QPushButton *_toggleRewardSelection;
bool _loading = true;
};

View File

@@ -257,65 +257,15 @@ void MacroConditionTwitch::ResetChatConnection()
_chatConnection.reset();
}
static void
setTempVarsHelper(const std::string &jsonStr,
std::function<void(const char *, const char *)> setVar)
{
try {
auto json = nlohmann::json::parse(jsonStr);
for (auto it = json.begin(); it != json.end(); ++it) {
if (it->is_string()) {
setVar(it.key().c_str(),
it->get<std::string>().c_str());
continue;
}
setVar(it.key().c_str(), it->dump().c_str());
}
} catch (const nlohmann::json::exception &e) {
vblog(LOG_INFO, "%s", jsonStr.c_str());
vblog(LOG_INFO, "%s", e.what());
}
}
static void
setTempVarsHelper(obs_data_t *data,
std::function<void(const char *, const char *)> setVar)
{
auto jsonStr = obs_data_get_json(data);
if (!jsonStr) {
return;
}
setTempVarsHelper(jsonStr, setVar);
}
bool MacroConditionTwitch::CheckChannelGenericEvents()
{
if (!_eventBuffer) {
return false;
}
while (!_eventBuffer->Empty()) {
auto event = _eventBuffer->ConsumeMessage();
if (!event) {
continue;
}
if (_subscriptionID != event->id) {
continue;
}
SetVariableValue(event->ToString());
setTempVarsHelper(event->data,
[this](const char *id, const char *value) {
SetTempVarValue(id, value);
});
if (_clearBufferOnMatch) {
_eventBuffer->Clear();
}
return true;
}
return false;
return HandleMatchingSubscriptionEvents([this](const Event &event) {
SetVariableValue(event.ToString());
SetJsonTempVars(event.data,
[this](const char *id, const char *value) {
SetTempVarValue(id, value);
});
});
}
bool MacroConditionTwitch::CheckChannelLiveEvents()
@@ -345,10 +295,105 @@ bool MacroConditionTwitch::CheckChannelLiveEvents()
}
SetVariableValue(event->ToString());
setTempVarsHelper(event->data,
[this](const char *id, const char *value) {
SetTempVarValue(id, value);
});
SetJsonTempVars(event->data,
[this](const char *id, const char *value) {
SetTempVarValue(id, value);
});
if (_clearBufferOnMatch) {
_eventBuffer->Clear();
}
return true;
}
return false;
}
bool MacroConditionTwitch::CheckChannelRewardChangeEvents()
{
return HandleMatchingSubscriptionEvents([this](const Event &event) {
SetVariableValue(event.ToString());
SetJsonTempVars(event.data,
[this](const char *id, const char *value) {
SetTempVarValue(id, value);
});
OBSDataAutoRelease image =
obs_data_get_obj(event.data, "image");
SetTempVarValue("image.url_4x",
obs_data_get_string(image, "url_4x"));
OBSDataAutoRelease default_image =
obs_data_get_obj(event.data, "default_image");
SetTempVarValue("default_image.url_4x",
obs_data_get_string(default_image, "url_4x"));
OBSDataAutoRelease max_per_stream =
obs_data_get_obj(event.data, "max_per_stream");
SetTempVarValue("max_per_stream.is_enabled",
obs_data_get_bool(max_per_stream,
"is_enabled"));
SetTempVarValue("max_per_stream.max_per_stream",
std::to_string(obs_data_get_int(max_per_stream,
"value")));
OBSDataAutoRelease max_per_user_per_stream =
obs_data_get_obj(event.data, "max_per_user_per_stream");
SetTempVarValue("max_per_user_per_stream.is_enabled",
obs_data_get_bool(max_per_user_per_stream,
"is_enabled"));
SetTempVarValue(
"max_per_user_per_stream.max_per_user_per_stream",
std::to_string(obs_data_get_int(max_per_user_per_stream,
"value")));
OBSDataAutoRelease global_cooldown =
obs_data_get_obj(event.data, "global_cooldown");
SetTempVarValue("global_cooldown.is_enabled",
obs_data_get_bool(global_cooldown,
"is_enabled"));
SetTempVarValue(
"max_per_user_per_stream.global_cooldown_seconds",
std::to_string(obs_data_get_int(max_per_user_per_stream,
"seconds")));
});
}
bool MacroConditionTwitch::CheckChannelRewardRedemptionEvents()
{
return HandleMatchingSubscriptionEvents([this](const Event &event) {
SetVariableValue(event.ToString());
SetJsonTempVars(event.data,
[this](const char *id, const char *value) {
SetTempVarValue(id, value);
});
OBSDataAutoRelease rewardInfo =
obs_data_get_obj(event.data, "reward");
SetJsonTempVars(rewardInfo, [this](const char *nestedId,
const char *value) {
const std::string id =
"reward." + std::string(nestedId);
SetTempVarValue(id, value);
});
});
}
bool MacroConditionTwitch::HandleMatchingSubscriptionEvents(
const std::function<void(const Event &)> &matchCb)
{
if (!_eventBuffer) {
return false;
}
while (!_eventBuffer->Empty()) {
auto event = _eventBuffer->ConsumeMessage();
if (!event) {
continue;
}
if (_subscriptionID != event->id) {
continue;
}
matchCb(*event);
if (_clearBufferOnMatch) {
_eventBuffer->Clear();
@@ -618,16 +663,18 @@ bool MacroConditionTwitch::CheckCondition()
case Condition::CHARITY_CAMPAIGN_END_EVENT:
case Condition::SHIELD_MODE_START_EVENT:
case Condition::SHIELD_MODE_END_EVENT:
case Condition::POINTS_REWARD_ADDITION_EVENT:
case Condition::POINTS_REWARD_UPDATE_EVENT:
case Condition::POINTS_REWARD_DELETION_EVENT:
case Condition::POINTS_REWARD_REDEMPTION_EVENT:
case Condition::POINTS_REWARD_REDEMPTION_UPDATE_EVENT:
case Condition::USER_BAN_EVENT:
case Condition::USER_UNBAN_EVENT:
case Condition::USER_MODERATOR_ADDITION_EVENT:
case Condition::USER_MODERATOR_DELETION_EVENT:
return CheckChannelGenericEvents();
case Condition::POINTS_REWARD_ADDITION_EVENT:
case Condition::POINTS_REWARD_UPDATE_EVENT:
case Condition::POINTS_REWARD_DELETION_EVENT:
return CheckChannelRewardChangeEvents();
case Condition::POINTS_REWARD_REDEMPTION_EVENT:
case Condition::POINTS_REWARD_REDEMPTION_UPDATE_EVENT:
return CheckChannelRewardRedemptionEvents();
case Condition::STREAM_ONLINE_LIVE_EVENT:
case Condition::STREAM_ONLINE_PLAYLIST_EVENT:
case Condition::STREAM_ONLINE_WATCHPARTY_EVENT:
@@ -1206,25 +1253,41 @@ void MacroConditionTwitch::SetupTempVars()
setupTempVarHelper("is_user_input_required", ".reward");
setupTempVarHelper("should_redemptions_skip_request_queue",
".reward");
setupTempVarHelper("max_per_stream", ".reward");
setupTempVarHelper("max_per_user_per_stream", ".reward");
setupTempVarHelper("background_color", ".reward");
setupTempVarHelper("image", ".reward");
setupTempVarHelper("image.url_4x", ".reward");
setupTempVarHelper("default_image", ".reward");
setupTempVarHelper("global_cooldown", ".reward");
setupTempVarHelper("default_image.url_4x", ".reward");
setupTempVarHelper("cooldown_expires_at", ".reward");
setupTempVarHelper("redemptions_redeemed_current_stream",
".reward");
setupTempVarHelper("max_per_stream", ".reward");
setupTempVarHelper("max_per_stream.is_enabled", ".reward");
setupTempVarHelper("max_per_stream.max_per_stream", ".reward");
setupTempVarHelper("max_per_user_per_stream", ".reward");
setupTempVarHelper("max_per_user_per_stream.is_enabled",
".reward");
setupTempVarHelper(
"max_per_user_per_stream.max_per_user_per_stream",
".reward");
setupTempVarHelper("global_cooldown", ".reward");
setupTempVarHelper("global_cooldown.is_enabled", ".reward");
setupTempVarHelper("global_cooldown.global_cooldown_seconds",
".reward");
break;
case Condition::POINTS_REWARD_REDEMPTION_EVENT:
case Condition::POINTS_REWARD_REDEMPTION_UPDATE_EVENT:
setupTempVarHelper("id", ".reward");
setupTempVarHelper("id", ".redemption");
setupTempVarHelper("user_id", ".reward");
setupTempVarHelper("user_login", ".reward");
setupTempVarHelper("user_name", ".reward");
setupTempVarHelper("user_input", ".reward");
setupTempVarHelper("status", ".reward");
setupTempVarHelper("reward", ".reward");
setupTempVarHelper("reward.id", ".reward");
setupTempVarHelper("reward.title", ".reward");
setupTempVarHelper("reward.prompt", ".reward");
setupTempVarHelper("reward.cost", ".reward");
setupTempVarHelper("redeemed_at", ".reward");
break;
case Condition::USER_BAN_EVENT:
@@ -1425,10 +1488,8 @@ MacroConditionTwitchEdit::MacroConditionTwitchEdit(
this,
SLOT(ChatMessagePatternChanged(const ChatMessagePattern &)));
QWidget::connect(_category,
SIGNAL(CategoreyChanged(const TwitchCategory &)), this,
SLOT(CategoreyChanged(const TwitchCategory &)));
QWidget::connect(this, SIGNAL(TempVarsChanged()), window(),
SIGNAL(SegmentTempVarsChanged()));
SIGNAL(CategoryChanged(const TwitchCategory &)), this,
SLOT(CategoryChanged(const TwitchCategory &)));
QWidget::connect(_clearBufferOnMatch, SIGNAL(stateChanged(int)), this,
SLOT(ClearBufferOnMatchChanged(int)));
@@ -1480,7 +1541,6 @@ void MacroConditionTwitchEdit::ConditionChanged(int idx)
_entryData->SetCondition(static_cast<MacroConditionTwitch::Condition>(
_conditions->itemData(idx).toInt()));
SetWidgetVisibility();
emit TempVarsChanged();
}
void MacroConditionTwitchEdit::TwitchTokenChanged(const QString &token)
@@ -1572,7 +1632,7 @@ void MacroConditionTwitchEdit::RegexTitleChanged(const RegexConfig &conf)
updateGeometry();
}
void MacroConditionTwitchEdit::CategoreyChanged(const TwitchCategory &category)
void MacroConditionTwitchEdit::CategoryChanged(const TwitchCategory &category)
{
GUARD_LOADING_AND_LOCK();
_entryData->_category = category;

View File

@@ -111,7 +111,11 @@ public:
private:
bool CheckChannelGenericEvents();
bool CheckChannelLiveEvents();
bool CheckChannelRewardChangeEvents();
bool CheckChannelRewardRedemptionEvents();
bool CheckChatMessages(TwitchToken &token);
bool HandleMatchingSubscriptionEvents(
const std::function<void(const Event &)> &matchCb);
bool CheckChatUserJoinOrLeave(TwitchToken &token);
void RegisterEventSubscription();
@@ -171,12 +175,11 @@ private slots:
void StreamTitleChanged();
void RegexTitleChanged(const RegexConfig &);
void ChatMessagePatternChanged(const ChatMessagePattern &);
void CategoreyChanged(const TwitchCategory &);
void CategoryChanged(const TwitchCategory &);
void ClearBufferOnMatchChanged(int);
signals:
void HeaderInfoChanged(const QString &);
void TempVarsChanged();
private:
void SetWidgetVisibility();

View File

@@ -88,7 +88,7 @@ void TwitchPointsRewardSelection::PopulateSelection()
const QSignalBlocker b(this);
clear();
auto pointsRewards = GetPointsRewards(token, *_channel);
auto pointsRewards = GetPointsRewardsForChannel(token, *_channel);
if (!pointsRewards) {
DisplayErrorMessage(obs_module_text(
"AdvSceneSwitcher.twitch.selection.points.reward.tooltip.error"));
@@ -127,47 +127,6 @@ void TwitchPointsRewardSelection::HideErrorMessage()
setToolTip("");
}
std::optional<std::vector<TwitchPointsReward>>
TwitchPointsRewardSelection::GetPointsRewards(
const std::shared_ptr<TwitchToken> &token, const TwitchChannel &channel)
{
httplib::Params params = {
{"broadcaster_id", channel.GetUserID(*token)}};
auto response = SendGetRequest(*token, "https://api.twitch.tv",
"/helix/channel_points/custom_rewards",
params);
if (response.status != 200) {
blog(LOG_WARNING,
"Failed to fetch points rewards for user %s and channel %s! (%d)",
token->GetName().c_str(), channel.GetName().c_str(),
response.status);
return {};
}
return ParseResponse(response.data);
}
std::vector<TwitchPointsReward>
TwitchPointsRewardSelection::ParseResponse(obs_data_t *response)
{
std::vector<TwitchPointsReward> pointRewards;
OBSDataArrayAutoRelease jsonArray =
obs_data_get_array(response, "data");
size_t count = obs_data_array_count(jsonArray);
for (size_t i = 0; i < count; ++i) {
OBSDataAutoRelease jsonObj = obs_data_array_item(jsonArray, i);
std::string id = obs_data_get_string(jsonObj, "id");
std::string title = obs_data_get_string(jsonObj, "title");
pointRewards.push_back({id, title});
}
return pointRewards;
}
void TwitchPointsRewardSelection::SelectionChanged(int index)
{
TwitchPointsReward pointsReward{
@@ -182,7 +141,9 @@ TwitchPointsRewardWidget::TwitchPointsRewardWidget(QWidget *parent)
_refreshButton(new QPushButton(this))
{
_refreshButton->setMaximumWidth(22);
SetButtonIcon(_refreshButton, ":res/images/refresh.svg");
SetButtonIcon(_refreshButton, GetThemeTypeName() == "Light"
? ":res/images/refresh.svg"
: "theme:Dark/refresh.svg");
_refreshButton->setToolTip(obs_module_text(
"AdvSceneSwitcher.twitch.selection.points.reward.refresh"));
@@ -216,4 +177,44 @@ void TwitchPointsRewardWidget::SetToken(const std::weak_ptr<TwitchToken> &token)
_selection->SetToken(token);
}
static std::vector<TwitchPointsReward> parseApiResponse(obs_data_t *response)
{
std::vector<TwitchPointsReward> pointRewards;
OBSDataArrayAutoRelease jsonArray =
obs_data_get_array(response, "data");
size_t count = obs_data_array_count(jsonArray);
for (size_t i = 0; i < count; ++i) {
OBSDataAutoRelease jsonObj = obs_data_array_item(jsonArray, i);
std::string id = obs_data_get_string(jsonObj, "id");
std::string title = obs_data_get_string(jsonObj, "title");
pointRewards.push_back({id, title});
}
return pointRewards;
}
std::optional<std::vector<TwitchPointsReward>>
GetPointsRewardsForChannel(const std::shared_ptr<TwitchToken> &token,
const TwitchChannel &channel)
{
httplib::Params params = {
{"broadcaster_id", channel.GetUserID(*token)}};
auto response = SendGetRequest(*token, "https://api.twitch.tv",
"/helix/channel_points/custom_rewards",
params, true);
if (response.status != 200) {
blog(LOG_WARNING,
"Failed to fetch points rewards for user %s and channel %s! (%d)",
token->GetName().c_str(), channel.GetName().c_str(),
response.status);
return {};
}
return parseApiResponse(response.data);
}
} // namespace advss

View File

@@ -35,10 +35,6 @@ protected:
private:
void AddPredefinedItems();
std::optional<std::vector<TwitchPointsReward>>
GetPointsRewards(const std::shared_ptr<TwitchToken> &token,
const TwitchChannel &channel);
std::vector<TwitchPointsReward> ParseResponse(obs_data_t *response);
bool _allowAny;
const std::vector<TokenOption> _supportedTokenOptions = {
@@ -71,4 +67,8 @@ protected:
QPushButton *_refreshButton;
};
std::optional<std::vector<TwitchPointsReward>>
GetPointsRewardsForChannel(const std::shared_ptr<TwitchToken> &token,
const TwitchChannel &channel);
} // namespace advss

View File

@@ -439,7 +439,7 @@ TwitchTokenSettingsDialog::TwitchTokenSettingsDialog(
"AdvSceneSwitcher.twitchToken.select",
"AdvSceneSwitcher.twitchToken.add",
"AdvSceneSwitcher.twitchToken.nameNotAvailable",
parent),
false, parent),
_requestToken(new QPushButton(
obs_module_text("AdvSceneSwitcher.twitchToken.request"))),
_showToken(new QPushButton()),
@@ -456,7 +456,6 @@ TwitchTokenSettingsDialog::TwitchTokenSettingsDialog(
_currentTokenValue->setText(QString::fromStdString(settings._token));
_name->setReadOnly(true);
_showNameEmptyWarning = false;
QWidget::connect(_requestToken, SIGNAL(clicked()), this,
SLOT(RequestToken()));
@@ -561,7 +560,9 @@ void TwitchTokenSettingsDialog::CheckIfTokenValid()
void TwitchTokenSettingsDialog::ShowToken()
{
SetButtonIcon(_showToken, ":res/images/visible.svg");
SetButtonIcon(_showToken, GetThemeTypeName() == "Light"
? ":res/images/visible.svg"
: "theme:Dark/visible.svg");
_currentTokenValue->setEchoMode(QLineEdit::Normal);
}
@@ -682,22 +683,33 @@ void TwitchTokenSettingsDialog::RequestToken()
void TwitchTokenSettingsDialog::GotToken(const std::optional<QString> &value)
{
_currentTokenValue->setText(value.value_or(""));
if (value.has_value()) {
_tokenStatus->setText(obs_module_text(
"AdvSceneSwitcher.twitchToken.request.success"));
_currentToken.SetToken(value.value().toStdString());
auto name = QString::fromStdString(_currentToken._name);
_name->setText(name);
_name->textEdited(name);
QMetaObject::invokeMethod(this, "NameChanged",
Q_ARG(const QString &, name));
SetTokenInfoVisible(true);
} else {
if (!value) {
_tokenStatus->setText(obs_module_text(
"AdvSceneSwitcher.twitchToken.request.fail"));
_name->setText("");
SetTokenInfoVisible(false);
_requestToken->setEnabled(true);
return;
}
_currentToken.SetToken(value.value().toStdString());
auto name = QString::fromStdString(_currentToken._name);
if (name.isEmpty()) {
_tokenStatus->setText(obs_module_text(
"AdvSceneSwitcher.twitchToken.request.fail"));
_name->setText("");
SetTokenInfoVisible(false);
_requestToken->setEnabled(true);
return;
}
_tokenStatus->setText(obs_module_text(
"AdvSceneSwitcher.twitchToken.request.success"));
_name->setText(name);
_name->textEdited(name);
QMetaObject::invokeMethod(this, "NameChanged",
Q_ARG(const QString &, name));
SetTokenInfoVisible(true);
_requestToken->setEnabled(true);
}

View File

@@ -1,12 +1,16 @@
#include "twitch-helpers.hpp"
#include "plugin-state-helpers.hpp"
#include "token.hpp"
#include <log-helper.hpp>
#include <nlohmann/json.hpp>
#include <string>
namespace advss {
static constexpr std::string_view clientID = "ds5tt4ogliifsqc04mz3d3etnck3e5";
static const int cacheTimeoutSeconds = 10;
static std::atomic_bool apiIsThrottling = {false};
const char *GetClientID()
{
@@ -64,6 +68,12 @@ static bool cacheIsValid(const std::map<Args, CacheEntry> &cache,
return it != cache.end() && !cacheIsTooOld(it->second);
}
static void cleanupCache(std::map<Args, CacheEntry> &cache, std::mutex &mtx)
{
std::lock_guard<std::mutex> lock(mtx);
cache.clear();
}
static httplib::Headers getTokenRequestHeaders(const std::string &token)
{
return {
@@ -78,6 +88,37 @@ static std::string getRequestBody(const OBSData &data)
return json ? json : "";
}
static void handleThrottling(const httplib::Result &response)
{
const auto &headers = response.value().headers;
const auto rateLimitResetTimeStr = headers.find("Ratelimit-Reset");
if (rateLimitResetTimeStr == headers.end()) {
return;
}
std::time_t rateLimitResetTime =
std::stoll(rateLimitResetTimeStr->second);
auto resetTimePoint =
std::chrono::system_clock::from_time_t(rateLimitResetTime);
auto sleepDuration = resetTimePoint - std::chrono::system_clock::now();
if (sleepDuration.count() < 0) {
return;
}
blog(LOG_WARNING, "Twitch API access is throttled for %lld seconds!",
static_cast<long long int>(
std::chrono::duration_cast<std::chrono::seconds>(
sleepDuration)
.count()));
apiIsThrottling = true;
auto resetThread = std::thread([sleepDuration]() {
std::this_thread::sleep_for(sleepDuration);
apiIsThrottling = false;
});
resetThread.detach();
}
static RequestResult processResult(const httplib::Result &response,
const char *funcName)
{
@@ -89,6 +130,10 @@ static RequestResult processResult(const httplib::Result &response,
return {};
}
if (response->status == 429) {
handleThrottling(response);
}
RequestResult result;
result.status = response->status;
@@ -103,10 +148,15 @@ static RequestResult processResult(const httplib::Result &response,
return result;
}
RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params)
static RequestResult sendGetRequest(const TwitchToken &token,
const std::string &uri,
const std::string &path,
const httplib::Params &params)
{
if (apiIsThrottling) {
return {};
}
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
@@ -127,9 +177,17 @@ RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params, bool useCache)
{
if (apiIsThrottling) {
return {};
}
static std::map<Args, CacheEntry> cache;
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
[[maybe_unused]] static bool _ = []() {
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });
return true;
}();
auto tokenStr = token.GetToken();
if (!tokenStr) {
@@ -139,22 +197,32 @@ RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
auto headers = getTokenRequestHeaders(*tokenStr);
Args args(uri, path, "", params, headers);
if (useCache && cacheIsValid(cache, args)) {
if (!useCache) {
return sendGetRequest(token, uri, path, params);
}
std::lock_guard<std::mutex> lock(mtx);
if (cacheIsValid(cache, args)) {
auto it = cache.find(args);
return it->second.result;
}
auto result = SendGetRequest(token, uri, path, params);
auto result = sendGetRequest(token, uri, path, params);
cache[args] = {result};
return result;
}
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data)
static RequestResult sendPostRequest(const TwitchToken &token,
const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data)
{
if (apiIsThrottling) {
return {};
}
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
@@ -179,9 +247,17 @@ RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
const httplib::Params &params,
const OBSData &data, bool useCache)
{
if (apiIsThrottling) {
return {};
}
static std::map<Args, CacheEntry> cache;
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
[[maybe_unused]] static bool _ = []() {
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });
return true;
}();
auto tokenStr = token.GetToken();
if (!tokenStr) {
@@ -192,21 +268,32 @@ RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
auto body = getRequestBody(data);
Args args(uri, path, body, params, headers);
if (useCache && cacheIsValid(cache, args)) {
if (!useCache) {
return sendPostRequest(token, uri, path, params, data);
}
std::lock_guard<std::mutex> lock(mtx);
if (cacheIsValid(cache, args)) {
auto it = cache.find(args);
return it->second.result;
}
auto result = SendPostRequest(token, uri, path, params, data);
auto result = sendPostRequest(token, uri, path, params, data);
cache[args] = {result};
return result;
}
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params, const OBSData &data)
static RequestResult sendPutRequest(const TwitchToken &token,
const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data)
{
if (apiIsThrottling) {
return {};
}
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
@@ -231,9 +318,17 @@ RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const httplib::Params &params, const OBSData &data,
bool useCache)
{
if (apiIsThrottling) {
return {};
}
static std::map<Args, CacheEntry> cache;
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
[[maybe_unused]] static bool _ = []() {
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });
return true;
}();
auto tokenStr = token.GetToken();
if (!tokenStr) {
@@ -244,22 +339,32 @@ RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
auto body = getRequestBody(data);
Args args(uri, path, body, params, headers);
if (useCache && cacheIsValid(cache, args)) {
if (!useCache) {
return sendPutRequest(token, uri, path, params, data);
}
std::lock_guard<std::mutex> lock(mtx);
if (cacheIsValid(cache, args)) {
auto it = cache.find(args);
return it->second.result;
}
auto result = SendPutRequest(token, uri, path, params, data);
auto result = sendPutRequest(token, uri, path, params, data);
cache[args] = {result};
return result;
}
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data)
static RequestResult sendPatchRequest(const TwitchToken &token,
const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data)
{
if (apiIsThrottling) {
return {};
}
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
@@ -284,9 +389,17 @@ RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const httplib::Params &params,
const OBSData &data, bool useCache)
{
if (apiIsThrottling) {
return {};
}
static std::map<Args, CacheEntry> cache;
static std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);
[[maybe_unused]] static bool _ = []() {
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });
return true;
}();
auto tokenStr = token.GetToken();
if (!tokenStr) {
@@ -297,21 +410,31 @@ RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
auto body = getRequestBody(data);
Args args(uri, path, body, params, headers);
if (useCache && cacheIsValid(cache, args)) {
if (!useCache) {
return sendPatchRequest(token, uri, path, params, data);
}
std::lock_guard<std::mutex> lock(mtx);
if (cacheIsValid(cache, args)) {
auto it = cache.find(args);
return it->second.result;
}
auto result = SendPatchRequest(token, uri, path, params, data);
auto result = sendPatchRequest(token, uri, path, params, data);
cache[args] = {result};
return result;
}
RequestResult SendDeleteRequest(const TwitchToken &token,
const std::string &uri, const std::string &path,
const httplib::Params &params)
static RequestResult sendDeleteRequest(const TwitchToken &token,
const std::string &uri,
const std::string &path,
const httplib::Params &params)
{
if (apiIsThrottling) {
return {};
}
httplib::Client cli(uri);
auto tokenStr = token.GetToken();
@@ -330,4 +453,34 @@ RequestResult SendDeleteRequest(const TwitchToken &token,
return processResult(response, __func__);
}
void SetJsonTempVars(const std::string &jsonStr,
std::function<void(const char *, const char *)> setVarFunc)
{
try {
auto json = nlohmann::json::parse(jsonStr);
for (auto it = json.begin(); it != json.end(); ++it) {
if (it->is_string()) {
setVarFunc(it.key().c_str(),
it->get<std::string>().c_str());
continue;
}
setVarFunc(it.key().c_str(), it->dump().c_str());
}
} catch (const nlohmann::json::exception &e) {
vblog(LOG_INFO, "%s", jsonStr.c_str());
vblog(LOG_INFO, "%s", e.what());
}
}
void SetJsonTempVars(obs_data_t *data,
std::function<void(const char *, const char *)> setVarFunc)
{
auto jsonStr = obs_data_get_json(data);
if (!jsonStr) {
return;
}
SetJsonTempVars(jsonStr, setVarFunc);
}
} // namespace advss

View File

@@ -15,42 +15,33 @@ struct RequestResult {
const char *GetClientID();
// These functions do *not* use or create RequestResult cache entries
// These functions can cache the RequestResult for 10s
RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params = {});
const httplib::Params &params = {},
bool useCache = false);
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params = {},
const OBSData &data = nullptr);
const OBSData &data = nullptr,
bool useCache = false);
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params = {},
const OBSData &data = nullptr);
const OBSData &data = nullptr,
bool useCache = false);
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params = {},
const OBSData &data = nullptr);
RequestResult SendDeleteRequest(const TwitchToken &token,
const std::string &uri, const std::string &path,
const httplib::Params &params = {});
const OBSData &data = nullptr,
bool useCache = false);
// These functions will cache the RequestResult for 10s
// Note that the cache will be reported as a "memory leak" on OBS shutdown
RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params, bool useCache);
RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data, bool useCache);
RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params, const OBSData &data,
bool useCache);
RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
const std::string &path,
const httplib::Params &params,
const OBSData &data, bool useCache);
// Helper functions to set temp var values
void SetJsonTempVars(const std::string &jsonStr,
std::function<void(const char *, const char *)> setVarFunc);
void SetJsonTempVars(obs_data_t *data,
std::function<void(const char *, const char *)> setVarFunc);
} // namespace advss

View File

@@ -44,8 +44,8 @@ TwitchConnectionsTable *TwitchConnectionsTable::Create()
void TwitchConnectionsTable::Add()
{
auto newToken = std::make_shared<TwitchToken>();
auto accepted =
TwitchTokenSettingsDialog::AskForSettings(this, *newToken);
auto accepted = TwitchTokenSettingsDialog::AskForSettings(
GetSettingsWindow(), *newToken);
if (!accepted) {
return;
}
@@ -139,7 +139,7 @@ static void openSettingsDialog()
return;
}
TwitchTokenSettingsDialog::AskForSettings(tabWidget->Table(),
TwitchTokenSettingsDialog::AskForSettings(GetSettingsWindow(),
*token.get());
}

View File

@@ -47,8 +47,8 @@ target_sources(
macro-condition-video.hpp
opencv-helpers.cpp
opencv-helpers.hpp
paramerter-wrappers.cpp
paramerter-wrappers.hpp
parameter-wrappers.cpp
parameter-wrappers.hpp
preview-dialog.cpp
preview-dialog.hpp
screenshot-dialog.cpp

View File

@@ -482,8 +482,9 @@ static inline void populateVideoInputSelection(QComboBox *list)
static inline void populateConditionSelection(QComboBox *list)
{
for (auto entry : conditionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
for (auto &[value, name] : conditionTypes) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
}
@@ -1189,8 +1190,6 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
SIGNAL(VideoSelectionChanged(const VideoInput &)),
&_previewDialog,
SLOT(VideoSelectionChanged(const VideoInput &)));
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)),
&_previewDialog, SLOT(ConditionChanged(int)));
QWidget::connect(_area, SIGNAL(Resized()), this, SLOT(Resize()));
QWidget::connect(entryData.get(),
&MacroConditionVideo::InputFileChanged, this,
@@ -1320,14 +1319,15 @@ void MacroConditionVideoEdit::HandleVideoInputUpdate()
emit VideoSelectionChanged(_entryData->_video);
}
void MacroConditionVideoEdit::ConditionChanged(int cond)
void MacroConditionVideoEdit::ConditionChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->SetCondition(static_cast<VideoCondition>(cond));
_entryData->SetCondition(
static_cast<VideoCondition>(_condition->itemData(idx).toInt()));
_entryData->ResetLastMatch();
SetWidgetVisibility();
@@ -1384,23 +1384,12 @@ void MacroConditionVideoEdit::ImageBrowseButtonClicked()
"AdvSceneSwitcher.condition.video.askFileAction"),
QMessageBox::Yes | QMessageBox::No |
QMessageBox::Cancel);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
msgBox.setButtonText(
QMessageBox::Yes,
obs_module_text(
"AdvSceneSwitcher.condition.video.askFileAction.file"));
msgBox.setButtonText(
QMessageBox::No,
obs_module_text(
"AdvSceneSwitcher.condition.video.askFileAction.screenshot"));
#else
auto yes = msgBox.button(QMessageBox::StandardButton::Yes);
yes->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.askFileAction.file"));
auto no = msgBox.button(QMessageBox::StandardButton::No);
no->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.askFileAction.screenshot"));
#endif
msgBox.setWindowFlags(Qt::Window | Qt::WindowTitleHint |
Qt::CustomizeWindowHint);
const auto result = msgBox.exec();
@@ -1433,8 +1422,8 @@ void MacroConditionVideoEdit::ImageBrowseButtonClicked()
return;
}
auto screenshot =
ScreenshotDialog::AskForScreenshot(_entryData->_video);
auto screenshot = ScreenshotDialog::AskForScreenshot(
_entryData->_video, _entryData->_areaParameters);
if (!screenshot) {
return;
}
@@ -1603,8 +1592,8 @@ void MacroConditionVideoEdit::UpdateEntryData()
static_cast<int>(_entryData->_video.type));
_scenes->SetScene(_entryData->_video.scene);
_sources->SetSource(_entryData->_video.source);
_condition->setCurrentIndex(
static_cast<int>(_entryData->GetCondition()));
_condition->setCurrentIndex(_condition->findData(
static_cast<int>(_entryData->GetCondition())));
_reduceLatency->setChecked(_entryData->_blockUntilScreenshotDone);
_imagePath->SetPath(QString::fromStdString(_entryData->_file));
_usePatternForChangedCheck->setChecked(

View File

@@ -1,7 +1,7 @@
#pragma once
#include "opencv-helpers.hpp"
#include "area-selection.hpp"
#include "paramerter-wrappers.hpp"
#include "parameter-wrappers.hpp"
#include "preview-dialog.hpp"
#include <macro-condition-edit.hpp>

View File

@@ -1,4 +1,4 @@
#include "paramerter-wrappers.hpp"
#include "parameter-wrappers.hpp"
#include <QFileInfo>
#include <source-helpers.hpp>
@@ -251,7 +251,8 @@ OCRParameters::OCRParameters(const OCRParameters &other)
regex(other.regex),
color(other.color),
colorThreshold(other.colorThreshold),
pageSegMode(other.pageSegMode)
pageSegMode(other.pageSegMode),
languageCode(other.languageCode)
{
if (!initDone) {
Setup();
@@ -268,6 +269,7 @@ OCRParameters &OCRParameters::operator=(const OCRParameters &other)
color = other.color;
colorThreshold = other.colorThreshold;
pageSegMode = other.pageSegMode;
languageCode = other.languageCode;
if (!initDone) {
Setup();
}
@@ -310,6 +312,7 @@ bool OCRParameters::Load(obs_data_t *obj)
if (initDone) {
ocr->SetPageSegMode(pageSegMode);
}
Setup();
return true;
}
@@ -329,6 +332,7 @@ bool OCRParameters::SetLanguageCode(const std::string &value)
if (!fileInfo.exists(dataPath)) {
return false;
}
languageCode = value;
Setup();
ocr->SetPageSegMode(pageSegMode);
return true;

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