Compare commits

...

37 Commits

Author SHA1 Message Date
WarmUpTill
510f83246e Only emit signals when value changed
* Fixes crash when deleting macro in case macro selection is in focus
* Prevent unnecessary create operation when switching macro segment type
2024-08-16 06:49:27 +02:00
WarmUpTill
c969b21f93 Fix MacroSelection not properly unhiding first macro in selection 2024-08-16 06:49:27 +02:00
WarmUpTill
6ec40ef8e9 Cleanup 2024-08-16 06:49:27 +02:00
WarmUpTill
27859e83b3 Fix crash when creating macro which uses macro segment factories 2024-08-16 06:49:27 +02:00
WarmUpTill
263565700a Fix crashes related to widget highlighting 2024-08-16 06:49:27 +02:00
WarmUpTill
5bb10a4aef Resolve unused parameter warnings 2024-08-15 01:02:03 +02:00
WarmUpTill
ee72bb192c Improve corrupted installation detection 2024-08-15 01:02:03 +02:00
WarmUpTill
1a1028cbaa Paste copied macro action depending on cursor position
If cursor is above else action section paste it there.
If cursor is above regular action section paste it there.

In case of neither paste in original position.
2024-08-14 22:02:55 +02:00
WarmUpTill
2f4f8bcc74 Add IsCursorInWidgetArea() 2024-08-14 22:02:55 +02:00
WarmUpTill
4113615cd6 Add context menu to change custom label for macro segments 2024-08-14 22:02:55 +02:00
WarmUpTill
9522d7c0b4 Adapt to new OBS dock API
* The plugin will no longer attempt to restore dock positions on scene
  collection change.
* When a macro is being renamed the dock widget with the current name
  will be removed.
  A new dock with the new name will have to be opened manually.
* The status dock position will be when updating to a version containing
  this changei from an older version.
2024-08-14 21:51:42 +02:00
WarmUpTill
cc3ea79836 buildspec: Update OBS Studio dependency to 30.0.2 2024-08-14 21:51:42 +02:00
WarmUpTill
f72e802d00 Add API link 2024-08-14 21:51:42 +02:00
WarmUpTill
36201cbfb4 Add API to register new macro condition and action types 2024-08-14 21:51:42 +02:00
WarmUpTill
685e28d161 Make read and write functions of variables thread-safe 2024-08-14 21:51:42 +02:00
WarmUpTill
82d23dcf0e Exclude python cache directories 2024-08-14 21:51:42 +02:00
WarmUpTill
ad322d54f0 Fix crash in case of high frequency resize events
I am not sure if this could really happen in normal use.
Was discovered after implementing the script properties UI widgets.
2024-08-14 21:51:42 +02:00
WarmUpTill
8bac6fe829 Set Run condition default timeout to one second
The action is often used in combination with python scripts which
usually take longer to initialize than 100ms
2024-08-14 21:51:42 +02:00
WarmUpTill
ac7531decb Fix crash in MIDI initialization 2024-08-14 21:51:42 +02:00
Junior Anzolin
7af4969ec6 Add portuguese brazil translation (#1168)
* Add portuguese brazil translation
2024-08-13 19:36:27 +02:00
WarmUpTill
a088325968 Refresh pattern data when pattern file changes 2024-08-11 16:03:08 +02:00
WarmUpTill
57bc58be8e Add duration modifier tests 2024-08-11 16:03:08 +02:00
WarmUpTill
f9730b1bc2 Refactor DurationModifier to enable testing 2024-08-11 16:03:08 +02:00
WarmUpTill
d4025214e5 Cleanup duplicate "[advss]" log prefix 2024-08-11 16:03:08 +02:00
WarmUpTill
c431e6c31d Show warning if default action or condition cannot be created 2024-08-11 16:03:08 +02:00
WarmUpTill
9941cfe9b5 Install libproc2-dev in Linux build environment
This ensures that the plugin will attempt to load either libprocps-dev
or libproc2-dev libraries at runtime, instead of just the libprocps-dev
library

Dep packages taken from:
 * https://packages.debian.org/bookworm/libproc2-0
 * https://packages.debian.org/bookworm/libproc2-dev
2024-08-11 16:03:08 +02:00
WarmUpTill
15998012e5 Change default verbosity of busy loop detection log
This is mostly relevant when analysing issues, so it does not make sense
to potentially spam the OBS log with this message
2024-08-11 16:03:08 +02:00
WarmUpTill
bcb043f997 Add condition logic tests 2024-08-11 16:03:08 +02:00
WarmUpTill
74116382b1 Enable include during unit testing 2024-08-11 16:03:08 +02:00
WarmUpTill
24f33fb0d2 Refactor condition logic to enable testing 2024-08-11 16:03:08 +02:00
WarmUpTill
d80df57ef8 Increase default threshold value for image pattern matching
The previous default value could cause a lot of false positive
detections and thus cause confusion if the image detection is working
correctly or not.
2024-08-11 16:03:08 +02:00
WarmUpTill
240c47975c Rework Media condition
* Split state and time check
* Improve signal handling
* Prevent exponential memory groth when using the "any" / "all" check
* Improve layout
2024-08-07 21:22:45 +02:00
WarmUpTill
bedb3b8dc6 Change plugin folder name
Requested by lindenkron to ease development of a plugin manager
2024-07-09 21:21:10 +02:00
WarmUpTill
8032dde045 Prevent users from accidentally enabling action cooldowns 2024-07-09 21:21:10 +02:00
WarmUpTill
bfc51b2df5 Add option to set individual transform settings 2024-07-09 21:00:25 +02:00
WarmUpTill
2160da59f3 Add support to check values of individual transform settings 2024-07-09 21:00:25 +02:00
WarmUpTill
d3164e3ed6 Add TransformSettingSelection 2024-07-09 21:00:25 +02:00
107 changed files with 9377 additions and 1019 deletions

View File

@@ -456,7 +456,12 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
popd
;;
linux)
# Nothing to do for now
# 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
}
;;
}
}

6
.gitignore vendored
View File

@@ -7,6 +7,7 @@
!/cmake
!/data
!/deps
!/scripting
!/forms
!/lib
!/module
@@ -34,4 +35,7 @@
.DS_Store
# Exclude CMake build number cache
/cmake/.CMakeBuildNumber
/cmake/.CMakeBuildNumber
# Exclude python caches
/**/__pycache__

View File

@@ -96,6 +96,8 @@ target_sources(
lib/macro/macro-action-macro.hpp
lib/macro/macro-action-queue.cpp
lib/macro/macro-action-queue.hpp
lib/macro/macro-action-script.cpp
lib/macro/macro-action-script.hpp
lib/macro/macro-action-variable.cpp
lib/macro/macro-action-variable.hpp
lib/macro/macro-action.cpp
@@ -108,6 +110,8 @@ target_sources(
lib/macro/macro-condition-macro.hpp
lib/macro/macro-condition-queue.cpp
lib/macro/macro-condition-queue.hpp
lib/macro/macro-condition-script.cpp
lib/macro/macro-condition-script.hpp
lib/macro/macro-condition-tempvar.cpp
lib/macro/macro-condition-tempvar.hpp
lib/macro/macro-condition-variable.cpp
@@ -128,10 +132,14 @@ target_sources(
lib/macro/macro-ref.hpp
lib/macro/macro-run-button.cpp
lib/macro/macro-run-button.hpp
lib/macro/macro-script-handler.cpp
lib/macro/macro-script-handler.hpp
lib/macro/macro-segment-copy-paste.cpp
lib/macro/macro-segment-copy-paste.hpp
lib/macro/macro-segment-list.cpp
lib/macro/macro-segment-list.hpp
lib/macro/macro-segment-script.cpp
lib/macro/macro-segment-script.hpp
lib/macro/macro-segment-selection.cpp
lib/macro/macro-segment-selection.hpp
lib/macro/macro-segment.cpp
@@ -155,6 +163,8 @@ target_sources(
lib/queue/action-queue-tab.hpp
lib/utils/backup.cpp
lib/utils/backup.hpp
lib/utils/condition-logic.cpp
lib/utils/condition-logic.hpp
lib/utils/curl-helper.cpp
lib/utils/curl-helper.hpp
lib/utils/cursor-shape-changer.cpp
@@ -163,6 +173,8 @@ target_sources(
lib/utils/double-slider.hpp
lib/utils/duration-control.cpp
lib/utils/duration-control.hpp
lib/utils/duration-modifier.cpp
lib/utils/duration-modifier.hpp
lib/utils/duration.cpp
lib/utils/duration.hpp
lib/utils/export-symbol-helper.hpp
@@ -192,7 +204,6 @@ target_sources(
lib/utils/name-dialog.hpp
lib/utils/non-modal-dialog.cpp
lib/utils/non-modal-dialog.hpp
lib/utils/obs-dock.hpp
lib/utils/obs-module-helper.cpp
lib/utils/obs-module-helper.hpp
lib/utils/path-helpers.cpp
@@ -201,6 +212,9 @@ target_sources(
lib/utils/plugin-state-helpers.hpp
lib/utils/priority-helper.cpp
lib/utils/priority-helper.hpp
lib/utils/properties-view.cpp
lib/utils/properties-view.hpp
lib/utils/properties-view.moc.hpp
lib/utils/regex-config.cpp
lib/utils/regex-config.hpp
lib/utils/resizing-text-edit.cpp
@@ -264,6 +278,11 @@ target_sources(
# --- End of section ---
# Subfolder for advanced scene switcher plugins
set(ADVSS_PLUGIN_FOLDER "advanced-scene-switcher-plugins")
target_compile_definitions(
${LIB_NAME} PRIVATE ADVSS_PLUGIN_FOLDER=\"${ADVSS_PLUGIN_FOLDER}\")
include(cmake/common/advss_helpers.cmake)
setup_obs_lib_dependency(${LIB_NAME})
setup_obs_lib_dependency(${PROJECT_NAME})

View File

@@ -22,5 +22,6 @@ sudo snap install obs-studio
## Contributing
- If you wish to contribute code to the project, have a look at this [section](BUILDING.md) describing how to compile the plugin.
- You can add custom conditions and actions at runtime using the API described [here](https://github.com/WarmUpTill/SceneSwitcher/wiki/Scripting).
- You can optionally use [pre-commit](https://pre-commit.com) to automatically handle formatting.
- If you wish to contribute translations, feel free to submit pull requests for the corresponding files under `data/locale`.

View File

@@ -1,33 +1,33 @@
{
"dependencies": {
"obs-studio": {
"version": "29.1.2",
"version": "30.0.2",
"baseUrl": "https://github.com/obsproject/obs-studio/archive/refs/tags",
"label": "OBS sources",
"hashes": {
"macos": "215f1fa5772c5dd9f3d6e35b0cb573912b00320149666a77864f9d305525504b",
"windows-x64": "46d451f3f42b9d2c59339ec268165849c7b7904cdf1cc2a8d44c015815a9e37d"
"macos": "be12c3ad0a85713750d8325e4b1db75086223402d7080d0e3c2833d7c5e83c27",
"windows-x64": "970058c49322cfa9cd6d620abb393fed89743ba7e74bd9dbb6ebe0ea8141d9c7"
}
},
"prebuilt": {
"version": "2023-04-12",
"version": "2023-11-03",
"baseUrl": "https://github.com/obsproject/obs-deps/releases/download",
"label": "Pre-Built obs-deps",
"hashes": {
"macos": "9535c6e1ad96f7d49960251e85a245774088d48da1d602bb82f734b10219125a",
"windows-x64": "c13a14a1acc4224b21304d97b63da4121de1ed6981297e50496fbc474abc0503"
"macos": "90c2fc069847ec2768dcc867c1c63b112c615ed845a907dc44acab7a97181974",
"windows-x64": "d0825a6fb65822c993a3059edfba70d72d2e632ef74893588cf12b1f0d329ce6"
}
},
"qt6": {
"version": "2023-04-12",
"version": "2023-11-03",
"baseUrl": "https://github.com/obsproject/obs-deps/releases/download",
"label": "Pre-Built Qt6",
"hashes": {
"macos": "eb7614544ab4f3d2c6052c797635602280ca5b028a6b987523d8484222ce45d1",
"windows-x64": "4d39364b8a8dee5aa24fcebd8440d5c22bb4551c6b440ffeacce7d61f2ed1add"
"macos": "ba4a7152848da0053f63427a2a2cb0a199af3992997c0db08564df6f48c9db98",
"windows-x64": "bc57dedf76b47119a6dce0435a2f21b35b08c8f2948b1cb34a157320f77732d1"
},
"debugSymbols": {
"windows-x64": "f34ee5067be19ed370268b15c53684b7b8aaa867dc800b68931df905d679e31f"
"windows-x64": "fd8ecd1d8cd2ef049d9f4d7fb5c134f784836d6020758094855dfa98bd025036"
}
}
},

View File

@@ -8,14 +8,11 @@ if(BUILD_OUT_OF_TREE)
endif()
endif()
# Subfolder for advanced scene switcher plugins
set(_PLUGIN_FOLDER "adv-ss-plugins")
# --- MACOS section ---
if(OS_MACOS)
set(ADVSS_BUNDLE_DIR "advanced-scene-switcher.plugin")
set(ADVSS_BUNDLE_MODULE_DIR "${ADVSS_BUNDLE_DIR}/Contents/MacOS")
set(ADVSS_BUNDLE_PLUGIN_DIR ${ADVSS_BUNDLE_MODULE_DIR}/${_PLUGIN_FOLDER})
set(ADVSS_BUNDLE_PLUGIN_DIR ${ADVSS_BUNDLE_MODULE_DIR}/${ADVSS_PLUGIN_FOLDER})
function(install_advss_lib_helper target where)
install(
@@ -154,8 +151,8 @@ else()
function(install_advss_plugin target)
plugin_install_helper(
"${target}" "${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
"${_PLUGIN_FOLDER}")
"${target}" "${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
"${ADVSS_PLUGIN_FOLDER}")
if(NOT OS_WINDOWS)
set_target_properties(${target} PROPERTIES INSTALL_RPATH
"$ORIGIN:$ORIGIN/..")
@@ -169,12 +166,12 @@ else()
${dep}
RUNTIME
DESTINATION
"${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
"${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT
${dep}_Runtime
LIBRARY
DESTINATION
"${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
"${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT
${dep}_Runtime
NAMELINK_COMPONENT
@@ -185,13 +182,13 @@ else()
${dep}
RUNTIME
DESTINATION
"${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
"${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT
obs_${dep}
EXCLUDE_FROM_ALL
LIBRARY
DESTINATION
"${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
"${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT
obs_${dep}
EXCLUDE_FROM_ALL)
@@ -214,18 +211,18 @@ else()
install(
FILES "${dep}"
DESTINATION "${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
DESTINATION "${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT ${_DEP_NAME}_Runtime
DESTINATION "${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
DESTINATION "${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT ${_DEP_NAME}_Runtime
NAMELINK_COMPONENT ${_DEP_NAME}_Development)
install(
FILES "${dep}"
DESTINATION "${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
DESTINATION "${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT obs_${_DEP_NAME}
EXCLUDE_FROM_ALL
DESTINATION "${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
DESTINATION "${OBS_PLUGIN_DESTINATION}/${ADVSS_PLUGIN_FOLDER}"
COMPONENT obs_${_DEP_NAME}
EXCLUDE_FROM_ALL)
@@ -335,7 +332,6 @@ function(install_advss_plugin_dependency)
if(NOT PARSED_ARGS_TARGET)
message(FATAL_ERROR "You must provide a target")
endif()
set(_PLUGIN_FOLDER "adv-ss-plugins")
foreach(_DEPENDENCY ${PARSED_ARGS_DEPENDENCIES})
if(EXISTS ${_DEPENDENCY})
install_advss_plugin_dependency_file(${PARSED_ARGS_TARGET} ${_DEPENDENCY})

View File

@@ -159,7 +159,7 @@ AdvSceneSwitcher.condition.media.source="Quelle"
AdvSceneSwitcher.condition.media.anyOnScene="Beliebige Medienquelle in"
AdvSceneSwitcher.condition.media.allOnScene="Alle Medienquellen in"
AdvSceneSwitcher.condition.media.inconsistencyInfo="Leider verhalten sich nicht alle Medien-Quell-Typen gleich (z.B. Medien-Quelle vs. VLC-Video-Quelle \"Gestoppt\"-Status).\nBitte experimentieren, was in eurem Setup funktioniert!"
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}Status ist{{states}}und{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.media.layout.legacy="{{sourceTypes}}{{mediaSources}}{{scenes}}{{checkTypes}}Status ist{{states}}und{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Video"
AdvSceneSwitcher.condition.video.condition.match="entspricht genau"
AdvSceneSwitcher.condition.video.condition.differ="stimmt nicht überein"
@@ -321,11 +321,8 @@ AdvSceneSwitcher.condition.date.entry.nextMatchDate="Nächster Treffer bei: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} Bei Wiederholung ausgewähltes Datum auf Wiederholungsdatum aktualisieren"
AdvSceneSwitcher.condition.sceneTransform="Szenenelement transformieren"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Transformation erhalten"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="Auf{{scenes}}{{sources}}{{types}}"
AdvSceneSwitcher.condition.sceneTransform.entry.type.matches="entspricht Transformation"
AdvSceneSwitcher.condition.sceneTransform.entry.type.changed="hat sich geändert"
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}"
AdvSceneSwitcher.condition.sceneTransform.condition.match="entspricht Transformation"
AdvSceneSwitcher.condition.sceneTransform.condition.changed="hat sich geändert"
AdvSceneSwitcher.condition.transition="Übergang"
AdvSceneSwitcher.condition.transition.type.current="Aktueller Übergangstyp ist"
AdvSceneSwitcher.condition.transition.type.duration="Aktuelle Übergangsdauer beträgt"
@@ -498,7 +495,7 @@ AdvSceneSwitcher.action.sceneOrder.type.movePosition="An Position verschieben"
AdvSceneSwitcher.action.sceneOrder.entry="Auf{{scenes}}{{actions}}{{sources}}{{position}}"
AdvSceneSwitcher.action.sceneTransform="Szenenelement transformieren"
AdvSceneSwitcher.action.sceneTransform.getTransform="Transformation erhalten"
AdvSceneSwitcher.action.sceneTransform.entry="Auf{{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.sceneTransform.entry="Auf{{scenes}}{{action}}{{rotation}}{{sources}}{{settingSelection}}{{singleSettingValue}}"
AdvSceneSwitcher.action.file="Datei"
AdvSceneSwitcher.action.file.type.write="Schreiben"
AdvSceneSwitcher.action.file.type.append="Anhängen"

View File

@@ -1,7 +1,7 @@
AdvSceneSwitcher.pluginName="Advanced Scene Switcher"
AdvSceneSwitcher.windowTitle="Advanced Scene Switcher"
; General Tab
# General Tab
AdvSceneSwitcher.generalTab.title="General"
AdvSceneSwitcher.generalTab.status="Status"
AdvSceneSwitcher.generalTab.status.hotkeytips="Hotkeys can be defined in the OBS settings"
@@ -37,6 +37,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Disable UI hints"
AdvSceneSwitcher.generalTab.generalBehavior.comboBoxFilterDisable="Disable filtering by typing in drop down menus"
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailure="Display warning if plugins cannot be loaded"
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage="<html><body>Loading of the following plugin libraries was unsuccessful, which could result in some Advanced Scene Switcher functions not being available:%1Check the OBS logs for details.<br>This message can be disabled on the General tab.</body></html>"
AdvSceneSwitcher.generalTab.generalBehavior.warnCorruptedInstallMessage="The plugin installation seems to be corrupted and might crash!\nPlease make sure the plugin was installed correctly!"
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="Hide tabs which can be represented via macros"
AdvSceneSwitcher.generalTab.matchBehavior="Match behavior"
AdvSceneSwitcher.generalTab.priority="Priority"
@@ -69,7 +70,7 @@ AdvSceneSwitcher.generalTab.transitionOverride="Set transition overrides"
AdvSceneSwitcher.generalTab.adjustActiveTransitionType="Change active transition type"
AdvSceneSwitcher.generalTab.transitionBehaviorSelectionError="At least one option must be enabled:\n\n - Use transition overrides\n\n - Change active transition type"
; Variables Tab
# Variables Tab
AdvSceneSwitcher.variableTab.title="Variables"
AdvSceneSwitcher.variableTab.help="Variables can be used in many places throughout the plugin.\n\nClick on the highlighted plus symbol to add a new variable."
AdvSceneSwitcher.variableTab.variableAddButton.tooltip="Add new variable"
@@ -88,7 +89,7 @@ AdvSceneSwitcher.variableTab.lastChanged.text="%1 seconds ago"
AdvSceneSwitcher.variableTab.lastChanged.text.none="No change since launch"
AdvSceneSwitcher.variableTab.lastChanged.tooltip="Times changed: %1\n\nPrevious value: %2"
; Action Queue Tab
# Action Queue Tab
AdvSceneSwitcher.actionQueueTab.title="Action Queues"
AdvSceneSwitcher.actionQueueTab.help="Action queues are executed sequentially but in parallel to the reset of the macro system.\nThe first action added to the queue will be the first one to be processed.\n\nClick on the highlighted plus symbol to add a new queue."
AdvSceneSwitcher.actionQueueTab.queueAddButton.tooltip="Add new action queue"
@@ -102,7 +103,7 @@ AdvSceneSwitcher.actionQueueTab.no="No"
AdvSceneSwitcher.actionQueueTab.removeSingleQueuePopup.text="Are you sure you want to remove \"%1\"?"
AdvSceneSwitcher.actionQueueTab.removeMultipleQueuesPopup.text="Are you sure you want to remove %1 action queues?"
; Websocket Connections Tab
# Websocket Connections Tab
AdvSceneSwitcher.websocketConnectionTab.title="Websocket Connections"
AdvSceneSwitcher.websocketConnectionTab.help="Websocket connections can be used to communicate with other OBS instances or programs.\n\nClick on the highlighted plus symbol to add a new connection."
AdvSceneSwitcher.websocketConnectionTab.websocketConnectionAddButton.tooltip="Add new webscoket connection"
@@ -116,7 +117,7 @@ AdvSceneSwitcher.websocketConnectionTab.protocol.no="No"
AdvSceneSwitcher.websocketConnectionTab.removeSingleConnectionPopup.text="Are you sure you want to remove \"%1\"?"
AdvSceneSwitcher.websocketConnectionTab.removeMultipleConnectionsPopup.text="Are you sure you want to remove %1 connections?"
; Twitch Connections Tab
# Twitch Connections Tab
AdvSceneSwitcher.twitchConnectionTab.title="Twitch Connections"
AdvSceneSwitcher.twitchConnectionTab.help="Twitch connections can be used to use Twitch events as triggers to execute actions or perform actions on the linked Twitch account.\n\nClick on the highlighted plus symbol to add a new connection."
AdvSceneSwitcher.twitchConnectionTab.twitchConnectionAddButton.tooltip.tooltip="Add new Twitch connection"
@@ -129,7 +130,7 @@ AdvSceneSwitcher.twitchConnectionTab.no="No"
AdvSceneSwitcher.twitchConnectionTab.removeSingleConnectionPopup.text="Are you sure you want to remove \"%1\"?"
AdvSceneSwitcher.twitchConnectionTab.removeMultipleConnectionsPopup.text="Are you sure you want to remove %1 Twitch connections?"
; Macro Tab
# Macro Tab
AdvSceneSwitcher.macroTab.title="Macro"
AdvSceneSwitcher.macroTab.macros="Macros"
AdvSceneSwitcher.macroTab.priorityWarning="Note: It is recommended to configure macros to be the highest priority functionality.\nThis setting can be changed on the General tab."
@@ -175,10 +176,13 @@ AdvSceneSwitcher.macroTab.collapseAll="Collapse all"
AdvSceneSwitcher.macroTab.maximize="Maximize"
AdvSceneSwitcher.macroTab.minimize="Minimize"
AdvSceneSwitcher.macroTab.segment.useCustomLabel="Use custom label"
AdvSceneSwitcher.macroTab.segment.customLabelRename="Rename custom label"
AdvSceneSwitcher.macroTab.segment.defaultCustomLabel="My label"
AdvSceneSwitcher.macroTab.segment.setCustomLabel="Set label ..."
AdvSceneSwitcher.macroTab.segment.copy="Copy"
AdvSceneSwitcher.macroTab.segment.paste="Paste"
AdvSceneSwitcher.macroTab.segment.pasteAction="Paste as action"
AdvSceneSwitcher.macroTab.segment.pasteElseAction="Paste as else-action"
AdvSceneSwitcher.macroTab.highlightSettings="Visual settings"
AdvSceneSwitcher.macroTab.hotkeySettings="Hotkey settings"
AdvSceneSwitcher.macroTab.generalSettings="General settings"
@@ -238,11 +242,11 @@ AdvSceneSwitcher.macroDock.run="Run"
AdvSceneSwitcher.macroDock.statusLabel.true="Conditions are true."
AdvSceneSwitcher.macroDock.statusLabel.false="Conditions are false."
; Macro List
# Macro List
AdvSceneSwitcher.macroList.deleted="deleted"
AdvSceneSwitcher.macroList.duplicate="\"%1\" is alreay selected!"
; Macro Logic
# Macro Logic
AdvSceneSwitcher.logic.none="Ignore entry"
AdvSceneSwitcher.logic.and="And"
AdvSceneSwitcher.logic.or="Or"
@@ -251,7 +255,8 @@ AdvSceneSwitcher.logic.orNot="Or not"
AdvSceneSwitcher.logic.rootNone="If"
AdvSceneSwitcher.logic.not="If not"
; Macro Conditions
# Macro Conditions
AdvSceneSwitcher.condition.unknown="Unknown condition"
AdvSceneSwitcher.condition.audio="Audio"
AdvSceneSwitcher.condition.audio.state.below="below"
AdvSceneSwitcher.condition.audio.state.exact="exactly"
@@ -307,11 +312,14 @@ AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{condition
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFileContent}}"
AdvSceneSwitcher.condition.media="Media"
AdvSceneSwitcher.condition.media.checkType.state="State matches"
AdvSceneSwitcher.condition.media.checkType.time="Time restriction matches"
AdvSceneSwitcher.condition.media.source="Source"
AdvSceneSwitcher.condition.media.anyOnScene="Any media source on"
AdvSceneSwitcher.condition.media.allOnScene="All media sources on"
AdvSceneSwitcher.condition.media.inconsistencyInfo="Unfortunately not all media source types behave the same (e.g. Media Source vs. VLC Video Source \"Stopped\" state).\nSo please experiment what works for your setup!"
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}state is{{states}}and{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.media.layout="{{sourceTypes}}{{mediaSources}}{{scenes}}{{checkTypes}}{{states}}{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.media.layout.legacy="{{sourceTypes}}{{mediaSources}}{{scenes}}{{checkTypes}}state is{{states}}and{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Video"
AdvSceneSwitcher.condition.video.condition.match="exactly matches"
AdvSceneSwitcher.condition.video.condition.differ="does not match"
@@ -531,11 +539,17 @@ AdvSceneSwitcher.condition.date.entry.nextMatchDate="Next match at: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}}On repeat update selected date to repeat date"
AdvSceneSwitcher.condition.sceneTransform="Scene item transform"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="On{{scenes}}{{sources}}{{types}}"
AdvSceneSwitcher.condition.sceneTransform.entry.type.matches="matches transform"
AdvSceneSwitcher.condition.sceneTransform.entry.type.changed="has changed"
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}}{{getSettings}}"
AdvSceneSwitcher.condition.sceneTransform.getCurrentValue="Get current value"
AdvSceneSwitcher.condition.sceneTransform.settingsType.all="All"
AdvSceneSwitcher.condition.sceneTransform.settingsType.single="Single"
AdvSceneSwitcher.condition.sceneTransform.condition.match="matches"
AdvSceneSwitcher.condition.sceneTransform.condition.changed="has changed"
AdvSceneSwitcher.condition.sceneTransform.compare.less="less"
AdvSceneSwitcher.condition.sceneTransform.compare.equal="equal"
AdvSceneSwitcher.condition.sceneTransform.compare.more="larger than"
AdvSceneSwitcher.condition.sceneTransform.entry="On{{scenes}}{{settingsType}}transform setting of{{sources}}{{conditions}}"
AdvSceneSwitcher.condition.sceneTransform.entry.singleSettingCompare="{{setting}}{{compare}}{{singleSettingValue}}"
AdvSceneSwitcher.condition.sceneTransform.entry.options="{{regex}}{{getSettings}}{{getCurrentValue}}"
AdvSceneSwitcher.condition.transition="Transition"
AdvSceneSwitcher.condition.transition.type.current="Current transition type is"
AdvSceneSwitcher.condition.transition.type.duration="Current transition duration is"
@@ -714,7 +728,8 @@ AdvSceneSwitcher.condition.usb.productName="Product Name:"
AdvSceneSwitcher.condition.usb.serialNumber="Serial Number:"
AdvSceneSwitcher.condition.noDevicesFoundWarning="No USB devices detected!\nThe plugin might not have the required permissions to check for USB devices."
; Macro Actions
# Macro Actions
AdvSceneSwitcher.action.unknown="Unknown action"
AdvSceneSwitcher.action.scene="Switch scene"
AdvSceneSwitcher.action.scene.type.program="Program"
AdvSceneSwitcher.action.scene.type.preview="Preview"
@@ -904,7 +919,8 @@ AdvSceneSwitcher.action.sceneOrder.type.moveBottom="Move to bottom"
AdvSceneSwitcher.action.sceneOrder.type.movePosition="Move to position"
AdvSceneSwitcher.action.sceneOrder.entry="On{{scenes}}{{actions}}{{sources}}{{position}}"
AdvSceneSwitcher.action.sceneTransform="Scene item transform"
AdvSceneSwitcher.action.sceneTransform.type.manual="Transform"
AdvSceneSwitcher.action.sceneTransform.type.manual="Set transform string"
AdvSceneSwitcher.action.sceneTransform.type.setSingleSetting="Set transform setting"
AdvSceneSwitcher.action.sceneTransform.type.reset="Reset transform"
AdvSceneSwitcher.action.sceneTransform.type.rotate="Rotate by"
AdvSceneSwitcher.action.sceneTransform.type.flipHorizontal="Flip horizontally"
@@ -915,7 +931,8 @@ AdvSceneSwitcher.action.sceneTransform.type.centerToScreen="Center to screen"
AdvSceneSwitcher.action.sceneTransform.type.centerVertically="Center vertically"
AdvSceneSwitcher.action.sceneTransform.type.centerHorizontally="Center horizontally"
AdvSceneSwitcher.action.sceneTransform.getTransform="Get transform"
AdvSceneSwitcher.action.sceneTransform.entry="On{{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.sceneTransform.getCurrentValue="Get current value"
AdvSceneSwitcher.action.sceneTransform.entry="On{{scenes}}{{action}}{{rotation}}{{sources}}{{settingSelection}}{{singleSettingValue}}"
AdvSceneSwitcher.action.file="File"
AdvSceneSwitcher.action.file.type.write="Write"
AdvSceneSwitcher.action.file.type.append="Append"
@@ -1102,7 +1119,7 @@ AdvSceneSwitcher.action.log="Log"
AdvSceneSwitcher.action.log.placeholder="My log message!"
AdvSceneSwitcher.action.log.entry="Write to OBS log:{{logMessage}}"
; Hotkey
# Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher"
AdvSceneSwitcher.hotkey.stopSwitcherHotkey="Stop the Advanced Scene Switcher"
AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="Toggle Start/Stop for the Advanced Scene Switcher"
@@ -1796,6 +1813,9 @@ AdvSceneSwitcher.tempVar.usb.vendorName="Vendor Name"
AdvSceneSwitcher.tempVar.usb.productName="Product Name"
AdvSceneSwitcher.tempVar.usb.serialNumber="Serial Number"
AdvSceneSwitcher.tempVar.transform.setting="Setting value"
AdvSceneSwitcher.tempVar.transform.settings="Settings string"
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene"
@@ -1840,6 +1860,20 @@ AdvSceneSwitcher.settings.suffix.type.frameRate=" (Frame rate)"
AdvSceneSwitcher.settings.suffix.type.group=" (Group)"
AdvSceneSwitcher.settings.suffix.type.colorAlpha=" (Color alpha)"
AdvSceneSwitcher.setting.select="--select setting--"
AdvSceneSwitcher.setting.transform.alignment="Positional Alignment"
AdvSceneSwitcher.setting.transform.cropBottom="[Crop]Bottom"
AdvSceneSwitcher.setting.transform.cropLeft="[Crop]Left"
AdvSceneSwitcher.setting.transform.cropRight="[Crop]Right"
AdvSceneSwitcher.setting.transform.cropTop="[Crop]Top"
AdvSceneSwitcher.setting.transform.boundingBoxType="Bounding Box Type"
AdvSceneSwitcher.setting.transform.boundingBoxAlign="Alignment in Bounding Box"
AdvSceneSwitcher.setting.transform.boundingBoxSize="Bounding Box Size"
AdvSceneSwitcher.setting.transform.rotation="Rotation"
AdvSceneSwitcher.setting.transform.scale="Scale"
AdvSceneSwitcher.setting.transform.size="Size"
AdvSceneSwitcher.setting.transform.position="Position"
AdvSceneSwitcher.setting.transform.height="Height"
AdvSceneSwitcher.setting.transform.width="Width"
AdvSceneSwitcher.sceneItemSelection.configure="Configure scene item selection type"
@@ -1889,8 +1923,44 @@ AdvSceneSwitcher.noSettingsButtons="No buttons found!"
AdvSceneSwitcher.clearBufferOnMatch="Clear message buffer when matching message was found"
; Legacy tabs below - please don't waste your time adding translations for these :)
; Transition Tab
AdvSceneSwitcher.script.settings="Settings"
AdvSceneSwitcher.script.timeout="Script timeout:{{timeout}}"
# This secion is copied from the OBS locale files
# commonly shared locale
Browse="Browse"
Show="Show"
Hide="Hide"
# properties window
Basic.PropertiesWindow="Properties for '%1'"
Basic.PropertiesWindow.AutoSelectFormat="%1 (autoselect: %2)"
Basic.PropertiesWindow.SelectColor="Select color"
Basic.PropertiesWindow.SelectFont="Select font"
Basic.PropertiesWindow.SelectFont.WindowTitle="Pick a Font"
Basic.PropertiesWindow.ConfirmTitle="Settings Changed"
Basic.PropertiesWindow.Confirm="There are unsaved changes. Do you want to keep them?"
Basic.PropertiesWindow.NoProperties="No properties available"
Basic.PropertiesWindow.AddFiles="Add Files"
Basic.PropertiesWindow.AddDir="Add Directory"
Basic.PropertiesWindow.AddURL="Add Path/URL"
Basic.PropertiesWindow.AddEditableListDir="Add directory to '%1'"
Basic.PropertiesWindow.AddEditableListFiles="Add files to '%1'"
Basic.PropertiesWindow.AddEditableListEntry="Add entry to '%1'"
Basic.PropertiesWindow.EditEditableListEntry="Edit entry from '%1'"
# properties view
Basic.PropertiesView.FPS.Simple="Simple FPS Values"
Basic.PropertiesView.FPS.Rational="Rational FPS Values"
Basic.PropertiesView.FPS.ValidFPSRanges="Valid FPS Ranges:"
Basic.PropertiesView.UrlButton.Text="Open this link in your default web browser?"
Basic.PropertiesView.UrlButton.Text.Url="URL: %1"
Basic.PropertiesView.UrlButton.OpenUrl="Open URL"
# Legacy tabs below - please don't waste your time adding translations for these :)
# Transition Tab
AdvSceneSwitcher.transitionTab.title="Transition"
AdvSceneSwitcher.transitionTab.transitionForAToB="Use transition for automated scene switch from scene A to scene B"
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>These settings <span style=\"font-style:italic;\">only</span> affect transitions caused by the scene switcher - Check out <a href=\"https://obsproject.com/forum/resources/transition-table.1174/\"><span style=\" text-decoration: underline; color:#268bd2;\">Transition Table</span></a> if you want to configure this for manual scene changes.<br/>Settings defined here take priority over transition settings configured elsewhere in the scene switcher.<br/><br/>Click the plus symbol below to add a new entry.</p></body></html>"
@@ -1901,7 +1971,7 @@ AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="Click on the plus symbol
AdvSceneSwitcher.transitionTab.defaultTransition.delay="Switch transition{{defTransitionDelay}}after scene change."
AdvSceneSwitcher.transitionTab.defaultTransition.delay.help="The delay is used to avoid cancelled scene switches, which can happen if the transition type is changed while a transition is still ongoing."
; Pause Scenes Tab
# Pause Scenes Tab
AdvSceneSwitcher.pauseTab.title="Pause"
AdvSceneSwitcher.pauseTab.pauseOnScene="Pause the Scene Switcher on scene"
AdvSceneSwitcher.pauseTab.pauseInFocus1="Pause the Scene Switcher when "
@@ -1912,7 +1982,7 @@ AdvSceneSwitcher.pauseTab.pauseTargetAll="all"
AdvSceneSwitcher.pauseTab.pauseEntry="Pause{{pauseTargets}}checks when{{pauseTypes}}{{scenes}}{{windows}}"
AdvSceneSwitcher.pauseTab.help="On this tab you can configure to pause individual switching methods if a scene is active or window is in focus.\n\nClick on the highlighted plus symbol to continue."
; Window Title Tab
# Window Title Tab
AdvSceneSwitcher.windowTitleTab.title="Title"
AdvSceneSwitcher.windowTitleTab.regexrDescription="<html><head/><body><p>Enter either direct window titles or valid regex. You can check syntax and matches for regular expressions using <a href=\"https://regexr.com\"><span style=\" text-decoration: underline; color:#268bd2;\">RegExr</span></a></p></body></html>"
AdvSceneSwitcher.windowTitleTab.stayInFocus1="Ignore this window name"
@@ -1924,14 +1994,14 @@ AdvSceneSwitcher.windowTitleTab.entry="{{windows}}{{scenes}}{{transitions}}{{ful
AdvSceneSwitcher.windowTitleTab.windowsHelp="Switch scenes based on the window title of running applications.\nThe following additional conditions can be selected:\nThe window is Fullscreen\nThe window is maximized\nThe window is focused\n\nClick on the highlighted plus symbol to continue."
AdvSceneSwitcher.windowTitleTab.ignoreWindowsHelp="If a window title is ignored the scene switcher will act as if the previously selected window is still in focus.\nThis will allow you to avoid scene switches, if you frequently switch to a different window, which shall not trigger a scene change.\n\nChoose a window or enter a window title above and click on the plus symbol below to add it to the list."
; Executable Tab
# Executable Tab
AdvSceneSwitcher.executableTab.title="Executable"
AdvSceneSwitcher.executableTab.implemented="Implemented by dasOven"
AdvSceneSwitcher.executableTab.requiresFocus="only if focused"
AdvSceneSwitcher.executableTab.entry="When{{processes}}is running switch to{{scenes}}using{{transitions}}{{requiresFocus}}"
AdvSceneSwitcher.executableTab.help="This tab will allow you to automatically switch scenes if a process is running.\nThis can be useful in situations where the window name could change or is not known.\n\nClick on the highlighted plus symbol to continue."
; Screen Region Tab
# Screen Region Tab
AdvSceneSwitcher.screenRegionTab.title="Region"
AdvSceneSwitcher.screenRegionTab.currentPosition="Cursor is currently at:"
AdvSceneSwitcher.screenRegionTab.showGuideFrames="Show guide frames"
@@ -1940,7 +2010,7 @@ AdvSceneSwitcher.screenRegionTab.excludeScenes.None="No selection"
AdvSceneSwitcher.screenRegionTab.entry="If cursor is in{{minX}}{{minY}}x{{maxX}}{{maxY}}switch to{{scenes}}using{{transitions}}unless in{{excludeScenes}}"
AdvSceneSwitcher.screenRegionTab.help="This tab will allow you to automatically switch scenes based on the current position of your mouse cursor.\n\nClick on the highlighted plus symbol to continue."
; Media Tab
# Media Tab
AdvSceneSwitcher.mediaTab.title="Media"
AdvSceneSwitcher.mediaTab.implemented="Implemented by Exeldro"
AdvSceneSwitcher.mediaTab.states.none="None"
@@ -1961,7 +2031,7 @@ AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger="Time remaining longer"
AdvSceneSwitcher.mediaTab.entry="When{{mediaSources}}state is{{states}}and{{timeRestrictions}}{{time}}switch to{{scenes}}using{{transitions}}"
AdvSceneSwitcher.mediaTab.help="This tab will allow you to switch scenes based on the states of media sources.\nFor example, you can automatically switch back to the previous scene once the selected media sourced ended its playback.\n\nClick on the highlighted plus symbol to continue."
; File Tab
# File Tab
AdvSceneSwitcher.fileTab.title="File"
AdvSceneSwitcher.fileTab.readWriteSceneFile="Read / write scene from / to file"
AdvSceneSwitcher.fileTab.currentSceneOutputFile="Write the name of the current scene to this file:"
@@ -1986,13 +2056,13 @@ AdvSceneSwitcher.fileTab.entry2="{{matchText}}"
AdvSceneSwitcher.fileTab.entry3="{{useRegex}}{{checkModificationDate}}{{checkFileContent}}"
AdvSceneSwitcher.fileTab.help="This tab will allow you to automatically switch scenes based on the content of remote or local files.\n\nClick on the highlighted plus symbol to continue."
; Random Tab
# Random Tab
AdvSceneSwitcher.randomTab.title="Random"
AdvSceneSwitcher.randomTab.randomDisabledWarning="Functionality disabled - To activate select \"If no switch condition is met switch to any scene in Random tab\" on General tab"
AdvSceneSwitcher.randomTab.entry="If no switch condition is met switch to{{scenes}}using{{transitions}}for{{delay}}"
AdvSceneSwitcher.randomTab.help="The scene switcher will randomly choose an entry on this tab to switch to for the configured time.\nNote that the same entry will not be chosen twice in a row.\n\nClick on the highlighted plus symbol to continue."
; Time Tab
# Time Tab
AdvSceneSwitcher.timeTab.title="Time"
AdvSceneSwitcher.timeTab.anyDay="On any day"
AdvSceneSwitcher.timeTab.mondays="Mondays"
@@ -2007,14 +2077,14 @@ AdvSceneSwitcher.timeTab.afterstart.tip="The time relative to the start of strea
AdvSceneSwitcher.timeTab.entry="{{triggers}}at{{time}}switch to{{scenes}}using{{transitions}}"
AdvSceneSwitcher.timeTab.help="This tab will allow you to automatically switch to a different scene based on the current local time.\n\nNote that the scene switcher will only switch scenes at the exact time you specified.\nMake sure you have configured the priority settings on the General tab to your liking so the selected time point will not be missed due to other switching methods having a higher priority.\n\nClick on the highlighted plus symbol to continue."
; Idle Tab
# Idle Tab
AdvSceneSwitcher.idleTab.title="Idle"
AdvSceneSwitcher.idleTab.enable="Enable Idle Detection"
AdvSceneSwitcher.idleTab.idleswitch="After{{duration}}of no keyboard or mouse inputs switch to scene{{scenes}}using the{{transitions}}"
AdvSceneSwitcher.idleTab.dontSwitchIfFocus1="Do not switch if"
AdvSceneSwitcher.idleTab.dontSwitchIfFocus2="is in focus"
; Scene Sequence Tab
# Scene Sequence Tab
AdvSceneSwitcher.sceneSequenceTab.title="Sequence"
AdvSceneSwitcher.sceneSequenceTab.description="A sequence of automatic scene switches can be cancelled by either pausing/stopping the scene switcher or manually switching to a different scene"
AdvSceneSwitcher.sceneSequenceTab.save="Save scene sequences to file"
@@ -2031,7 +2101,7 @@ AdvSceneSwitcher.sceneSequenceTab.extendEdit="Extend Sequence"
AdvSceneSwitcher.sceneSequenceTab.extendEntry="After{{delay}}switch to{{scenes}}using{{transitions}}"
AdvSceneSwitcher.sceneSequenceTab.help="This tab will allow you to automatically switch to a different scene if a scene was active for a configured period of time.\nFor example, you could automatically cycle back and forth between two scenes automatically.\n\nClick on the highlighted plus symbol to continue."
; Audio Tab
# Audio Tab
AdvSceneSwitcher.audioTab.title="Audio"
AdvSceneSwitcher.audioTab.condition.above="above"
AdvSceneSwitcher.audioTab.condition.below="below"
@@ -2041,7 +2111,7 @@ AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="If multiple entries match
AdvSceneSwitcher.audioTab.multiMatchfallback="... for{{duration}}seconds switch to{{scenes}}using{{transitions}}"
AdvSceneSwitcher.audioTab.help="This tab will allow you to switch scenes based on the volume of sources.\nFor example, you could automatically switch to a different scene if the volume of your microphone reaches a certain threshold.\n\nClick on the highlighted plus symbol to continue."
; Video Tab
# Video Tab
AdvSceneSwitcher.videoTab.title="Video"
AdvSceneSwitcher.videoTab.getScreenshot="Get screenshot for selected entry"
AdvSceneSwitcher.videoTab.getScreenshotHelp="Get Screenshot of the currently selected entry's video source and automatically set it as the target image"
@@ -2054,7 +2124,7 @@ AdvSceneSwitcher.videoTab.ignoreInactiveSource="unless source is inactive"
AdvSceneSwitcher.videoTab.entry="When{{videoSources}}{{condition}}{{filePath}}{{browseButton}}for{{duration}}switch to{{scenes}}using{{transitions}}{{ignoreInactiveSource}}"
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>This tab will allow you to switch scenes based on the current video output of selected sources.<br/>Make sure to check out <a href=\"https://obsproject.com/forum/resources/pixel-match-switcher.1202/\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a> for an even better implementation of this functionality.<br/><br/> Click on the highlighted plus symbol to continue.</p></body></html>"
; Network Tab
# Network Tab
AdvSceneSwitcher.networkTab.title="Network"
AdvSceneSwitcher.networkTab.description="This tab will allow you to remotely control the active scene of another OBS instance.\nPlease note that the scene names have to match exactly on all OBS instances."
AdvSceneSwitcher.networkTab.warning="Running the server outside of a local network will allow third parties to read the active scene."
@@ -2079,7 +2149,7 @@ AdvSceneSwitcher.networkTab.client.status.connecting="Connecting"
AdvSceneSwitcher.networkTab.client.status.connected="Connected"
AdvSceneSwitcher.networkTab.client.reconnect="Force reconnect"
; Scene Group Tab
# Scene Group Tab
AdvSceneSwitcher.sceneGroupTab.title="Scene Group"
AdvSceneSwitcher.sceneGroupTab.list="Scene Groups"
AdvSceneSwitcher.sceneGroupTab.edit="Edit Scene Groups"
@@ -2099,7 +2169,7 @@ AdvSceneSwitcher.sceneGroupTab.exists="Scene Group or Scene name exists already"
AdvSceneSwitcher.sceneGroupTab.help="Scene Groups can be selected as a target just like a regular scene.\n\nAs the name suggests a scene group is a collection of multiple scenes.\nThe scene group will advance through the list of its assigned scenes depending on the configured settings, which can be found on the right side.\n\nYou can configure the scene group to advance to the next scene in the list:\nAfter a number of times the scene group is selected as a target.\nAfter a certain amount of time has passed.\nOr randomly.\n\nFor example, a scene group containing the scenes ...\nScene 1\nScene 2\nScene 3 \n... will activate \"Scene 1\" the first time it is selected as a target.\nThe second time it will activate \"Scene 2\".\nThe remaining times \"Scene 3\" will be activated.\n\nClick the highlighted plus symbol below to add a new scene group."
AdvSceneSwitcher.sceneGroupTab.scenes.help="Select the scene group you want to modify on the left.\n\nSelect a scene to add to this scene group by selecting the scene above and clicking the plus symbol below.\n\nA scene can be added multiple times to the same scene group."
; Scene Trigger Tab
# Scene Trigger Tab
AdvSceneSwitcher.sceneTriggerTab.title="Scene Triggers"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none="--select trigger--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive="is active"

View File

@@ -128,7 +128,7 @@ AdvSceneSwitcher.condition.media="Medios"
AdvSceneSwitcher.condition.media.anyOnScene="Cualquier fuente multimedia activada"
AdvSceneSwitcher.condition.media.allOnScene="Todas las fuentes de medios activadas"
AdvSceneSwitcher.condition.media.inconsistencyInfo="Desafortunadamente, no todos los tipos de fuentes de medios se comportan de la misma manera (p. ej., fuente de medios frente a estado \"Detenido\" de fuente de video VLC).\n¡Así que experimente lo que funciona para su configuración!"
AdvSceneSwitcher.condition.media.entry="El estado de{{sourceTypes}}{{mediaSources}}{{scenes}}es{{states}}y{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.media.layout.legacy="El estado de{{sourceTypes}}{{mediaSources}}{{scenes}}{{checkTypes}}es{{states}}y{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Video"
AdvSceneSwitcher.condition.video.condition.match="coincide exactamente"
AdvSceneSwitcher.condition.video.condition.differ="no coincide"
@@ -269,10 +269,7 @@ AdvSceneSwitcher.condition.date.entry.nextMatchDate="Próxima coincidencia en: %
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} Al repetir actualizar la fecha seleccionada para repetir la fecha"
AdvSceneSwitcher.condition.sceneTransform="Transformar elemento de escena"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Obtener transformación"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="En{{scenes}}{{sources}}{{types}}"
AdvSceneSwitcher.condition.sceneTransform.entry.type.matches="coincide con la transformación"
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}"
AdvSceneSwitcher.condition.sceneTransform.condition.match="coincide con la transformación"
AdvSceneSwitcher.condition.transition="Transición"
AdvSceneSwitcher.condition.transition.type.current="El tipo de transición actual es"
AdvSceneSwitcher.condition.transition.type.duration="La duración de la transición actual es"
@@ -415,7 +412,7 @@ AdvSceneSwitcher.action.sceneOrder.type.movePosition="Mover a la posición"
AdvSceneSwitcher.action.sceneOrder.entry="En{{scenes}}{{actions}}{{sources}}{{position}}"
AdvSceneSwitcher.action.sceneTransform="Transformar elemento de escena"
AdvSceneSwitcher.action.sceneTransform.getTransform="Obtener transformación"
AdvSceneSwitcher.action.sceneTransform.entry="En{{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.sceneTransform.entry="En{{scenes}}{{action}}{{rotation}}{{sources}}{{settingSelection}}{{singleSettingValue}}"
AdvSceneSwitcher.action.file="Archivo"
AdvSceneSwitcher.action.file.type.write="Escribir"
AdvSceneSwitcher.action.file.type.append="Agregar"

View File

@@ -196,7 +196,7 @@ AdvSceneSwitcher.condition.media.source="Source"
AdvSceneSwitcher.condition.media.anyOnScene="Toute source média sur la scène"
AdvSceneSwitcher.condition.media.allOnScene="Toutes les sources média sur la scène"
AdvSceneSwitcher.condition.media.inconsistencyInfo="Malheureusement, toutes les sources média ne se comportent pas de la même manière (par exemple, l'état \"Arrêté\" de la source Media par rapport à la source Vidéo VLC).\nPar conséquent, veuillez expérimenter ce qui fonctionne le mieux pour votre configuration !"
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}l'état est{{states}}et{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.media.layout.legacy="{{sourceTypes}}{{mediaSources}}{{scenes}}{{checkTypes}}l'état est{{states}}et{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Vidéo"
AdvSceneSwitcher.condition.video.condition.match="correspond exactement à"
AdvSceneSwitcher.condition.video.condition.differ="ne correspond pas à"
@@ -382,8 +382,7 @@ AdvSceneSwitcher.condition.date.entry.nextMatchDate="Prochaine correspondance à
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}}À la répétition, mettre à jour la date sélectionnée pour la date de répétition"
AdvSceneSwitcher.condition.sceneTransform="Transformation de l'élément de la scène"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Obtenir la transformation"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="Sur{{scenes}}{{sources}}{{types}}"
AdvSceneSwitcher.condition.sceneTransform.entry.type.matches="correspond à la transformation"
AdvSceneSwitcher.condition.sceneTransform.condition.match="correspond à la transformation"
AdvSceneSwitcher.condition.transition="Transition"
AdvSceneSwitcher.condition.transition.type.current="Le type de transition actuel est"
AdvSceneSwitcher.condition.transition.type.duration="La durée de la transition actuelle est"
@@ -611,7 +610,7 @@ AdvSceneSwitcher.action.sceneTransform.type.centerToScreen="Centrer à l'écran"
AdvSceneSwitcher.action.sceneTransform.type.centerVertically="Centrer verticalement"
AdvSceneSwitcher.action.sceneTransform.type.centerHorizontally="Centrer horizontalement"
AdvSceneSwitcher.action.sceneTransform.getTransform="Obtenir la transformation"
AdvSceneSwitcher.action.sceneTransform.entry="Sur{{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.sceneTransform.entry="Sur{{scenes}}{{action}}{{rotation}}{{sources}}{{settingSelection}}{{singleSettingValue}}"
AdvSceneSwitcher.action.file="Fichier"
AdvSceneSwitcher.action.file.type.write="Écrire"
AdvSceneSwitcher.action.file.type.append="Ajouter"

View File

@@ -311,7 +311,7 @@ AdvSceneSwitcher.condition.media.source="ソース"
AdvSceneSwitcher.condition.media.anyOnScene="あらゆるメディアソース"
AdvSceneSwitcher.condition.media.allOnScene="すべてのメディアソースはこちら"
AdvSceneSwitcher.condition.media.inconsistencyInfo="残念ながら、すべてのメディア ソース タイプが同じように動作するわけではありません (例: メディア ソースと VLC ビデオ ソースの \"Stopped\" 状態)。\nそのため、ご自身のセットアップに適したものを試してみてください。"
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}の状態は{{states}}かつ{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.media.layout.legacy="{{sourceTypes}}{{mediaSources}}{{scenes}}{{checkTypes}}の状態は{{states}}かつ{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="動画"
AdvSceneSwitcher.condition.video.condition.match="完全一致"
AdvSceneSwitcher.condition.video.condition.differ="合わない"
@@ -531,11 +531,8 @@ AdvSceneSwitcher.condition.date.entry.pattern="現在の日付「{{currentDate}}
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}}繰り返しの場合、選択した日付を繰り返しの日付に更新します"
; AdvSceneSwitcher.condition.sceneTransform="Scene item transform"
; AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
; AdvSceneSwitcher.condition.sceneTransform.entry.line1="On{{scenes}}{{sources}}{{types}}"
AdvSceneSwitcher.condition.sceneTransform.entry.type.matches="マッチトランスフォーム"
AdvSceneSwitcher.condition.sceneTransform.entry.type.changed="変わりました"
; AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
; AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}}{{getSettings}}"
AdvSceneSwitcher.condition.sceneTransform.condition.match="マッチトランスフォーム"
AdvSceneSwitcher.condition.sceneTransform.condition.changed="変わりました"
; AdvSceneSwitcher.condition.transition="Transition"
; AdvSceneSwitcher.condition.transition.type.current="Current transition type is"
AdvSceneSwitcher.condition.transition.type.duration="Current transition duration is"
@@ -903,7 +900,6 @@ AdvSceneSwitcher.action.sceneTransform.type.centerToScreen="画面中央から"
AdvSceneSwitcher.action.sceneTransform.type.centerVertically="縦方向中央に配置"
AdvSceneSwitcher.action.sceneTransform.type.centerHorizontally="水平方向に中央揃え"
AdvSceneSwitcher.action.sceneTransform.getTransform="変形を取得"
; AdvSceneSwitcher.action.sceneTransform.entry="On{{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.file="ファイル"
; AdvSceneSwitcher.action.file.type.write="Write"
; AdvSceneSwitcher.action.file.type.append="Append"

2139
data/locale/pt-BR.ini Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -117,7 +117,7 @@ AdvSceneSwitcher.condition.file.entry.line3="{{checkModificationDate}}{{checkFil
AdvSceneSwitcher.condition.media="Medya"
AdvSceneSwitcher.condition.media.anyOnScene="Herhangi bir medya kaynağı"
AdvSceneSwitcher.condition.media.allOnScene="Tüm medya kaynakları "
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}durumu{{states}}ve{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.media.layout.legacy="{{sourceTypes}}{{mediaSources}}{{scenes}}{{checkTypes}}durumu{{states}}ve{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="Video"
AdvSceneSwitcher.condition.video.condition.match="Tam olarak eşleşir"
AdvSceneSwitcher.condition.video.condition.differ="Eşleştirme"
@@ -226,10 +226,7 @@ AdvSceneSwitcher.condition.date.state.before="Önce"
AdvSceneSwitcher.condition.date.state.between="Arasında"
AdvSceneSwitcher.condition.sceneTransform="Sahne öğesi dönüşümü"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Dönüşümü al"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="Açık{{scenes}}{{sources}}{{types}}"
AdvSceneSwitcher.condition.sceneTransform.entry.type.matches="dönüşümle eşleşir"
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}"
AdvSceneSwitcher.condition.sceneTransform.condition.match="dönüşümle eşleşir"
AdvSceneSwitcher.condition.transition="Geçiş"
AdvSceneSwitcher.condition.transition.type.current="Geçerli geçiş türü"
AdvSceneSwitcher.condition.transition.type.duration="Mevcut geçiş süresi"
@@ -344,7 +341,7 @@ AdvSceneSwitcher.action.sceneOrder.type.movePosition="Konuma taşı"
AdvSceneSwitcher.action.sceneOrder.entry="Açık{{scenes}}{{actions}}{{sources}}{{position}}"
AdvSceneSwitcher.action.sceneTransform="Sahne öğesi dönüşümü"
AdvSceneSwitcher.action.sceneTransform.getTransform="Dönüşümü al"
AdvSceneSwitcher.action.sceneTransform.entry="Açık{{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.sceneTransform.entry="Açık{{scenes}}{{action}}{{rotation}}{{sources}}{{settingSelection}}{{singleSettingValue}}"
AdvSceneSwitcher.action.file="Dosya"
AdvSceneSwitcher.action.file.type.write="Yaz"
AdvSceneSwitcher.action.file.type.append="Ekle"

View File

@@ -184,7 +184,7 @@ AdvSceneSwitcher.condition.media.source="源"
AdvSceneSwitcher.condition.media.anyOnScene="任何媒体源"
AdvSceneSwitcher.condition.media.allOnScene="所有媒体源"
AdvSceneSwitcher.condition.media.inconsistencyInfo="不幸的是并非所有媒体源类型的行为都相同例如媒体源与VLC视频源“停止”状态。\n所以请尝试适合您的设置"
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}} 的状态是 {{states}}{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.media.layout.legacy="{{sourceTypes}}{{mediaSources}}{{scenes}}{{checkTypes}}的状态是{{states}}{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="视频"
AdvSceneSwitcher.condition.video.condition.match="完全匹配"
AdvSceneSwitcher.condition.video.condition.differ="不匹配"
@@ -365,10 +365,7 @@ AdvSceneSwitcher.condition.date.entry.nextMatchDate="下一次匹配 在: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} 重复时,将选定的日期更新为重复日期"
AdvSceneSwitcher.condition.sceneTransform="场景项目被改变"
AdvSceneSwitcher.condition.sceneTransform.getTransform="内容或者设置被改变了"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="在 {{scenes}} {{sources}} {{types}}"
AdvSceneSwitcher.condition.sceneTransform.entry.type.matches="匹配变换"
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}"
AdvSceneSwitcher.condition.sceneTransform.condition.match="匹配变换"
AdvSceneSwitcher.condition.transition="转场"
AdvSceneSwitcher.condition.transition.type.current="当前转场特效类型为"
AdvSceneSwitcher.condition.transition.type.duration="当前转场特效持续时间为"
@@ -555,7 +552,7 @@ AdvSceneSwitcher.action.sceneOrder.type.movePosition="移动到数值位置"
AdvSceneSwitcher.action.sceneOrder.entry="在 {{scenes}} {{actions}} {{sources}} {{position}}"
AdvSceneSwitcher.action.sceneTransform="场景项目转换"
AdvSceneSwitcher.action.sceneTransform.getTransform="获得转换"
AdvSceneSwitcher.action.sceneTransform.entry="在 {{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.sceneTransform.entry="在{{scenes}}{{action}}{{rotation}}{{sources}}{{settingSelection}}{{singleSettingValue}}"
AdvSceneSwitcher.action.file="文件"
AdvSceneSwitcher.action.file.type.write="写入"
AdvSceneSwitcher.action.file.type.append="追加写入"

View File

@@ -390,7 +390,7 @@
<item>
<layout class="QHBoxLayout" name="cooldownLayout">
<item>
<widget class="QLabel" name="label">
<widget class="QCheckBox" name="enableCooldown">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.cooldown</string>
</property>

View File

@@ -93,10 +93,24 @@ static void DisplayMissingDataDirWarning()
DisplayMessage(msg);
}
bool CanCreateDefaultAction();
bool CanCreateDefaultCondition();
static void DisplayCorruptedInstallWarning()
{
if (CanCreateDefaultAction() && CanCreateDefaultCondition()) {
return;
}
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.warnCorruptedInstallMessage"));
}
void AdvSceneSwitcher::LoadUI()
{
DisplayMissingDataDirWarning();
DisplayMissingDependencyWarning();
DisplayCorruptedInstallWarning();
SetupGeneralTab();
SetupTitleTab();
@@ -241,8 +255,8 @@ void SwitcherData::Thread()
duration = std::chrono::milliseconds(interval) +
std::chrono::milliseconds(linger) - runTime;
if (duration.count() < 1) {
blog(LOG_INFO,
"detected busy loop - refusing to sleep less than 1ms");
vblog(LOG_INFO,
"detected busy loop - refusing to sleep less than 1ms");
duration = std::chrono::milliseconds(10);
}
}
@@ -708,7 +722,7 @@ static void LoadPlugins()
{
QFileInfo libPath(
QString(obs_get_module_binary_path(obs_current_module())));
QString pluginDir(libPath.absolutePath() + "/adv-ss-plugins");
QString pluginDir(libPath.absolutePath() + "/" ADVSS_PLUGIN_FOLDER);
#ifdef _WIN32
QString libPattern = "*.dll";
SetDllDirectory(pluginDir.toStdWString().c_str());

View File

@@ -1,5 +1,6 @@
#pragma once
#include "macro-segment-list.hpp"
#include "condition-logic.hpp"
#include "log-helper.hpp"
#include <ui_advanced-scene-switcher.h>
@@ -12,7 +13,6 @@ class MacroActionEdit;
class MacroConditionEdit;
class Duration;
class SequenceWidget;
enum class LogicType;
struct SceneGroup;
/*******************************************************************************
@@ -53,6 +53,7 @@ public slots:
void on_noMatchRandomSwitch_clicked();
void NoMatchDelayDurationChanged(const Duration &);
void CooldownDurationChanged(const Duration &);
void on_enableCooldown_stateChanged(int state);
void on_startupBehavior_currentIndexChanged(int index);
void on_logLevel_currentIndexChanged(int index);
void on_autoStartEvent_currentIndexChanged(int index);
@@ -173,7 +174,7 @@ public slots:
void MacroConditionReorder(int to, int target);
void AddMacroCondition(int idx);
void AddMacroCondition(Macro *macro, int idx, const std::string &id,
obs_data_t *data, LogicType logic);
obs_data_t *data, Logic::Type logic);
void RemoveMacroCondition(int idx);
void MoveMacroConditionUp(int idx);
void MoveMacroConditionDown(int idx);

View File

@@ -91,6 +91,17 @@ void AdvSceneSwitcher::CooldownDurationChanged(const Duration &dur)
switcher->cooldown = dur;
}
void AdvSceneSwitcher::on_enableCooldown_stateChanged(int state)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->enableCooldown = state;
ui->cooldownTime->setEnabled(state);
}
void AdvSceneSwitcher::on_startupBehavior_currentIndexChanged(int index)
{
if (loading) {
@@ -505,6 +516,7 @@ void SwitcherData::SaveGeneralSettings(obs_data_t *obj)
noMatchDelay.Save(obj, "noMatchDelay");
cooldown.Save(obj, "cooldown");
obs_data_set_bool(obj, "enableCooldown", enableCooldown);
obs_data_set_bool(obj, "active", sceneColletionStop ? true : !stop);
sceneColletionStop = false;
@@ -550,6 +562,11 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
noMatchDelay.Load(obj, "noMatchDelay");
cooldown.Load(obj, "cooldown");
if (!obs_data_has_user_value(obj, "enableCooldown")) {
enableCooldown = cooldown.Seconds() != 0;
} else {
enableCooldown = obs_data_get_bool(obj, "enableCooldown");
}
obs_data_set_default_bool(obj, "active", true);
stop = !obs_data_get_bool(obj, "active");
@@ -653,7 +670,7 @@ void SwitcherData::CheckNoMatchSwitch(bool &match, OBSWeakSource &scene,
void SwitcherData::checkSwitchCooldown(bool &match)
{
if (!match) {
if (!match || !enableCooldown) {
return;
}
@@ -822,6 +839,8 @@ void AdvSceneSwitcher::SetupGeneralTab()
ui->checkInterval->setValue(switcher->interval);
ui->enableCooldown->setChecked(switcher->enableCooldown);
ui->cooldownTime->setEnabled(switcher->enableCooldown);
ui->cooldownTime->SetDuration(switcher->cooldown);
ui->cooldownTime->setToolTip(obs_module_text(
"AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint"));

View File

@@ -170,8 +170,10 @@ void AdvSceneSwitcher::on_sceneGroupAdd_clicked()
item->setData(Qt::UserRole, text);
ui->sceneGroups->setCurrentItem(item);
addPulse->deleteLater();
addPulse = nullptr;
if (addPulse) {
addPulse->deleteLater();
addPulse = nullptr;
}
ui->sceneGroupHelp->setVisible(false);
emit SceneGroupAdded(QString::fromStdString(name));

View File

@@ -22,7 +22,7 @@ void AdvSceneSwitcher::on_triggerAdd_clicked()
listAddClicked(ui->sceneTriggers,
new SceneTriggerWidget(this,
&switcher->sceneTriggers.back()),
ui->triggerAdd, addPulse);
addPulse);
ui->triggerHelp->setVisible(false);
}

View File

@@ -22,7 +22,7 @@ void AdvSceneSwitcher::on_audioAdd_clicked()
AudioSwitchWidget *sw =
new AudioSwitchWidget(this, &switcher->audioSwitches.back());
listAddClicked(ui->audioSwitches, sw, ui->audioAdd, addPulse);
listAddClicked(ui->audioSwitches, sw, addPulse);
ui->audioHelp->setVisible(false);
}

View File

@@ -19,7 +19,7 @@ void AdvSceneSwitcher::on_executableAdd_clicked()
listAddClicked(ui->executables,
new ExecutableSwitchWidget(
this, &switcher->executableSwitches.back()),
ui->executableAdd, addPulse);
addPulse);
ui->exeHelp->setVisible(false);
}

View File

@@ -291,7 +291,7 @@ void AdvSceneSwitcher::on_fileAdd_clicked()
listAddClicked(ui->fileSwitches,
new FileSwitchWidget(this,
&switcher->fileSwitches.back()),
ui->fileAdd, addPulse);
addPulse);
ui->fileHelp->setVisible(false);
}

View File

@@ -22,7 +22,7 @@ void AdvSceneSwitcher::on_mediaAdd_clicked()
listAddClicked(ui->mediaSwitches,
new MediaSwitchWidget(this,
&switcher->mediaSwitches.back()),
ui->mediaAdd, addPulse);
addPulse);
ui->mediaHelp->setVisible(false);
}

View File

@@ -20,7 +20,7 @@ void AdvSceneSwitcher::on_pauseAdd_clicked()
listAddClicked(ui->pauseEntries,
new PauseEntryWidget(this,
&switcher->pauseEntries.back()),
ui->pauseAdd, addPulse);
addPulse);
ui->pauseHelp->setVisible(false);
}

View File

@@ -19,7 +19,7 @@ void AdvSceneSwitcher::on_randomAdd_clicked()
listAddClicked(ui->randomSwitches,
new RandomSwitchWidget(this,
&switcher->randomSwitches.back()),
ui->randomAdd, addPulse);
addPulse);
ui->randomHelp->setVisible(false);
}

View File

@@ -79,7 +79,7 @@ void AdvSceneSwitcher::on_screenRegionAdd_clicked()
listAddClicked(ui->screenRegionSwitches,
new ScreenRegionWidget(
this, &switcher->screenRegionSwitches.back()),
ui->screenRegionAdd, addPulse);
addPulse);
ui->regionHelp->setVisible(false);
}

View File

@@ -26,7 +26,7 @@ void AdvSceneSwitcher::on_sceneSequenceAdd_clicked()
listAddClicked(ui->sceneSequenceSwitches,
new SequenceWidget(
this, &switcher->sceneSequenceSwitches.back()),
ui->sceneSequenceAdd, addPulse);
addPulse);
ui->sequenceHelp->setVisible(false);
}

View File

@@ -17,7 +17,7 @@ void AdvSceneSwitcher::on_timeAdd_clicked()
listAddClicked(ui->timeSwitches,
new TimeSwitchWidget(this,
&switcher->timeSwitches.back()),
ui->timeAdd, addPulse);
addPulse);
ui->timeHelp->setVisible(false);
}

View File

@@ -24,7 +24,7 @@ void AdvSceneSwitcher::on_videoAdd_clicked()
VideoSwitchWidget *sw =
new VideoSwitchWidget(this, &switcher->videoSwitches.back());
listAddClicked(ui->videoSwitches, sw, ui->videoAdd, addPulse);
listAddClicked(ui->videoSwitches, sw, addPulse);
ui->videoHelp->setVisible(false);
}

View File

@@ -21,7 +21,7 @@ void AdvSceneSwitcher::on_windowAdd_clicked()
listAddClicked(ui->windowSwitches,
new WindowSwitchWidget(this,
&switcher->windowSwitches.back()),
ui->windowAdd, addPulse);
addPulse);
ui->windowHelp->setVisible(false);
}

View File

@@ -1,7 +1,28 @@
#include "macro-action-factory.hpp"
#include <mutex>
namespace advss {
namespace {
class MacroActionUnknown : public MacroAction {
public:
MacroActionUnknown(Macro *m) : MacroAction(m) {}
std::shared_ptr<MacroAction> Copy() const
{
return std::make_shared<MacroActionUnknown>(GetMacro());
}
bool PerformAction() { return true; };
bool Save(obs_data_t *obj) const { return MacroAction::Save(obj); };
bool Load(obs_data_t *obj) { return MacroAction::Load(obj); };
std::string GetId() const { return "unknown"; }
};
} // namespace
static std::recursive_mutex mutex;
std::map<std::string, MacroActionInfo> &MacroActionFactory::GetMap()
{
static std::map<std::string, MacroActionInfo> _methods;
@@ -10,6 +31,7 @@ std::map<std::string, MacroActionInfo> &MacroActionFactory::GetMap()
bool MacroActionFactory::Register(const std::string &id, MacroActionInfo info)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it == GetMap().end()) {
GetMap()[id] = info;
return true;
@@ -17,35 +39,61 @@ bool MacroActionFactory::Register(const std::string &id, MacroActionInfo info)
return false;
}
bool MacroActionFactory::Deregister(const std::string &id)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (GetMap().count(id) == 0) {
return false;
}
GetMap().erase(id);
return true;
}
static std::shared_ptr<MacroAction> createUnknownAction(Macro *m)
{
return std::make_shared<MacroActionUnknown>(m);
}
std::shared_ptr<MacroAction> MacroActionFactory::Create(const std::string &id,
Macro *m)
{
if (auto it = GetMap().find(id); it != GetMap().end())
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._create(m);
}
return nullptr;
return createUnknownAction(m);
}
static QWidget *createUnknownActionWidget()
{
return new QLabel(obs_module_text("AdvSceneSwitcher.action.unknown"));
}
QWidget *MacroActionFactory::CreateWidget(const std::string &id,
QWidget *parent,
std::shared_ptr<MacroAction> action)
{
if (auto it = GetMap().find(id); it != GetMap().end())
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._createWidget(parent, action);
}
return nullptr;
return createUnknownActionWidget();
}
std::string MacroActionFactory::GetActionName(const std::string &id)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._name;
}
return "unknown action";
return obs_module_text("AdvSceneSwitcher.action.unknown");
}
std::string MacroActionFactory::GetIdByName(const QString &name)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
for (auto it : GetMap()) {
if (name == obs_module_text(it.second._name.c_str())) {
return it.first;
@@ -54,4 +102,14 @@ std::string MacroActionFactory::GetIdByName(const QString &name)
return "";
}
bool CanCreateDefaultAction()
{
const auto action = MacroActionFactory::Create(
MacroAction::GetDefaultID().data(), nullptr);
if (!action) {
return false;
}
return action->GetId() == MacroAction::GetDefaultID().data();
}
} // namespace advss

View File

@@ -6,10 +6,10 @@
namespace advss {
struct MacroActionInfo {
using CreateAction = std::shared_ptr<MacroAction> (*)(Macro *m);
using CreateActionWidget = QWidget *(*)(QWidget *parent,
std::shared_ptr<MacroAction>);
CreateAction _create = nullptr;
std::function<std::shared_ptr<MacroAction>(Macro *m)> _create = nullptr;
CreateActionWidget _createWidget = nullptr;
std::string _name;
};
@@ -19,6 +19,7 @@ public:
MacroActionFactory() = delete;
EXPORT static bool Register(const std::string &id, MacroActionInfo);
static bool Deregister(const std::string &id);
static std::shared_ptr<MacroAction> Create(const std::string &id,
Macro *m);
static QWidget *CreateWidget(const std::string &id, QWidget *parent,
@@ -31,4 +32,6 @@ private:
static std::map<std::string, MacroActionInfo> &GetMap();
};
bool CanCreateDefaultAction();
} // namespace advss

View File

@@ -0,0 +1,92 @@
#include "macro-action-script.hpp"
#include "layout-helpers.hpp"
#include "macro-helpers.hpp"
#include "properties-view.hpp"
#include "sync-helpers.hpp"
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)
: MacroAction(m),
MacroSegmentScript(defaultSettings, propertiesSignalName,
triggerSignal, completionSignal),
_id(id)
{
}
MacroActionScript::MacroActionScript(const advss::MacroActionScript &other)
: MacroAction(other.GetMacro()),
MacroSegmentScript(other),
_id(other._id)
{
}
bool MacroActionScript::PerformAction()
{
if (!ScriptHandler::ActionIdIsValid(_id)) {
blog(LOG_WARNING, "skipping unknown script action \"%s\"",
_id.c_str());
return true;
}
(void)SendTriggerSignal();
return true;
}
void MacroActionScript::LogAction() const
{
ablog(LOG_INFO, "performing script action \"%s\"", GetId().c_str());
}
bool MacroActionScript::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
MacroSegmentScript::Save(obj);
return true;
}
bool MacroActionScript::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
MacroSegmentScript::Load(obj);
return true;
}
std::shared_ptr<MacroAction> MacroActionScript::Copy() const
{
return std::make_shared<MacroActionScript>(*this);
}
void MacroActionScript::WaitForCompletion() const
{
using namespace std::chrono_literals;
auto start = std::chrono::high_resolution_clock::now();
auto timePassed = std::chrono::duration_cast<std::chrono::milliseconds>(
start - start);
const auto timeoutMs = GetTimeoutSeconds() * 1000.0;
std::unique_lock<std::mutex> lock(*GetMutex());
while (!TriggerIsCompleted()) {
if (MacroWaitShouldAbort() || MacroIsStopped(GetMacro())) {
break;
}
if ((double)timePassed.count() > timeoutMs) {
blog(LOG_INFO, "script action timeout (%s)",
_id.c_str());
break;
}
GetMacroWaitCV().wait_for(lock, 10ms);
const auto now = std::chrono::high_resolution_clock::now();
timePassed =
std::chrono::duration_cast<std::chrono::milliseconds>(
now - start);
}
}
} // namespace advss

View File

@@ -0,0 +1,29 @@
#pragma once
#include "macro-action-edit.hpp"
#include "macro-script-handler.hpp"
#include "macro-segment-script.hpp"
namespace advss {
class MacroActionScript : public MacroAction, public MacroSegmentScript {
public:
MacroActionScript(Macro *m, const std::string &id,
const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &signalComplete);
MacroActionScript(const advss::MacroActionScript &);
bool PerformAction();
void LogAction() const;
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetId() const { return _id; };
std::shared_ptr<MacroAction> Copy() const;
private:
void WaitForCompletion() const;
std::string _id = "";
};
} // namespace advss

View File

@@ -10,26 +10,6 @@
namespace advss {
static inline void populateLogicSelection(QComboBox *list, bool root = false)
{
if (root) {
for (const auto &entry : MacroCondition::logicTypes) {
if (static_cast<int>(entry.first) < logic_root_offset) {
list->addItem(obs_module_text(
entry.second._name.c_str()));
}
}
} else {
for (const auto &entry : MacroCondition::logicTypes) {
if (static_cast<int>(entry.first) >=
logic_root_offset) {
list->addItem(obs_module_text(
entry.second._name.c_str()));
}
}
}
}
static inline void populateConditionSelection(QComboBox *list)
{
for (const auto &[_, condition] :
@@ -120,13 +100,13 @@ void DurationModifierEdit::Collapse(bool collapse)
MacroConditionEdit::MacroConditionEdit(
QWidget *parent, std::shared_ptr<MacroCondition> *entryData,
const std::string &id, bool root)
const std::string &id, bool isRootCondition)
: MacroSegmentEdit(parent),
_logicSelection(new QComboBox()),
_conditionSelection(new FilterComboBox()),
_dur(new DurationModifierEdit()),
_entryData(entryData),
_isRoot(root)
_isRoot(isRootCondition)
{
QWidget::connect(_logicSelection, SIGNAL(currentIndexChanged(int)),
this, SLOT(LogicSelectionChanged(int)));
@@ -139,7 +119,7 @@ MacroConditionEdit::MacroConditionEdit(
this,
SLOT(DurationModifierChanged(DurationModifier::Type)));
populateLogicSelection(_logicSelection, root);
Logic::PopulateLogicTypeSelection(_logicSelection, isRootCondition);
populateConditionSelection(_conditionSelection);
_section->AddHeaderWidget(_logicSelection);
@@ -168,15 +148,10 @@ void MacroConditionEdit::LogicSelectionChanged(int idx)
return;
}
LogicType type;
if (IsRootNode()) {
type = static_cast<LogicType>(idx);
} else {
type = static_cast<LogicType>(idx + logic_root_offset);
}
auto lock = LockContext();
(*_entryData)->SetLogicType(type);
const auto logic = static_cast<Logic::Type>(
_logicSelection->itemData(idx).toInt());
(*_entryData)->SetLogicType(logic);
}
bool MacroConditionEdit::IsRootNode()
@@ -186,13 +161,9 @@ bool MacroConditionEdit::IsRootNode()
void MacroConditionEdit::SetLogicSelection()
{
auto logic = (*_entryData)->GetLogicType();
if (IsRootNode()) {
_logicSelection->setCurrentIndex(static_cast<int>(logic));
} else {
_logicSelection->setCurrentIndex(static_cast<int>(logic) -
logic_root_offset);
}
const auto logic = (*_entryData)->GetLogicType();
_logicSelection->setCurrentIndex(
_logicSelection->findData(static_cast<int>(logic)));
}
void MacroConditionEdit::SetRootNode(bool root)
@@ -200,7 +171,7 @@ void MacroConditionEdit::SetRootNode(bool root)
_isRoot = root;
const QSignalBlocker blocker(_logicSelection);
_logicSelection->clear();
populateLogicSelection(_logicSelection, root);
Logic::PopulateLogicTypeSelection(_logicSelection, root);
SetLogicSelection();
}
@@ -301,17 +272,17 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
}
std::string id;
LogicType logic;
Logic::Type logic;
if (idx >= 1) {
id = macro->Conditions().at(idx - 1)->GetId();
if (idx == 1) {
logic = LogicType::OR;
logic = Logic::Type::OR;
} else {
logic = macro->Conditions().at(idx - 1)->GetLogicType();
}
} else {
id = MacroCondition::GetDefaultID();
logic = LogicType::ROOT_NONE;
logic = Logic::Type::ROOT_NONE;
}
OBSDataAutoRelease data;
@@ -324,7 +295,7 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
void AdvSceneSwitcher::AddMacroCondition(Macro *macro, int idx,
const std::string &id,
obs_data_t *data, LogicType logic)
obs_data_t *data, Logic::Type logic)
{
if (idx < 0 || idx > (int)macro->Conditions().size()) {
assert(false);
@@ -389,7 +360,7 @@ void AdvSceneSwitcher::RemoveMacroCondition(int idx)
macro->UpdateConditionIndices();
if (idx == 0 && macro->Conditions().size() > 0) {
auto newRoot = macro->Conditions().at(0);
newRoot->SetLogicType(LogicType::ROOT_NONE);
newRoot->SetLogicType(Logic::Type::ROOT_NONE);
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(0))
->SetRootNode(true);
@@ -540,22 +511,23 @@ void AdvSceneSwitcher::MacroConditionReorder(int to, int from)
auto lock = LockContext();
auto condition = macro->Conditions().at(from);
if (to == 0) {
condition->SetLogicType(LogicType::ROOT_NONE);
condition->SetLogicType(Logic::Type::ROOT_NONE);
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(from))
->SetRootNode(true);
macro->Conditions().at(0)->SetLogicType(LogicType::AND);
macro->Conditions().at(0)->SetLogicType(
Logic::Type::AND);
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(0))
->SetRootNode(false);
}
if (from == 0) {
condition->SetLogicType(LogicType::AND);
condition->SetLogicType(Logic::Type::AND);
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(from))
->SetRootNode(false);
macro->Conditions().at(1)->SetLogicType(
LogicType::ROOT_NONE);
Logic::Type::ROOT_NONE);
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(1))
->SetRootNode(true);

View File

@@ -2,6 +2,7 @@
#include "macro-condition.hpp"
#include "macro-condition-factory.hpp"
#include "filter-combo-box.hpp"
#include "duration-control.hpp"
#include <memory>

View File

@@ -1,7 +1,24 @@
#include "macro-condition-factory.hpp"
#include <mutex>
namespace advss {
namespace {
class MacroConditionUnknown : public MacroCondition {
public:
MacroConditionUnknown(Macro *m) : MacroCondition(m) {}
bool CheckCondition() { return false; }
bool Save(obs_data_t *obj) const { return MacroCondition::Save(obj); };
bool Load(obs_data_t *obj) { return MacroCondition::Load(obj); };
std::string GetId() const { return "unknown"; }
};
} // namespace
static std::recursive_mutex mutex;
std::map<std::string, MacroConditionInfo> &MacroConditionFactory::GetMap()
{
static std::map<std::string, MacroConditionInfo> _methods;
@@ -11,6 +28,7 @@ std::map<std::string, MacroConditionInfo> &MacroConditionFactory::GetMap()
bool MacroConditionFactory::Register(const std::string &id,
MacroConditionInfo info)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it == GetMap().end()) {
GetMap()[id] = info;
return true;
@@ -18,35 +36,60 @@ bool MacroConditionFactory::Register(const std::string &id,
return false;
}
bool MacroConditionFactory::Deregister(const std::string &id)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (GetMap().count(id) == 0) {
return false;
}
GetMap().erase(id);
return true;
}
static std::shared_ptr<MacroCondition> createUnknownCondition(Macro *m)
{
return std::make_shared<MacroConditionUnknown>(m);
}
std::shared_ptr<MacroCondition>
MacroConditionFactory::Create(const std::string &id, Macro *m)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._create(m);
}
return nullptr;
return createUnknownCondition(m);
}
static QWidget *createUnknownConditionWidget()
{
return new QLabel(
obs_module_text("AdvSceneSwitcher.condition.unknown"));
}
QWidget *
MacroConditionFactory::CreateWidget(const std::string &id, QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._createWidget(parent, cond);
}
return nullptr;
return createUnknownConditionWidget();
}
std::string MacroConditionFactory::GetConditionName(const std::string &id)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._name;
}
return "unknown condition";
return obs_module_text("AdvSceneSwitcher.condition.unknown");
}
std::string MacroConditionFactory::GetIdByName(const QString &name)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
for (auto it : GetMap()) {
if (name == obs_module_text(it.second._name.c_str())) {
return it.first;
@@ -57,10 +100,21 @@ std::string MacroConditionFactory::GetIdByName(const QString &name)
bool MacroConditionFactory::UsesDurationModifier(const std::string &id)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._useDurationModifier;
}
return false;
}
bool CanCreateDefaultCondition()
{
const auto condition = MacroConditionFactory::Create(
MacroCondition::GetDefaultID().data(), nullptr);
if (!condition) {
return false;
}
return condition->GetId() == MacroCondition::GetDefaultID().data();
}
} // namespace advss

View File

@@ -6,10 +6,11 @@
namespace advss {
struct MacroConditionInfo {
using CreateCondition = std::shared_ptr<MacroCondition> (*)(Macro *m);
using CreateConditionWidget =
QWidget *(*)(QWidget *parent, std::shared_ptr<MacroCondition>);
CreateCondition _create = nullptr;
std::function<std::shared_ptr<MacroCondition>(Macro *m)> _create =
nullptr;
CreateConditionWidget _createWidget = nullptr;
std::string _name;
bool _useDurationModifier = true;
@@ -19,6 +20,7 @@ class MacroConditionFactory {
public:
MacroConditionFactory() = delete;
EXPORT static bool Register(const std::string &, MacroConditionInfo);
static bool Deregister(const std::string &);
static std::shared_ptr<MacroCondition> Create(const std::string &,
Macro *m);
static QWidget *CreateWidget(const std::string &id, QWidget *parent,
@@ -32,4 +34,6 @@ private:
static std::map<std::string, MacroConditionInfo> &GetMap();
};
bool CanCreateDefaultCondition();
} // namespace advss

View File

@@ -0,0 +1,79 @@
#include "macro-condition-script.hpp"
#include "layout-helpers.hpp"
#include "macro-helpers.hpp"
#include "sync-helpers.hpp"
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)
: MacroCondition(m),
MacroSegmentScript(defaultSettings, propertiesSignalName,
triggerSignal, completionSignal),
_id(id)
{
}
MacroConditionScript::MacroConditionScript(
const advss::MacroConditionScript &other)
: MacroCondition(other.GetMacro()),
MacroSegmentScript(other),
_id(other._id)
{
}
bool MacroConditionScript::CheckCondition()
{
if (!ScriptHandler::ConditionIdIsValid(_id)) {
blog(LOG_WARNING, "skipping unknown script condition \"%s\"",
_id.c_str());
return false;
}
return SendTriggerSignal();
}
bool MacroConditionScript::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
MacroSegmentScript::Save(obj);
return true;
}
bool MacroConditionScript::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
MacroSegmentScript::Load(obj);
return true;
}
void MacroConditionScript::WaitForCompletion() const
{
using namespace std::chrono_literals;
auto start = std::chrono::high_resolution_clock::now();
auto timePassed = std::chrono::duration_cast<std::chrono::milliseconds>(
start - start);
const auto timeoutMs = GetTimeoutSeconds() * 1000.0;
while (!TriggerIsCompleted()) {
if (MacroWaitShouldAbort() || MacroIsStopped(GetMacro())) {
break;
}
if ((double)timePassed.count() > timeoutMs) {
blog(LOG_INFO, "script condition timeout (%s)",
_id.c_str());
break;
}
std::this_thread::sleep_for(10ms);
const auto now = std::chrono::high_resolution_clock::now();
timePassed =
std::chrono::duration_cast<std::chrono::milliseconds>(
now - start);
}
}
} // namespace advss

View File

@@ -0,0 +1,27 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "macro-script-handler.hpp"
#include "macro-segment-script.hpp"
namespace advss {
class MacroConditionScript : public MacroCondition, public MacroSegmentScript {
public:
MacroConditionScript(Macro *m, const std::string &id,
const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &signalComplete);
MacroConditionScript(const advss::MacroConditionScript &);
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetId() const { return _id; };
private:
void WaitForCompletion() const;
std::string _id = "";
};
} // namespace advss

View File

@@ -2,79 +2,6 @@
namespace advss {
const std::map<LogicType, LogicTypeInfo> MacroCondition::logicTypes = {
{LogicType::NONE, {"AdvSceneSwitcher.logic.none"}},
{LogicType::AND, {"AdvSceneSwitcher.logic.and"}},
{LogicType::OR, {"AdvSceneSwitcher.logic.or"}},
{LogicType::AND_NOT, {"AdvSceneSwitcher.logic.andNot"}},
{LogicType::OR_NOT, {"AdvSceneSwitcher.logic.orNot"}},
{LogicType::ROOT_NONE, {"AdvSceneSwitcher.logic.rootNone"}},
{LogicType::ROOT_NOT, {"AdvSceneSwitcher.logic.not"}},
};
void DurationModifier::Save(obs_data_t *obj, const char *condName,
const char *duration) const
{
obs_data_set_int(obj, condName, static_cast<int>(_type));
_dur.Save(obj, duration);
}
void DurationModifier::Load(obs_data_t *obj, const char *condName,
const char *duration)
{
// For backwards compatibility check if duration value exist without
// time constraint condition - if so assume DurationCondition::MORE
if (!obs_data_has_user_value(obj, condName) &&
obs_data_has_user_value(obj, duration)) {
obs_data_set_int(obj, condName, static_cast<int>(Type::MORE));
}
_type = static_cast<Type>(obs_data_get_int(obj, condName));
_dur.Load(obj, duration);
// TODO: remove this fallback
if (obs_data_has_user_value(obj, "displayUnit")) {
_dur.SetUnit(static_cast<Duration::Unit>(
obs_data_get_int(obj, "displayUnit")));
}
}
bool DurationModifier::DurationReached()
{
switch (_type) {
case DurationModifier::Type::NONE:
return true;
break;
case DurationModifier::Type::MORE:
return _dur.DurationReached();
break;
case DurationModifier::Type::EQUAL:
if (_dur.DurationReached() && !_timeReached) {
_timeReached = true;
return true;
}
break;
case DurationModifier::Type::LESS:
return !_dur.DurationReached();
break;
case DurationModifier::Type::WITHIN:
if (_dur.IsReset()) {
return false;
}
return !_dur.DurationReached();
break;
default:
break;
}
return false;
}
void DurationModifier::Reset()
{
_timeReached = false;
_dur.Reset();
}
MacroCondition::MacroCondition(Macro *m, bool supportsVariableValue)
: MacroSegment(m, supportsVariableValue)
{
@@ -84,73 +11,64 @@ bool MacroCondition::Save(obs_data_t *obj) const
{
MacroSegment::Save(obj);
obs_data_set_string(obj, "id", GetId().c_str());
obs_data_set_int(obj, "logic", static_cast<int>(_logic));
// To avoid conflicts with conditions which also use the Duration class
// save the duration modifier in a separate obj
auto durObj = obs_data_create();
_duration.Save(durObj);
obs_data_set_obj(obj, "durationModifier", durObj);
obs_data_release(durObj);
_logic.Save(obj, "logic");
_durationModifier.Save(obj);
return true;
}
bool MacroCondition::Load(obs_data_t *obj)
{
MacroSegment::Load(obj);
_logic = static_cast<LogicType>(obs_data_get_int(obj, "logic"));
if (obs_data_has_user_value(obj, "durationModifier")) {
auto durObj = obs_data_get_obj(obj, "durationModifier");
_duration.Load(durObj);
obs_data_release(durObj);
} else {
// For backwards compatibility
_duration.Load(obj);
}
_logic.Load(obj, "logic");
_durationModifier.Load(obj);
return true;
}
void MacroCondition::ValidateLogicSelection(bool isRootCondition,
const char *context)
{
if (_logic.IsValidSelection(isRootCondition)) {
return;
}
if (_logic.IsRootType()) {
_logic.SetType(Logic::Type::ROOT_NONE);
blog(LOG_WARNING,
"setting invalid logic selection to 'if' for macro %s",
context);
return;
}
_logic.SetType(Logic::Type::NONE);
blog(LOG_WARNING,
"setting invalid logic selection to 'ignore' for macro %s",
context);
}
void MacroCondition::ResetDuration()
{
_duration.Reset();
_durationModifier.ResetDuration();
}
void MacroCondition::CheckDurationModifier(bool &val)
bool MacroCondition::CheckDurationModifier(bool conditionValue)
{
if (_duration.GetType() != DurationModifier::Type::WITHIN && !val) {
_duration.Reset();
}
if (_duration.GetType() == DurationModifier::Type::WITHIN && val) {
_duration.Reset();
}
switch (_duration.GetType()) {
case DurationModifier::Type::NONE:
case DurationModifier::Type::MORE:
case DurationModifier::Type::EQUAL:
case DurationModifier::Type::LESS:
val = val && _duration.DurationReached();
return;
case DurationModifier::Type::WITHIN:
if (val) {
_duration.SetTimeRemaining(
_duration.GetDuration().Seconds());
}
val = val || _duration.DurationReached();
break;
default:
break;
}
return _durationModifier.CheckConditionWithDurationModifier(
conditionValue);
}
DurationModifier MacroCondition::GetDurationModifier() const
{
return _durationModifier;
}
void MacroCondition::SetDurationModifier(DurationModifier::Type m)
{
_duration.SetModifier(m);
_durationModifier.SetModifier(m);
}
void MacroCondition::SetDuration(const Duration &duration)
{
_duration.SetValue(duration);
_durationModifier.SetDuration(duration);
}
std::string_view MacroCondition::GetDefaultID()

View File

@@ -1,63 +1,11 @@
#pragma once
#include "macro-segment.hpp"
#include "condition-logic.hpp"
#include "duration-modifier.hpp"
#include "macro-ref.hpp"
#include <duration-control.hpp>
namespace advss {
constexpr auto logic_root_offset = 100;
enum class LogicType {
ROOT_NONE = 0,
ROOT_NOT,
ROOT_LAST,
// leave some space for potential expansion
NONE = 100,
AND,
OR,
AND_NOT,
OR_NOT,
LAST,
};
static inline bool isRootLogicType(LogicType l)
{
return static_cast<int>(l) < logic_root_offset;
}
struct LogicTypeInfo {
std::string _name;
};
class DurationModifier {
public:
enum class Type {
NONE,
MORE,
EQUAL,
LESS,
WITHIN, // Condition will remain true for a set a amount of time
// regardless of current state
};
void Save(obs_data_t *obj, const char *condName = "time_constraint",
const char *duration = "seconds") const;
void Load(obs_data_t *obj, const char *condName = "time_constraint",
const char *duration = "seconds");
void SetModifier(Type cond) { _type = cond; }
void SetTimeRemaining(const double &val) { _dur.SetTimeRemaining(val); }
void SetValue(const Duration &value) { _dur = value; }
Type GetType() { return _type; }
Duration GetDuration() { return _dur; }
bool DurationReached();
void Reset();
private:
Type _type = Type::NONE;
Duration _dur;
bool _timeReached = false;
};
class EXPORT MacroCondition : public MacroSegment {
public:
MacroCondition(Macro *m, bool supportsVariableValue = false);
@@ -65,20 +13,24 @@ public:
virtual bool CheckCondition() = 0;
virtual bool Save(obs_data_t *obj) const = 0;
virtual bool Load(obs_data_t *obj) = 0;
LogicType GetLogicType() { return _logic; }
void SetLogicType(LogicType logic) { _logic = logic; }
static const std::map<LogicType, LogicTypeInfo> logicTypes;
void ResetDuration();
void CheckDurationModifier(bool &val);
DurationModifier GetDurationModifier() { return _duration; }
Logic::Type GetLogicType() const { return _logic.GetType(); }
void SetLogicType(const Logic::Type &logic) { _logic.SetType(logic); }
void ValidateLogicSelection(bool isRootCondition, const char *context);
DurationModifier GetDurationModifier() const;
void SetDurationModifier(DurationModifier::Type m);
void SetDuration(const Duration &duration);
void ResetDuration();
bool CheckDurationModifier(bool conditionValue);
static std::string_view GetDefaultID();
private:
LogicType _logic = LogicType::ROOT_NONE;
DurationModifier _duration;
Logic _logic = Logic(Logic::Type::ROOT_NONE);
DurationModifier _durationModifier;
};
class EXPORT MacroRefCondition : virtual public MacroCondition {

View File

@@ -6,14 +6,14 @@
namespace advss {
MacroDock::MacroDock(std::weak_ptr<Macro> m, QWidget *parent,
MacroDock::MacroDock(std::weak_ptr<Macro> m,
const StringVariable &runButtonText,
const StringVariable &pauseButtonText,
const StringVariable &unpauseButtonText,
const StringVariable &conditionsTrueText,
const StringVariable &conditionsFalseText,
bool enableHighlight)
: OBSDock(parent),
: QFrame(),
_runButtonText(runButtonText),
_pauseButtonText(pauseButtonText),
_unpauseButtonText(unpauseButtonText),
@@ -25,19 +25,16 @@ MacroDock::MacroDock(std::weak_ptr<Macro> m, QWidget *parent,
_statusText(new QLabel(conditionsFalseText.c_str())),
_macro(m)
{
setFrameShape(QFrame::StyledPanel);
setFrameShadow(QFrame::Sunken);
auto macro = _macro.lock();
if (macro) {
setWindowTitle(QString::fromStdString(macro->Name()));
_run->setVisible(macro->DockHasRunButton());
_pauseToggle->setVisible(macro->DockHasPauseButton());
_statusText->setVisible(macro->DockHasStatusLabel());
} else {
setWindowTitle("<deleted macro>");
}
setFeatures(DockWidgetClosable | DockWidgetMovable |
DockWidgetFloatable);
QWidget::connect(_run, SIGNAL(clicked()), this, SLOT(RunClicked()));
QWidget::connect(_pauseToggle, SIGNAL(clicked()), this,
SLOT(PauseToggleClicked()));
@@ -52,21 +49,7 @@ MacroDock::MacroDock(std::weak_ptr<Macro> m, QWidget *parent,
QWidget::connect(&_timer, SIGNAL(timeout()), this, SLOT(Highlight()));
_timer.start(500);
// QFrame wrapper is necessary to avoid dock being partially
// transparent
QFrame *wrapper = new QFrame;
wrapper->setFrameShape(QFrame::StyledPanel);
wrapper->setFrameShadow(QFrame::Sunken);
wrapper->setLayout(layout);
setWidget(wrapper);
setFloating(true);
hide();
}
void MacroDock::SetName(const QString &name)
{
setWindowTitle(name);
setLayout(layout);
}
void MacroDock::ShowRunButton(bool value)

View File

@@ -1,10 +1,9 @@
#pragma once
#include "obs-dock.hpp"
#include "variable-string.hpp"
#include <QLabel>
#include <QPushButton>
#include <QTimer>
#include <QLabel>
#include <memory>
#include <chrono>
@@ -12,18 +11,16 @@ namespace advss {
class Macro;
class MacroDock : public OBSDock {
class MacroDock : public QFrame {
Q_OBJECT
public:
MacroDock(std::weak_ptr<Macro>, QWidget *parent,
const StringVariable &runButtonText,
MacroDock(std::weak_ptr<Macro>, const StringVariable &runButtonText,
const StringVariable &pauseButtonText,
const StringVariable &unpauseButtonText,
const StringVariable &conditionsTrueText,
const StringVariable &conditionsFalseText,
bool enableHighlight);
void SetName(const QString &);
void ShowRunButton(bool);
void SetRunButtonText(const StringVariable &);
void ShowPauseButton(bool);

View File

@@ -92,7 +92,7 @@ void MacroInputSelection::SetInputs(const MacroInputVariables &inputs)
void MacroInputSelection::Add()
{
std::string varName;
bool accepted = VariableSelectionDialog::AskForVariable(this, varName);
bool accepted = VariableSelectionDialog::AskForVariable(varName);
if (!accepted || varName.empty()) {
return;
@@ -155,7 +155,7 @@ void MacroInputSelection::Down()
void MacroInputSelection::Clicked(QListWidgetItem *item)
{
std::string varName;
bool accepted = VariableSelectionDialog::AskForVariable(this, varName);
bool accepted = VariableSelectionDialog::AskForVariable(varName);
if (!accepted || varName.empty()) {
return;

View File

@@ -79,7 +79,7 @@ void MacroList::MacroRemove(const QString &name)
void MacroList::Add()
{
std::string macroName;
bool accepted = MacroSelectionDialog::AskForMacro(this, macroName);
bool accepted = MacroSelectionDialog::AskForMacro(macroName);
if (!accepted || macroName.empty()) {
return;
@@ -132,7 +132,7 @@ void MacroList::Down()
void MacroList::Clicked(QListWidgetItem *item)
{
std::string macroName;
bool accepted = MacroSelectionDialog::AskForMacro(this, macroName);
bool accepted = MacroSelectionDialog::AskForMacro(macroName);
if (!accepted || macroName.empty()) {
return;

View File

@@ -0,0 +1,447 @@
#include "macro-script-handler.hpp"
#include "macro-action-script.hpp"
#include "macro-condition-script.hpp"
#include "plugin-state-helpers.hpp"
#include "log-helper.hpp"
#include "variable.hpp"
#include <obs-properties.h>
namespace advss {
std::mutex ScriptHandler::_mutex = {};
std::unordered_map<std::string, ScriptSegmentType> ScriptHandler::_actions = {};
std::unordered_map<std::string, ScriptSegmentType> ScriptHandler::_conditions =
{};
/* Procedure handler helpers */
#define RETURN_STATUS(status) \
{ \
calldata_set_bool(data, "success", status); \
return; \
}
#define RETURN_SUCCESS() RETURN_STATUS(true);
#define RETURN_FAILURE() RETURN_STATUS(false);
static constexpr std::string_view nameParam = "name";
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 std::string getRegisterScriptSegmentDeclString(const char *funcName)
{
return std::string("bool ") + funcName + "(in string " +
nameParam.data() + ", in ptr " + defaultSettingsParam.data() +
", out string " + propertiesSignalParam.data() +
", out string " + triggerSignalParam.data() + ")";
}
static std::string getDeregisterScriptSegmentDeclString(const char *funcName)
{
return std::string("bool ") + funcName + "(in string " +
nameParam.data() + ")";
}
/* Script actions */
static constexpr std::string_view registerActionFuncName =
"advss_register_script_action";
static constexpr std::string_view deregisterActionFuncName =
"advss_deregister_script_action";
static const std::string registerScriptActionDeclString =
getRegisterScriptSegmentDeclString(registerActionFuncName.data());
static const std::string deregisterScriptActionDeclString =
getDeregisterScriptSegmentDeclString(deregisterActionFuncName.data());
/* Script conditions */
static constexpr std::string_view registerConditionFuncName =
"advss_register_script_condition";
static constexpr std::string_view deregisterConditionFuncName =
"advss_deregister_script_condition";
static const std::string registerScriptConditionDeclString =
getRegisterScriptSegmentDeclString(registerConditionFuncName.data());
static const std::string deregisterScriptConditionDeclString =
getDeregisterScriptSegmentDeclString(
deregisterConditionFuncName.data());
/* Script variables */
static constexpr std::string_view valueParam = "value";
static constexpr std::string_view getVariableValueFuncName =
"advss_get_variable_value";
static constexpr std::string_view setVariableValueFuncName =
"advss_set_variable_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 bool setup();
static bool setupDone = setup();
static bool setup()
{
proc_handler_t *ph = obs_get_proc_handler();
assert(ph != NULL);
proc_handler_add(ph, registerScriptActionDeclString.c_str(),
&ScriptHandler::RegisterScriptAction, nullptr);
proc_handler_add(ph, deregisterScriptActionDeclString.c_str(),
&ScriptHandler::DeregisterScriptAction, nullptr);
proc_handler_add(ph, registerScriptConditionDeclString.c_str(),
&ScriptHandler::RegisterScriptCondition, nullptr);
proc_handler_add(ph, deregisterScriptConditionDeclString.c_str(),
&ScriptHandler::DeregisterScriptCondition, nullptr);
proc_handler_add(ph, getVariableValueDeclString.c_str(),
&ScriptHandler::GetVariableValue, nullptr);
proc_handler_add(ph, setVariableValueDeclString.c_str(),
&ScriptHandler::SetVariableValue, nullptr);
return true;
}
static void replaceWhitespace(std::string &string)
{
std::transform(string.begin(), string.end(), string.begin(), [](char c) {
return std::isspace(static_cast<unsigned char>(c)) ? '_' : c;
});
}
static std::string nameToScriptID(const std::string &name)
{
return std::string("script_") + name;
}
static std::string getTriggerSignal(const std::string &name,
const bool isAction)
{
std::string signal = name;
replaceWhitespace(signal);
signal += "_run";
signal += isAction ? "_action" : "_condition";
return signal;
}
static std::string getCompletionSignal(const std::string &name,
const bool isAction)
{
auto signal = getTriggerSignal(name, isAction);
signal += "_complete";
return signal;
}
static std::string getPropertiesSignal(const std::string &name,
const bool isAction)
{
std::string signal = name;
replaceWhitespace(signal);
signal += isAction ? "_action" : "_condition";
signal += "_get_properties";
return signal;
}
void ScriptHandler::RegisterScriptAction(void *, calldata_t *data)
{
const char *actionName;
if (!calldata_get_string(data, nameParam.data(), &actionName) ||
strlen(actionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerScriptActionDeclString.data(), nameParam.data());
RETURN_FAILURE();
}
obs_data_t *defaultSettingsPtr = nullptr;
if (!calldata_get_ptr(data, defaultSettingsParam.data(),
&defaultSettingsPtr)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerActionFuncName.data(),
defaultSettingsParam.data());
RETURN_FAILURE();
}
std::lock_guard<std::mutex> lock(_mutex);
OBSData defaultSettings(defaultSettingsPtr);
obs_data_release(defaultSettingsPtr);
if (_actions.count(actionName) > 0) {
blog(LOG_WARNING, "[%s] failed! Action \"%s\" already exists!",
registerActionFuncName.data(), actionName);
RETURN_FAILURE();
}
const std::string id = nameToScriptID(actionName);
auto triggerSignalName = getTriggerSignal(actionName, true);
auto completionSignalName = getCompletionSignal(actionName, true);
auto propertiesSignalName = getPropertiesSignal(actionName, true);
const auto createScriptAction =
[id, defaultSettings, propertiesSignalName, triggerSignalName,
completionSignalName](
Macro *m) -> std::shared_ptr<MacroAction> {
return std::make_shared<MacroActionScript>(
m, id, defaultSettings, propertiesSignalName,
triggerSignalName, completionSignalName);
};
if (!MacroActionFactory::Register(id, {createScriptAction,
MacroSegmentScriptEdit::Create,
actionName})) {
blog(LOG_WARNING,
"[%s] failed! Action id \"%s\" already exists!",
registerActionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
blog(LOG_INFO, "[%s] successful for \"%s\"",
registerActionFuncName.data(), actionName);
calldata_set_string(data, triggerSignalParam.data(),
triggerSignalName.c_str());
calldata_set_string(data, propertiesSignalParam.data(),
propertiesSignalName.c_str());
_actions.emplace(id, ScriptSegmentType(id, propertiesSignalName,
triggerSignalName,
completionSignalName));
RETURN_SUCCESS();
}
void ScriptHandler::DeregisterScriptAction(void *, calldata_t *data)
{
const char *actionName;
if (!calldata_get_string(data, nameParam.data(), &actionName) ||
strlen(actionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
deregisterActionFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
const std::string id = nameToScriptID(actionName);
std::lock_guard<std::mutex> lock(_mutex);
if (_actions.count(id) == 0) {
blog(LOG_WARNING,
"[%s] failed! Action \"%s\" was never registered!",
deregisterActionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
if (!MacroActionFactory::Deregister(id)) {
blog(LOG_WARNING,
"[%s] failed! Action id \"%s\" does not exist!",
deregisterActionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
auto it = _actions.find(id);
if (it != _actions.end()) {
_actions.erase(it);
}
RETURN_SUCCESS();
}
void ScriptHandler::RegisterScriptCondition(void *, calldata_t *data)
{
const char *conditionName;
if (!calldata_get_string(data, nameParam.data(), &conditionName) ||
strlen(conditionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerScriptConditionDeclString.data(),
nameParam.data());
RETURN_FAILURE();
}
obs_data_t *defaultSettingsPtr = nullptr;
if (!calldata_get_ptr(data, defaultSettingsParam.data(),
&defaultSettingsPtr)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerScriptConditionDeclString.data(),
defaultSettingsParam.data());
RETURN_FAILURE();
}
std::lock_guard<std::mutex> lock(_mutex);
OBSData defaultSettings(defaultSettingsPtr);
obs_data_release(defaultSettingsPtr);
if (_conditions.count(conditionName) > 0) {
blog(LOG_WARNING,
"[%s] failed! Condition \"%s\" already exists!",
registerConditionFuncName.data(), conditionName);
RETURN_FAILURE();
}
const std::string id = nameToScriptID(conditionName);
auto triggerSignalName = getTriggerSignal(conditionName, false);
auto completionSignalName = getCompletionSignal(conditionName, false);
auto propertiesSignalName = getPropertiesSignal(conditionName, false);
const auto createScriptCondition =
[id, defaultSettings, propertiesSignalName, triggerSignalName,
completionSignalName](
Macro *m) -> std::shared_ptr<MacroCondition> {
return std::make_shared<MacroConditionScript>(
m, id, defaultSettings, propertiesSignalName,
triggerSignalName, completionSignalName);
};
if (!MacroConditionFactory::Register(
id, {createScriptCondition, MacroSegmentScriptEdit::Create,
conditionName})) {
blog(LOG_WARNING,
"[%s] failed! Condition id \"%s\" already exists!",
registerConditionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
blog(LOG_INFO, "[%s] successful for \"%s\"",
registerConditionFuncName.data(), conditionName);
calldata_set_string(data, triggerSignalParam.data(),
triggerSignalName.c_str());
calldata_set_string(data, propertiesSignalParam.data(),
propertiesSignalName.c_str());
_conditions.emplace(id, ScriptSegmentType(id, propertiesSignalName,
triggerSignalName,
completionSignalName));
RETURN_SUCCESS();
}
void ScriptHandler::DeregisterScriptCondition(void *, calldata_t *data)
{
const char *conditionName;
if (!calldata_get_string(data, nameParam.data(), &conditionName) ||
strlen(conditionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
deregisterConditionFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
const std::string id = nameToScriptID(conditionName);
std::lock_guard<std::mutex> lock(_mutex);
if (_conditions.count(id) == 0) {
blog(LOG_WARNING,
"[%s] failed! Condition \"%s\" was never registered!",
deregisterConditionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
if (!MacroConditionFactory::Deregister(id)) {
blog(LOG_WARNING,
"[%s] failed! Condition id \"%s\" does not exist!",
deregisterConditionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
auto it = _conditions.find(id);
if (it != _conditions.end()) {
_conditions.erase(it);
}
RETURN_SUCCESS();
}
void ScriptHandler::GetVariableValue(void *, calldata_t *data)
{
const char *variableName;
if (!calldata_get_string(data, nameParam.data(), &variableName) ||
strlen(variableName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
getVariableValueFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
auto weakVariable = GetWeakVariableByName(variableName);
auto variable = weakVariable.lock();
if (!variable) {
blog(LOG_WARNING,
"[%s] failed! \"%s\" variable does not exist!",
getVariableValueFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
calldata_set_string(data, valueParam.data(), variable->Value().c_str());
RETURN_SUCCESS();
}
void ScriptHandler::SetVariableValue(void *, calldata_t *data)
{
const char *variableName;
if (!calldata_get_string(data, nameParam.data(), &variableName) ||
strlen(variableName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
getVariableValueFuncName.data(), nameParam.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!",
getVariableValueFuncName.data(), valueParam.data());
RETURN_FAILURE();
}
auto weakVariable = GetWeakVariableByName(variableName);
auto variable = weakVariable.lock();
if (!variable) {
blog(LOG_WARNING,
"[%s] failed! \"%s\" variable does not exist!",
getVariableValueFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
variable->SetValue(variableValue);
RETURN_SUCCESS();
}
bool ScriptHandler::ActionIdIsValid(const std::string &id)
{
std::lock_guard<std::mutex> lock(_mutex);
return _actions.count(id) > 0;
}
bool ScriptHandler::ConditionIdIsValid(const std::string &id)
{
std::lock_guard<std::mutex> lock(_mutex);
return _conditions.count(id) > 0;
}
static std::string signalNameToTriggerSignalDecl(const std::string &name)
{
return std::string("void ") + name + "()";
}
static std::string signalNameToPropertiesSignalDecl(const std::string &name)
{
return std::string("void ") + name + "(in ptr " +
GetPropertiesSignalParamName().data() + ")";
}
static std::string signalNameToCompletionSignalDecl(const std::string &name)
{
return std::string("bool ") + name + "(in int " +
GeCompletionIdParamName().data() + ")";
}
ScriptSegmentType::ScriptSegmentType(const std::string &id,
const std::string &propertiesSignal,
const std::string &triggerSignal,
const std::string &completionSignal)
: _id(id)
{
signal_handler_add(
obs_get_signal_handler(),
signalNameToPropertiesSignalDecl(propertiesSignal).c_str());
signal_handler_add(
obs_get_signal_handler(),
signalNameToTriggerSignalDecl(triggerSignal).c_str());
signal_handler_add(
obs_get_signal_handler(),
signalNameToCompletionSignalDecl(completionSignal).c_str());
}
} // namespace advss

View File

@@ -0,0 +1,59 @@
#pragma once
#include <obs.hpp>
#include <mutex>
#include <string>
#include <unordered_map>
namespace advss {
class ScriptSegmentType {
public:
ScriptSegmentType() = delete;
ScriptSegmentType(const std::string &id,
const std::string &propertiesSignal,
const std::string &triggerSignal,
const std::string &completionSignal);
private:
std::string _id;
};
class ScriptHandler {
public:
ScriptHandler() = delete;
static void RegisterScriptAction(void *ctx, calldata_t *data);
static void DeregisterScriptAction(void *ctx, calldata_t *data);
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 SetVariableValue(void *ctx, calldata_t *data);
static bool ActionIdIsValid(const std::string &id);
static bool ConditionIdIsValid(const std::string &id);
private:
static std::mutex _mutex;
static std::unordered_map<std::string, ScriptSegmentType> _actions;
static std::unordered_map<std::string, ScriptSegmentType> _conditions;
};
static constexpr std::string_view GetPropertiesSignalParamName()
{
return "properties";
}
static constexpr std::string_view GetActionCompletionSignalParamName()
{
return "completion_signal_name";
}
static constexpr std::string_view GeCompletionIdParamName()
{
return "completion_id";
}
static constexpr std::string_view GeResultSignalParamName()
{
return "result";
}
} // namespace advss

View File

@@ -1,5 +1,7 @@
#include "macro-segment-copy-paste.hpp"
#include "advanced-scene-switcher.hpp"
#include "macro.hpp"
#include "ui-helpers.hpp"
#include <QShortcut>
@@ -61,23 +63,35 @@ void AdvSceneSwitcher::PasteMacroSegment()
const auto condition = std::static_pointer_cast<MacroCondition>(
copyInfo.segment);
auto logic = condition->GetLogicType();
if (logic > LogicType::ROOT_LAST &&
if (logic > Logic::Type::ROOT_LAST &&
macro->Conditions().empty()) {
logic = LogicType::ROOT_NONE;
logic = Logic::Type::ROOT_NONE;
}
if (logic < LogicType::ROOT_LAST &&
if (logic < Logic::Type::ROOT_LAST &&
!macro->Conditions().empty()) {
logic = LogicType::OR;
logic = Logic::Type::OR;
}
AddMacroCondition(macro.get(), macro->Conditions().size(),
copyInfo.segment->GetId(), data.Get(), logic);
break;
}
case MacroSegmentCopyInfo::Type::ACTION:
if (IsCursorInWidgetArea(ui->macroElseActions)) {
AddMacroElseAction(macro.get(),
macro->ElseActions().size(),
copyInfo.segment->GetId(),
data.Get());
break;
}
AddMacroAction(macro.get(), macro->Actions().size(),
copyInfo.segment->GetId(), data.Get());
break;
case MacroSegmentCopyInfo::Type::ELSE:
if (IsCursorInWidgetArea(ui->macroActions)) {
AddMacroAction(macro.get(), macro->Actions().size(),
copyInfo.segment->GetId(), data.Get());
break;
}
AddMacroElseAction(macro.get(), macro->ElseActions().size(),
copyInfo.segment->GetId(), data.Get());
break;
@@ -91,6 +105,12 @@ bool MacroSegmentIsInClipboard()
return copyInfo.type != MacroSegmentCopyInfo::Type::NONE;
}
bool MacroActionIsInClipboard()
{
return copyInfo.type == MacroSegmentCopyInfo::Type::ACTION ||
copyInfo.type == MacroSegmentCopyInfo::Type::ELSE;
}
void SetupSegmentCopyPasteShortcutHandlers(AdvSceneSwitcher *window)
{
auto copyShortcut = new QShortcut(QKeySequence("Ctrl+C"), window);

View File

@@ -3,6 +3,7 @@ namespace advss {
class AdvSceneSwitcher;
bool MacroSegmentIsInClipboard();
bool MacroActionIsInClipboard();
void SetupSegmentCopyPasteShortcutHandlers(AdvSceneSwitcher *window);
} // namespace advss

View File

@@ -0,0 +1,219 @@
#include "macro-segment-script.hpp"
#include "layout-helpers.hpp"
#include "macro-action.hpp"
#include "macro-condition.hpp"
#include "macro-helpers.hpp"
#include "macro-script-handler.hpp"
#include "obs-module-helper.hpp"
#include "properties-view.hpp"
#include "sync-helpers.hpp"
namespace advss {
static std::atomic_int completionIdCounter = 0;
MacroSegmentScript::MacroSegmentScript(obs_data_t *defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &completionSignal)
: _settings(obs_data_get_defaults(defaultSettings)),
_propertiesSignal(propertiesSignalName),
_triggerSignal(triggerSignal),
_completionSignal(completionSignal)
{
signal_handler_connect(obs_get_signal_handler(),
completionSignal.c_str(),
&MacroSegmentScript::CompletionSignalReceived,
this);
}
MacroSegmentScript::MacroSegmentScript(const MacroSegmentScript &other)
: _settings(obs_data_create()),
_propertiesSignal(other._propertiesSignal),
_triggerSignal(other._triggerSignal),
_completionSignal(other._completionSignal)
{
signal_handler_connect(obs_get_signal_handler(),
_completionSignal.c_str(),
&MacroSegmentScript::CompletionSignalReceived,
this);
obs_data_apply(_settings.Get(), other._settings.Get());
}
bool MacroSegmentScript::Save(obs_data_t *obj) const
{
obs_data_set_obj(obj, "settings", _settings.Get());
_timeout.Save(obj);
return true;
}
bool MacroSegmentScript::Load(obs_data_t *obj)
{
OBSDataAutoRelease settings = obs_data_get_obj(obj, "settings");
obs_data_apply(_settings.Get(), settings);
_timeout.Load(obj);
return true;
}
obs_properties_t *MacroSegmentScript::GetProperties() const
{
auto data = calldata_create();
signal_handler_signal(obs_get_signal_handler(),
_propertiesSignal.c_str(), data);
obs_properties_t *properties = nullptr;
if (!calldata_get_ptr(data, GetPropertiesSignalParamName().data(),
&properties)) {
calldata_destroy(data);
return nullptr;
}
calldata_destroy(data);
return properties;
}
void MacroSegmentScript::UpdateSettings(obs_data_t *newSettings) const
{
obs_data_clear(_settings.Get());
obs_data_apply(_settings.Get(), newSettings);
}
bool MacroSegmentScript::SendTriggerSignal()
{
_completionId = ++completionIdCounter;
_triggerIsComplete = false;
_triggerResult = false;
auto data = calldata_create();
calldata_set_string(data, GetActionCompletionSignalParamName().data(),
_completionSignal.c_str());
calldata_set_int(data, GeCompletionIdParamName().data(), _completionId);
calldata_set_string(data, "settings", obs_data_get_json(GetSettings()));
signal_handler_signal(obs_get_signal_handler(), _triggerSignal.c_str(),
data);
calldata_destroy(data);
SetMacroAbortWait(false);
WaitForCompletion();
return _triggerResult;
}
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)) {
blog(LOG_WARNING,
"received completion signal without \"%s\" parameter",
GeCompletionIdParamName().data());
return;
}
bool result;
if (!calldata_get_bool(data, GeResultSignalParamName().data(),
&result)) {
blog(LOG_WARNING,
"received completion signal without \"%s\" parameter",
GeResultSignalParamName().data());
return;
}
if (id != segment->_completionId) {
return;
}
segment->_triggerIsComplete = true;
segment->_triggerResult = result;
}
obs_properties_t *MacroSegmentScriptEdit::GetProperties(void *obj)
{
auto segmentEdit = reinterpret_cast<MacroSegmentScriptEdit *>(obj);
if (!segmentEdit) {
return nullptr;
}
return segmentEdit->_entryData->GetProperties();
}
void MacroSegmentScriptEdit::UpdateSettings(void *obj, obs_data_t *settings)
{
auto segmentEdit = reinterpret_cast<MacroSegmentScriptEdit *>(obj);
if (!segmentEdit || segmentEdit->_loading || !segmentEdit->_entryData) {
return;
}
auto lock = LockContext();
segmentEdit->_entryData->UpdateSettings(settings);
}
MacroSegmentScriptEdit::MacroSegmentScriptEdit(
QWidget *parent, std::shared_ptr<MacroSegmentScript> entryData)
: QWidget(parent),
_timeout(new DurationSelection(this))
{
QWidget::connect(_timeout, &DurationSelection::DurationChanged, this,
&MacroSegmentScriptEdit::TimeoutChanged);
auto timeoutLayout = new QHBoxLayout();
PlaceWidgets(obs_module_text("AdvSceneSwitcher.script.timeout"),
timeoutLayout, {{"{{timeout}}", _timeout}});
auto layout = new QVBoxLayout();
auto properties = entryData->GetProperties();
if (!!properties) {
obs_properties_destroy(properties);
// We need a separate OBSData object here as we can't risk
// entryData->_settings being modified while it is currently used
OBSDataAutoRelease data = obs_data_create();
obs_data_apply(data, entryData->GetSettings());
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(30, 0, 0)
auto propertiesView =
new OBSPropertiesView(data.Get(), this, GetProperties,
nullptr, UpdateSettings);
layout->addWidget(propertiesView);
connect(propertiesView, &OBSPropertiesView::PropertiesResized,
this, [this]() {
adjustSize();
updateGeometry();
});
#else
layout->addWidget(new QLabel(
"Displaying script properties not supported when compiled for OBS 29!"));
#endif
}
layout->addLayout(timeoutLayout);
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
adjustSize();
updateGeometry();
}
void MacroSegmentScriptEdit::UpdateEntryData()
{
_timeout->SetDuration(_entryData->_timeout);
}
QWidget *MacroSegmentScriptEdit::Create(QWidget *parent,
std::shared_ptr<MacroAction> segment)
{
return new MacroSegmentScriptEdit(
parent, std::dynamic_pointer_cast<MacroSegmentScript>(segment));
}
QWidget *MacroSegmentScriptEdit::Create(QWidget *parent,
std::shared_ptr<MacroCondition> segment)
{
return new MacroSegmentScriptEdit(
parent, std::dynamic_pointer_cast<MacroSegmentScript>(segment));
}
void MacroSegmentScriptEdit::TimeoutChanged(const Duration &timeout)
{
GUARD_LOADING_AND_LOCK();
_entryData->_timeout = timeout;
}
} // namespace advss

View File

@@ -0,0 +1,78 @@
#pragma once
#include "duration-control.hpp"
#include "macro-script-handler.hpp"
#include <atomic>
#include <obs-data.h>
namespace advss {
class Macro;
class MacroAction;
class MacroCondition;
class MacroSegmentScript {
public:
MacroSegmentScript(obs_data_t *defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &completionSignal);
MacroSegmentScript(const advss::MacroSegmentScript &);
protected:
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
obs_properties_t *GetProperties() const;
OBSData GetSettings() const { return _settings.Get(); }
void UpdateSettings(obs_data_t *newSettings) const;
bool SendTriggerSignal();
double GetTimeoutSeconds() const { return _timeout.Seconds(); };
bool TriggerIsCompleted() const { return _triggerIsComplete; }
private:
virtual void WaitForCompletion() const = 0;
static void CompletionSignalReceived(void *param, calldata_t *data);
OBSDataAutoRelease _settings;
std::string _propertiesSignal = "";
std::string _triggerSignal = "";
std::string _completionSignal = "";
std::atomic_bool _triggerIsComplete = {false};
bool _triggerResult = false;
int64_t _completionId = 0;
Duration _timeout = Duration(10.0);
friend class MacroSegmentScriptEdit;
};
class MacroSegmentScriptEdit : public QWidget {
Q_OBJECT
public:
MacroSegmentScriptEdit(
QWidget *parent,
std::shared_ptr<MacroSegmentScript> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> segment);
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> segment);
private slots:
void TimeoutChanged(const Duration &);
private:
static obs_properties_t *GetProperties(void *obj);
static void UpdateSettings(void *obj, obs_data_t *settings);
DurationSelection *_timeout;
std::shared_ptr<MacroSegmentScript> _entryData;
bool _loading = true;
};
} // namespace advss

View File

@@ -70,7 +70,8 @@ void MacroSegmentSelection::MacroSegmentOrderChanged()
SetupDescription();
}
QString GetMacroSegmentDescription(Macro *macro, int idx, bool isCondition)
static QString GetMacroSegmentDescription(Macro *macro, int idx,
bool isCondition)
{
if (!macro) {
return "";

View File

@@ -68,7 +68,7 @@ void MacroSelection::HideSelectedMacro()
void MacroSelection::ShowAllMacros()
{
auto v = qobject_cast<QListView *>(view());
for (int i = count(); i > 0; i--) {
for (int i = count() - 1; i >= 0; i--) {
v->setRowHidden(i, false);
}
}
@@ -126,7 +126,7 @@ MacroSelectionDialog::MacroSelectionDialog(QWidget *parent)
setLayout(layout);
}
bool MacroSelectionDialog::AskForMacro(QWidget *parent, std::string &macroName)
bool MacroSelectionDialog::AskForMacro(std::string &macroName)
{
MacroSelectionDialog dialog(GetSettingsWindow());
dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));

View File

@@ -28,7 +28,7 @@ class MacroSelectionDialog : public QDialog {
public:
MacroSelectionDialog(QWidget *parent);
static bool AskForMacro(QWidget *parent, std::string &macroName);
static bool AskForMacro(std::string &macroName);
private:
MacroSelection *_macroSelection;

View File

@@ -100,8 +100,10 @@ void AdvSceneSwitcher::on_macroAdd_clicked()
}
ui->macros->Add(newMacro);
addPulse->deleteLater();
addPulse = nullptr;
if (addPulse) {
addPulse->deleteLater();
addPulse = nullptr;
}
emit MacroAdded(QString::fromStdString(name));
}
@@ -1047,8 +1049,30 @@ void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
menu.exec(globalPos);
}
static void handleCustomLabelChange(MacroSegmentEdit *segmentEdit,
QAction *contextMenuOption)
static bool handleCustomLabelRename(MacroSegmentEdit *segmentEdit)
{
std::string label;
auto segment = segmentEdit->Data();
if (!segment) {
return false;
}
bool accepted = NameDialog::AskForName(
GetSettingsWindow(),
obs_module_text(
"AdvSceneSwitcher.macroTab.segment.setCustomLabel"),
"", label, QString::fromStdString(segment->GetCustomLabel()));
if (!accepted) {
return false;
}
segment->SetCustomLabel(label);
segmentEdit->HeaderInfoChanged("");
return true;
}
static void handleCustomLabelEnableChange(MacroSegmentEdit *segmentEdit,
QAction *contextMenuOption)
{
bool enable = contextMenuOption->isChecked();
auto segment = segmentEdit->Data();
@@ -1059,19 +1083,66 @@ static void handleCustomLabelChange(MacroSegmentEdit *segmentEdit,
return;
}
std::string label;
bool accepted = NameDialog::AskForName(
GetSettingsWindow(),
obs_module_text(
"AdvSceneSwitcher.macroTab.segment.setCustomLabel"),
"", label, QString::fromStdString(segment->GetCustomLabel()));
if (!accepted) {
if (!handleCustomLabelRename(segmentEdit)) {
segment->SetUseCustomLabel(false);
}
}
static void setupSegmentLabelContextMenuEntries(MacroSegmentEdit *segmentEdit,
QMenu &menu)
{
if (!segmentEdit) {
return;
}
segment->SetCustomLabel(label);
segmentEdit->HeaderInfoChanged("");
auto segment = segmentEdit ? segmentEdit->Data() : nullptr;
const bool customLabelIsEnabled = segment &&
segment->GetUseCustomLabel();
auto enableCustomLabel = menu.addAction(obs_module_text(
"AdvSceneSwitcher.macroTab.segment.useCustomLabel"));
enableCustomLabel->setCheckable(true);
enableCustomLabel->setChecked(customLabelIsEnabled);
QWidget::connect(enableCustomLabel, &QAction::triggered,
[segmentEdit, enableCustomLabel]() {
handleCustomLabelEnableChange(
segmentEdit, enableCustomLabel);
});
if (!customLabelIsEnabled) {
return;
}
auto customLabelRename = menu.addAction(obs_module_text(
"AdvSceneSwitcher.macroTab.segment.customLabelRename"));
QWidget::connect(customLabelRename, &QAction::triggered,
[segmentEdit]() {
handleCustomLabelRename(segmentEdit);
});
}
static void setupCopyPasteContextMenuEnry(AdvSceneSwitcher *ss,
MacroSegmentEdit *segmentEdit,
QMenu &menu)
{
auto copy = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.segment.copy"), ss,
[ss]() { ss->CopyMacroSegment(); });
copy->setEnabled(!!segmentEdit);
const char *pasteText = "AdvSceneSwitcher.macroTab.segment.paste";
if (MacroActionIsInClipboard()) {
if (IsCursorInWidgetArea(ss->ui->macroActions)) {
pasteText =
"AdvSceneSwitcher.macroTab.segment.pasteAction";
} else if (IsCursorInWidgetArea(ss->ui->macroElseActions)) {
pasteText =
"AdvSceneSwitcher.macroTab.segment.pasteElseAction";
}
}
auto paste = menu.addAction(obs_module_text(pasteText), ss,
[ss]() { ss->PasteMacroSegment(); });
paste->setEnabled(MacroSegmentIsInClipboard());
}
static void setupConextMenu(AdvSceneSwitcher *ss, const QPoint &pos,
@@ -1084,43 +1155,19 @@ static void setupConextMenu(AdvSceneSwitcher *ss, const QPoint &pos,
QMenu menu;
auto segmentEdit = list->WidgetAt(pos);
auto copy = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.segment.copy"), ss,
[ss]() { ss->CopyMacroSegment(); });
copy->setEnabled(!!segmentEdit);
auto paste = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.segment.paste"), ss,
[ss]() { ss->PasteMacroSegment(); });
paste->setEnabled(MacroSegmentIsInClipboard());
setupCopyPasteContextMenuEnry(ss, segmentEdit, menu);
menu.addSeparator();
if (segmentEdit) {
auto customLabel = menu.addAction(obs_module_text(
"AdvSceneSwitcher.macroTab.segment.useCustomLabel"));
customLabel->setCheckable(true);
auto segment = segmentEdit ? segmentEdit->Data() : nullptr;
customLabel->setChecked(segment &&
segment->GetUseCustomLabel());
QWidget::connect(customLabel, &QAction::triggered,
std::bind(handleCustomLabelChange, segmentEdit,
customLabel));
}
setupSegmentLabelContextMenuEntries(segmentEdit, menu);
menu.addSeparator();
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"),
ss, [ss, expand]() { expand(ss); });
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"),
ss, [ss, collapse]() { collapse(ss); });
menu.addSeparator();
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"),
ss, [ss, maximize]() { maximize(ss); });
menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"),
ss, [ss, minimize]() { minimize(ss); });
menu.exec(list->mapToGlobal(pos));
}
@@ -1165,7 +1212,7 @@ void AdvSceneSwitcher::CopyMacro()
}
OBSDataAutoRelease data = obs_data_create();
macro->Save(data);
macro->Save(data, true);
newMacro->Load(data);
newMacro->PostLoad();
newMacro->SetName(name);
@@ -1173,8 +1220,10 @@ void AdvSceneSwitcher::CopyMacro()
Macro::PrepareMoveToGroup(macro->Parent(), newMacro);
ui->macros->Add(newMacro, macro);
addPulse->deleteLater();
addPulse = nullptr;
if (addPulse) {
addPulse->deleteLater();
addPulse = nullptr;
}
emit MacroAdded(QString::fromStdString(name));
}

View File

@@ -160,7 +160,7 @@ void MacroTreeItem::Update(bool force)
_spacer = nullptr;
}
if (_type == Type::Group) {
if (_type == Type::Group && _expand) {
_boxLayout->removeWidget(_expand);
_expand->deleteLater();
_expand = nullptr;

View File

@@ -14,10 +14,66 @@
#include <QAction>
#include <QMainWindow>
#include <unordered_map>
#include <util/platform.h>
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(30, 0, 0)
#include <QDockWidget>
namespace {
struct DockMapEntry {
QAction *action = nullptr;
QWidget *dock = nullptr;
};
} // namespace
static std::unordered_map<const char *, DockMapEntry> dockIds;
static std::mutex dockMutex;
static bool obs_frontend_add_dock_by_id(const char *id, const char *title,
QWidget *widget)
{
std::lock_guard<std::mutex> lock(dockMutex);
if (dockIds.count(id) > 0) {
return false;
}
widget->setObjectName(id);
auto dock = new QDockWidget();
dock->setWindowTitle(title);
dock->setWidget(widget);
dock->setFloating(true);
dock->setVisible(false);
dock->setFeatures(QDockWidget::DockWidgetClosable |
QDockWidget::DockWidgetMovable |
QDockWidget::DockWidgetFloatable);
auto action = static_cast<QAction *>(obs_frontend_add_dock(dock));
if (!action) {
return false;
}
dockIds[id] = {action, dock};
return true;
}
static void obs_frontend_remove_dock(const char *id)
{
std::lock_guard<std::mutex> lock(dockMutex);
auto it = dockIds.find(id);
if (it == dockIds.end()) {
return;
}
it->second.action->deleteLater();
it->second.dock->deleteLater();
dockIds.erase(it);
}
#endif
namespace advss {
static constexpr int perfLogThreshold = 300;
static std::deque<std::shared_ptr<Macro>> macros;
Macro::Macro(const std::string &name, const bool addHotkey)
@@ -101,6 +157,30 @@ void Macro::PrepareMoveToGroup(std::shared_ptr<Macro> group,
}
}
static bool checkCondition(const std::shared_ptr<MacroCondition> &condition)
{
using namespace std::chrono_literals;
static constexpr auto perfLogThreshold = 300ms;
const auto startTime = std::chrono::high_resolution_clock::now();
const bool conditionMatched = condition->CheckCondition();
const auto endTime = std::chrono::high_resolution_clock::now();
const auto timeSpent = endTime - startTime;
if (timeSpent >= perfLogThreshold) {
const long int ms =
std::chrono::duration_cast<std::chrono::milliseconds>(
timeSpent)
.count();
blog(LOG_WARNING,
"spent %ld ms in %s condition check of macro '%s'!", ms,
condition->GetId().c_str(),
condition->GetMacro()->Name().c_str());
}
return conditionMatched;
}
bool Macro::CeckMatch(bool ignorePause)
{
if (_isGroup) {
@@ -108,77 +188,36 @@ bool Macro::CeckMatch(bool ignorePause)
}
_matched = false;
for (auto &c : _conditions) {
for (auto &condition : _conditions) {
if (_paused && !ignorePause) {
vblog(LOG_INFO, "Macro %s is paused", _name.c_str());
return false;
}
auto startTime = std::chrono::high_resolution_clock::now();
bool cond = c->CheckCondition();
auto endTime = std::chrono::high_resolution_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
endTime - startTime);
if (ms.count() >= perfLogThreshold) {
blog(LOG_WARNING,
"spent %ld ms in %s condition check of macro '%s'!",
(long int)ms.count(), c->GetId().c_str(),
Name().c_str());
}
bool conditionMatched = checkCondition(condition);
conditionMatched =
condition->CheckDurationModifier(conditionMatched);
c->CheckDurationModifier(cond);
switch (c->GetLogicType()) {
case LogicType::NONE:
vblog(LOG_INFO,
"ignoring condition check 'none' for '%s'",
_name.c_str());
const auto logicType = condition->GetLogicType();
if (logicType == Logic::Type::NONE) {
vblog(LOG_INFO, "ignoring condition '%s' for '%s'",
condition->GetId().c_str(), _name.c_str());
continue;
case LogicType::AND:
_matched = _matched && cond;
if (cond) {
c->EnableHighlight();
}
break;
case LogicType::OR:
_matched = _matched || cond;
if (cond) {
c->EnableHighlight();
}
break;
case LogicType::AND_NOT:
_matched = _matched && !cond;
if (!cond) {
c->EnableHighlight();
}
break;
case LogicType::OR_NOT:
_matched = _matched || !cond;
if (!cond) {
c->EnableHighlight();
}
break;
case LogicType::ROOT_NONE:
_matched = cond;
if (cond) {
c->EnableHighlight();
}
break;
case LogicType::ROOT_NOT:
_matched = !cond;
if (!cond) {
c->EnableHighlight();
}
break;
default:
blog(LOG_WARNING,
"ignoring unknown condition check for '%s'",
_name.c_str());
break;
}
vblog(LOG_INFO, "condition %s returned %d", c->GetId().c_str(),
cond);
vblog(LOG_INFO, "condition %s returned %d",
condition->GetId().c_str(), conditionMatched);
const bool isNegativeLogicType =
Logic::IsNegationType(logicType);
if ((conditionMatched && !isNegativeLogicType) ||
(!conditionMatched && isNegativeLogicType)) {
condition->EnableHighlight();
}
_matched = Logic::ApplyConditionLogic(
logicType, _matched, conditionMatched, _name.c_str());
}
vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched);
_conditionSateChanged = _lastMatched != _matched;
@@ -276,9 +315,15 @@ int64_t Macro::MsSinceLastCheck() const
void Macro::SetName(const std::string &name)
{
const bool nameChanged = _name == name;
_name = name;
SetHotkeysDesc();
SetDockWidgetName();
if (nameChanged) {
_dockId = GenerateDockId();
}
EnableDock(_registerDock);
}
void Macro::ResetTimers()
@@ -333,11 +378,6 @@ bool Macro::RunElseActions(bool ignorePause)
return RunActionsHelper(_elseActions, ignorePause);
}
bool Macro::DockIsVisible() const
{
return _dock && _dockAction && _dock->isVisible();
}
bool Macro::WasPausedSince(
const std::chrono::high_resolution_clock::time_point &time) const
{
@@ -568,9 +608,11 @@ std::shared_ptr<Macro> Macro::Parent() const
return _parent.lock();
}
bool Macro::Save(obs_data_t *obj) const
bool Macro::Save(obs_data_t *obj, bool saveForCopy) const
{
obs_data_set_string(obj, "name", _name.c_str());
if (!saveForCopy) {
obs_data_set_string(obj, "name", _name.c_str());
}
obs_data_set_bool(obj, "pause", _paused);
obs_data_set_bool(obj, "parallel", _runInParallel);
obs_data_set_bool(obj, "onChange", _performActionsOnChange);
@@ -586,7 +628,7 @@ bool Macro::Save(obs_data_t *obj) const
return true;
}
SaveDockSettings(obj);
SaveDockSettings(obj, saveForCopy);
SaveSplitterPos(_actionConditionSplitterPosition, obj,
"macroActionConditionSplitterPosition");
@@ -631,40 +673,6 @@ bool Macro::Save(obs_data_t *obj) const
return true;
}
bool isValidLogic(LogicType t, bool root)
{
bool isRoot = isRootLogicType(t);
if (!isRoot == root) {
return false;
}
if (isRoot) {
if (t >= LogicType::ROOT_LAST) {
return false;
}
} else if (t >= LogicType::LAST) {
return false;
}
return true;
}
void setValidLogic(MacroCondition *c, bool root, std::string name)
{
if (isValidLogic(c->GetLogicType(), root)) {
return;
}
if (root) {
c->SetLogicType(LogicType::ROOT_NONE);
blog(LOG_WARNING,
"setting invalid logic selection to 'if' for macro %s",
name.c_str());
} else {
c->SetLogicType(LogicType::NONE);
blog(LOG_WARNING,
"setting invalid logic selection to 'ignore' for macro %s",
name.c_str());
}
}
bool Macro::Load(obs_data_t *obj)
{
_name = obs_data_get_string(obj, "name");
@@ -720,7 +728,7 @@ bool Macro::Load(obs_data_t *obj)
_conditions.emplace_back(newEntry);
auto c = _conditions.back().get();
c->Load(arrayObj);
setValidLogic(c, root, _name);
c->ValidateLogicSelection(root, Name().c_str());
} else {
blog(LOG_WARNING,
"discarding condition entry with unknown id (%s) for macro %s",
@@ -865,14 +873,10 @@ bool Macro::PauseHotkeysEnabled() const
return _registerHotkeys;
}
void Macro::SaveDockSettings(obs_data_t *obj) const
void Macro::SaveDockSettings(obs_data_t *obj, bool saveForCopy) const
{
auto dockSettings = obs_data_create();
obs_data_set_bool(dockSettings, "register", _registerDock);
// The object name is used to restore the position of the dock
if (_registerDock) {
SetDockWidgetName();
}
obs_data_set_bool(dockSettings, "hasRunButton", _dockHasRunButton);
obs_data_set_bool(dockSettings, "hasPauseButton", _dockHasPauseButton);
obs_data_set_bool(dockSettings, "hasStatusLabel", _dockHasStatusLabel);
@@ -885,18 +889,14 @@ void Macro::SaveDockSettings(obs_data_t *obj) const
"conditionsTrueStatusText");
_conditionsFalseStatusText.Save(dockSettings,
"conditionsFalseStatusText");
if (_dock) {
auto window = static_cast<QMainWindow *>(
obs_frontend_get_main_window());
obs_data_set_bool(dockSettings, "isFloating",
_dock->isFloating());
obs_data_set_bool(dockSettings, "isVisible", DockIsVisible());
obs_data_set_int(dockSettings, "area",
window->dockWidgetArea(_dock));
obs_data_set_string(
dockSettings, "geometry",
_dock->saveGeometry().toBase64().constData());
if (saveForCopy) {
auto uuid = GenerateDockId();
obs_data_set_string(dockSettings, "dockId", uuid.c_str());
} else {
obs_data_set_string(dockSettings, "dockId", _dockId.c_str());
}
obs_data_set_int(dockSettings, "version", 1);
obs_data_set_obj(obj, "dockSettings", dockSettings);
obs_data_release(dockSettings);
}
@@ -913,8 +913,13 @@ void Macro::LoadDockSettings(obs_data_t *obj)
return;
}
if (!obs_data_has_user_value(dockSettings, "version")) {
_dockId = std::string("ADVSS-") + _name;
} else {
_dockId = obs_data_get_string(dockSettings, "dockId");
}
const bool dockEnabled = obs_data_get_bool(dockSettings, "register");
_dockIsVisible = obs_data_get_bool(dockSettings, "isVisible");
// TODO: remove these default settings in a future version
obs_data_set_default_string(
@@ -942,15 +947,6 @@ void Macro::LoadDockSettings(obs_data_t *obj)
obs_data_get_bool(dockSettings, "hasStatusLabel");
_dockHighlight = obs_data_get_bool(dockSettings,
"highlightIfConditionsTrue");
_dockIsFloating = obs_data_get_bool(dockSettings, "isFloating");
_dockArea = static_cast<Qt::DockWidgetArea>(
obs_data_get_int(dockSettings, "area"));
auto geometryStr =
obs_data_get_string(dockSettings, "geometry");
if (geometryStr && strlen(geometryStr)) {
_dockGeo =
QByteArray::fromBase64(QByteArray(geometryStr));
}
}
EnableDock(dockEnabled);
obs_data_release(dockSettings);
@@ -958,47 +954,29 @@ void Macro::LoadDockSettings(obs_data_t *obj)
void Macro::EnableDock(bool value)
{
if (_registerDock == value) {
return;
}
// Reset dock regardless
RemoveDock();
// Unregister dock
if (_registerDock) {
_dockIsFloating = true;
_dockGeo = QByteArray();
if (!value) {
_registerDock = value;
return;
}
// Create new dock widget
auto window =
static_cast<QMainWindow *>(obs_frontend_get_main_window());
_dock = new MacroDock(GetWeakMacroByName(_name.c_str()), window,
_runButtonText, _pauseButtonText,
_unpauseButtonText, _conditionsTrueStatusText,
_dock = new MacroDock(GetWeakMacroByName(_name.c_str()), _runButtonText,
_pauseButtonText, _unpauseButtonText,
_conditionsTrueStatusText,
_conditionsFalseStatusText, _dockHighlight);
SetDockWidgetName(); // Used by OBS to restore position
// Register new dock
_dockAction = static_cast<QAction *>(obs_frontend_add_dock(_dock));
// Note that OBSBasic::OBSInit() has precedence over the visibility and
// geometry set here.
// The function calls here are only intended to attempt to restore the
// dock status when switching scene collections.
if (InitialLoadIsComplete()) {
_dock->setVisible(_dockIsVisible);
if (window->dockWidgetArea(_dock) != _dockArea) {
window->addDockWidget(_dockArea, _dock);
}
if (_dock->isFloating() != _dockIsFloating) {
_dock->setFloating(_dockIsFloating);
}
_dock->restoreGeometry(_dockGeo);
if (!obs_frontend_add_dock_by_id(_dockId.c_str(), _name.c_str(),
_dock)) {
blog(LOG_INFO, "failed to add macro dock for macro %s",
_name.c_str());
_dock->deleteLater();
_dock = nullptr;
_registerDock = false;
return;
}
_registerDock = value;
}
@@ -1095,28 +1073,22 @@ StringVariable Macro::ConditionsFalseStatusText() const
void Macro::RemoveDock()
{
if (_dock) {
_dock->close();
_dock->deleteLater();
_dockAction->deleteLater();
_dock = nullptr;
_dockAction = nullptr;
}
obs_frontend_remove_dock(_dockId.c_str());
_dock = nullptr;
}
void Macro::SetDockWidgetName() const
std::string Macro::GenerateDockId()
{
if (!_dock) {
return;
}
// Set prefix to avoid dock name conflict
_dock->setObjectName("ADVSS-" + QString::fromStdString(_name));
_dock->SetName(QString::fromStdString(_name));
#if LIBOBS_API_VER > MAKE_SEMANTIC_VERSION(30, 0, 0)
auto uuid = os_generate_uuid();
auto id = std::string("advss-macro-dock-") + std::string(uuid);
bfree(uuid);
return id;
if (!_dockAction) {
return;
}
_dockAction->setText(QString::fromStdString(_name));
#else
static std::atomic_int16_t idCounter = 0;
return std::to_string(++idCounter);
#endif
}
static void pauseCB(void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed)

View File

@@ -98,7 +98,7 @@ public:
std::shared_ptr<Macro> Parent() const;
// Saving and loading
bool Save(obs_data_t *obj) const;
bool Save(obs_data_t *obj, bool saveForCopy = false) const;
bool Load(obs_data_t *obj);
// Some macros can refer to other macros, which are not yet loaded.
// Use this function to set these references after loading is complete.
@@ -155,11 +155,10 @@ private:
bool RunActions(bool ignorePause);
bool RunElseActions(bool ignorePause);
bool DockIsVisible() const;
void SetDockWidgetName() const;
void SaveDockSettings(obs_data_t *obj) const;
void SaveDockSettings(obs_data_t *obj, bool saveForCopy) const;
void LoadDockSettings(obs_data_t *obj);
void RemoveDock();
static std::string GenerateDockId();
std::string _name = "";
bool _die = false;
@@ -217,12 +216,8 @@ private:
obs_module_text("AdvSceneSwitcher.macroDock.statusLabel.true");
StringVariable _conditionsFalseStatusText =
obs_module_text("AdvSceneSwitcher.macroDock.statusLabel.false");
bool _dockIsFloating = true;
bool _dockIsVisible = false;
Qt::DockWidgetArea _dockArea;
QByteArray _dockGeo;
MacroDock *_dock = nullptr;
QAction *_dockAction = nullptr;
std::string _dockId = GenerateDockId();
};
void LoadMacros(obs_data_t *obj);

View File

@@ -131,6 +131,7 @@ public:
RECORINDG_OR_STREAMING
};
AutoStart autoStartEvent = AutoStart::NEVER;
bool enableCooldown = false;
Duration cooldown;
bool showSystemTrayNotifications = false;
bool transitionOverrideOverride = false;

View File

@@ -0,0 +1,109 @@
#include "condition-logic.hpp"
#include "obs-module-helper.hpp"
#include "log-helper.hpp"
#include <cassert>
#include <QComboBox>
namespace advss {
const std::map<Logic::Type, const char *> Logic::localeMap = {
{Logic::Type::NONE, {"AdvSceneSwitcher.logic.none"}},
{Logic::Type::AND, {"AdvSceneSwitcher.logic.and"}},
{Logic::Type::OR, {"AdvSceneSwitcher.logic.or"}},
{Logic::Type::AND_NOT, {"AdvSceneSwitcher.logic.andNot"}},
{Logic::Type::OR_NOT, {"AdvSceneSwitcher.logic.orNot"}},
{Logic::Type::ROOT_NONE, {"AdvSceneSwitcher.logic.rootNone"}},
{Logic::Type::ROOT_NOT, {"AdvSceneSwitcher.logic.not"}},
};
bool Logic::ApplyConditionLogic(Type type, bool currentMatchResult,
bool conditionMatched, const char *context)
{
if (!context) {
context = "";
}
switch (type) {
case Type::ROOT_NONE:
return conditionMatched;
case Type::ROOT_NOT:
return !conditionMatched;
case Type::ROOT_LAST:
break;
case Type::NONE:
vblog(LOG_INFO, "skipping condition check for '%s'", context);
return currentMatchResult;
case Type::AND:
return currentMatchResult && conditionMatched;
case Type::OR:
return currentMatchResult || conditionMatched;
case Type::AND_NOT:
return currentMatchResult && !conditionMatched;
case Type::OR_NOT:
return currentMatchResult || !conditionMatched;
case Type::LAST:
default:
blog(LOG_WARNING, "ignoring invalid logic check (%s)", context);
return currentMatchResult;
}
return currentMatchResult;
}
void Logic::PopulateLogicTypeSelection(QComboBox *list, bool isRootCondition)
{
auto compare = isRootCondition
? std::function<bool(int)>{[](int typeValue) {
return typeValue < rootOffset;
}}
: std::function<bool(int)>{[](int typeValue) {
return typeValue >= rootOffset;
}};
for (const auto &[type, name] : localeMap) {
const int typeValue = static_cast<int>(type);
if (compare(typeValue)) {
list->addItem(obs_module_text(name), typeValue);
}
}
}
void Logic::Save(obs_data_t *obj, const char *name) const
{
obs_data_set_int(obj, name, static_cast<int>(_type));
}
void Logic::Load(obs_data_t *obj, const char *name)
{
_type = static_cast<Type>(obs_data_get_int(obj, name));
}
bool Logic::IsRootType() const
{
return _type < static_cast<Type>(rootOffset);
}
bool Logic::IsNegationType(Logic::Type type)
{
return type == Type::ROOT_NOT || type == Type::AND_NOT ||
type == Type::OR_NOT;
;
}
bool Logic::IsValidSelection(bool isRootCondition) const
{
if (!IsRootType() == isRootCondition) {
return false;
}
if (IsRootType() &&
(_type < Type::ROOT_NONE || _type >= Type::ROOT_LAST)) {
return false;
}
if (!IsRootType() &&
(_type <= Type::ROOT_LAST || _type >= Type::LAST)) {
return false;
}
return true;
}
} // namespace advss

View File

@@ -0,0 +1,51 @@
#pragma once
#include <map>
#include <obs-data.h>
#include <string>
class QComboBox;
namespace advss {
class Logic {
public:
static constexpr auto rootOffset = 100;
enum class Type {
ROOT_NONE = 0,
ROOT_NOT,
ROOT_LAST,
// leave some space for potential expansion
NONE = rootOffset,
AND,
OR,
AND_NOT,
OR_NOT,
LAST,
};
Logic(Type type) { _type = type; }
void Save(obs_data_t *obj, const char *name) const;
void Load(obs_data_t *obj, const char *name);
Type GetType() const { return _type; }
void SetType(const Type &type) { _type = type; }
bool IsRootType() const;
static bool IsNegationType(Logic::Type);
bool IsValidSelection(bool isRootCondition) const;
static bool ApplyConditionLogic(Type, bool currentMatchResult,
bool conditionMatched,
const char *context);
static void PopulateLogicTypeSelection(QComboBox *list,
bool isRootCondition);
private:
Type _type = Type::NONE;
static const std::map<Type, const char *> localeMap;
};
} // namespace advss

View File

@@ -77,13 +77,12 @@ bool CurlHelper::LoadLib()
{
_lib = new QLibrary(curl_library_name, nullptr);
if (Resolve()) {
blog(LOG_INFO, "[adv-ss] found curl library");
blog(LOG_INFO, "found curl library");
return true;
} else {
delete _lib;
_lib = nullptr;
blog(LOG_WARNING,
"[adv-ss] couldn't find the curl library in PATH");
blog(LOG_WARNING, "couldn't find the curl library in PATH");
}
QStringList locations;
locations << QDir::currentPath();
@@ -94,14 +93,13 @@ bool CurlHelper::LoadLib()
locations << "/usr/local/opt/curl/lib";
#endif
for (QString path : locations) {
blog(LOG_INFO, "[adv-ss] trying '%s'",
path.toUtf8().constData());
blog(LOG_INFO, "trying '%s'", path.toUtf8().constData());
QFileInfo libPath(
QDir(path).absoluteFilePath(curl_library_name));
if (libPath.exists() && libPath.isFile()) {
QString libFilePath = libPath.absoluteFilePath();
blog(LOG_INFO, "[adv-ss] found curl library at '%s'",
blog(LOG_INFO, "found curl library at '%s'",
libFilePath.toUtf8().constData());
_lib = new QLibrary(libFilePath, nullptr);
@@ -113,7 +111,7 @@ bool CurlHelper::LoadLib()
}
}
}
blog(LOG_WARNING, "[adv-ss] can't find the curl library");
blog(LOG_WARNING, "can't find the curl library");
return false;
}
@@ -128,11 +126,11 @@ bool CurlHelper::Resolve()
if (_init && _setopt && _slistAppend && _perform && _cleanup &&
_error) {
blog(LOG_INFO, "[adv-ss] curl loaded successfully");
blog(LOG_INFO, "curl loaded successfully");
return true;
}
blog(LOG_INFO, "[adv-ss] curl symbols not resolved");
blog(LOG_INFO, "curl symbols not resolved");
return false;
}

View File

@@ -0,0 +1,110 @@
#include "duration-modifier.hpp"
namespace advss {
void DurationModifier::Save(obs_data_t *obj, const char *name,
const char *duration) const
{
auto data = obs_data_create();
obs_data_set_int(data, name, static_cast<int>(_type));
_duration.Save(data, duration);
obs_data_set_obj(obj, "durationModifier", data);
obs_data_release(data);
}
void DurationModifier::Load(obs_data_t *obj, const char *name,
const char *duration)
{
obs_data_t *data = nullptr;
if (obs_data_has_user_value(obj, "durationModifier")) {
data = obs_data_get_obj(obj, "durationModifier");
} else {
// For backwards compatibility
obs_data_addref(obj);
data = obj;
}
// For backwards compatibility check if duration value exist without
// time constraint condition - if so assume DurationCondition::MORE
if (!obs_data_has_user_value(data, name) &&
obs_data_has_user_value(data, duration)) {
obs_data_set_int(data, name, static_cast<int>(Type::MORE));
}
_type = static_cast<Type>(obs_data_get_int(data, name));
_duration.Load(data, duration);
// TODO: remove this fallback
if (obs_data_has_user_value(data, "displayUnit")) {
_duration.SetUnit(static_cast<Duration::Unit>(
obs_data_get_int(data, "displayUnit")));
}
obs_data_release(data);
}
void DurationModifier::SetTimeRemaining(double seconds)
{
_duration.SetTimeRemaining(seconds);
}
bool DurationModifier::DurationReached()
{
switch (_type) {
case DurationModifier::Type::NONE:
return true;
case DurationModifier::Type::MORE:
return _duration.DurationReached();
case DurationModifier::Type::EQUAL:
if (_duration.DurationReached() && !_durationWasReached) {
_durationWasReached = true;
return true;
}
break;
case DurationModifier::Type::LESS:
return !_duration.DurationReached();
case DurationModifier::Type::WITHIN:
if (_duration.IsReset()) {
return false;
}
return !_duration.DurationReached();
default:
break;
}
return false;
}
void DurationModifier::ResetDuration()
{
_durationWasReached = false;
_duration.Reset();
}
bool DurationModifier::CheckConditionWithDurationModifier(bool conditionValue)
{
if (_type != DurationModifier::Type::WITHIN && !conditionValue) {
ResetDuration();
}
if (_type == DurationModifier::Type::WITHIN && conditionValue) {
ResetDuration();
}
switch (_type) {
case DurationModifier::Type::NONE:
case DurationModifier::Type::MORE:
case DurationModifier::Type::EQUAL:
case DurationModifier::Type::LESS:
return conditionValue && DurationReached();
case DurationModifier::Type::WITHIN:
if (conditionValue) {
SetTimeRemaining(_duration.Seconds());
}
return conditionValue || DurationReached();
default:
break;
}
return conditionValue;
}
} // namespace advss

View File

@@ -0,0 +1,31 @@
#pragma once
#include "duration.hpp"
namespace advss {
class DurationModifier {
public:
enum class Type { NONE, MORE, EQUAL, LESS, WITHIN };
void Save(obs_data_t *obj, const char *name = "time_constraint",
const char *duration = "seconds") const;
void Load(obs_data_t *obj, const char *name = "time_constraint",
const char *duration = "seconds");
void SetModifier(Type type) { _type = type; }
void SetDuration(const Duration &duration) { _duration = duration; }
Type GetType() const { return _type; }
Duration GetDuration() const { return _duration; }
void ResetDuration();
bool CheckConditionWithDurationModifier(bool conditionValue);
private:
void SetTimeRemaining(double seconds);
bool DurationReached();
Type _type = Type::NONE;
Duration _duration;
bool _durationWasReached = false;
};
} // namespace advss

View File

@@ -101,7 +101,7 @@ double Duration::TimeRemaining() const
void Duration::SetTimeRemaining(double remaining)
{
long long msPassed = (Seconds() - remaining) * 1000;
long long msPassed = (long long)((Seconds() - remaining) * 1000);
_startTime = std::chrono::high_resolution_clock::now() -
std::chrono::milliseconds(msPassed);
}

View File

@@ -84,8 +84,7 @@ void FilterComboBox::focusOutEvent(QFocusEvent *event)
// Reset on invalid selection
if (findText(currentText()) == -1) {
setCurrentIndex(-1);
emit currentIndexChanged(-1);
emit currentTextChanged("");
Emit(-1, "");
}
QComboBox::focusOutEvent(event);
@@ -121,8 +120,7 @@ void FilterComboBox::CompleterHighlightChanged(const QModelIndex &index)
if (idx == -1) {
return;
}
emit currentIndexChanged(idx);
emit currentTextChanged(text);
Emit(idx, text);
}
void FilterComboBox::TextChanged(const QString &text)
@@ -134,8 +132,18 @@ void FilterComboBox::TextChanged(const QString &text)
if (idx == -1) {
return;
}
emit currentIndexChanged(idx);
emit currentTextChanged(text);
}
void FilterComboBox::Emit(int index, const QString &text)
{
if (_lastEmittedIndex != index) {
_lastEmittedIndex = index;
emit currentIndexChanged(index);
}
if (_lastEmittedText != text) {
_lastEmittedText = text;
emit currentTextChanged(text);
}
}
} // namespace advss

View File

@@ -25,6 +25,11 @@ private slots:
void TextChanged(const QString &);
private:
void Emit(int index, const QString &text);
int _lastEmittedIndex = -1;
QString _lastEmittedText;
int _lastCompleterHighlightRow = -1;
static bool _filteringEnabled;
};

View File

@@ -1,8 +1,16 @@
#pragma once
#ifndef UNIT_TEST
#include <util/base.h>
#endif
namespace advss {
#ifdef UNIT_TEST
#define blog(level, msg, ...)
#define vblog(level, msg, ...)
#define ablog(level, msg, ...)
#else
// Print log with "[adv-ss] " prefix
#define blog(level, msg, ...) blog(level, "[adv-ss] " msg, ##__VA_ARGS__)
// Print log with "[adv-ss] " if log level is set to "verbose"
@@ -20,6 +28,8 @@ namespace advss {
} \
} while (0)
#endif
// Returns true if log level is set to "verbose"
EXPORT bool VerboseLoggingEnabled();
// Returns true if log level is set to "action" or "verbose"

View File

@@ -1,10 +0,0 @@
#pragma once
#include <QDockWidget>
// QDockWidget wrapper enable applying "OBSDock" stylesheet
class OBSDock : public QDockWidget {
Q_OBJECT
public:
inline OBSDock(QWidget *parent = nullptr) : QDockWidget(parent) {}
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,264 @@
#pragma once
#include <obs-data.h>
#include <obs.hpp>
#include <qtimer.h>
#include <QPointer>
#include <vector>
#include <memory>
#include <QScrollArea>
#include <QPlainTextEdit>
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(30, 0, 0)
class QFormLayout;
class QLabel;
class QResizeEvent;
typedef obs_properties_t *(*PropertiesReloadCallback)(void *obj);
typedef void (*PropertiesUpdateCallback)(void *obj, obs_data_t *old_settings,
obs_data_t *new_settings);
typedef void (*PropertiesVisualUpdateCb)(void *obj, obs_data_t *settings);
namespace advss {
class OBSPropertiesView;
/* ------------------------------------------------------------------------- */
class VScrollArea : public QScrollArea {
Q_OBJECT
public:
VScrollArea(QWidget *parent = nullptr);
protected:
virtual void resizeEvent(QResizeEvent *event) override;
};
/* ------------------------------------------------------------------------- */
class SpinBoxIgnoreScroll : public QSpinBox {
Q_OBJECT
public:
SpinBoxIgnoreScroll(QWidget *parent = nullptr);
protected:
virtual void wheelEvent(QWheelEvent *event) override;
};
/* ------------------------------------------------------------------------- */
class OBSPlainTextEdit : public QPlainTextEdit {
Q_OBJECT
public:
explicit OBSPlainTextEdit(QWidget *parent = nullptr,
bool monospace = true);
};
/* ------------------------------------------------------------------------- */
class WidgetInfo : public QObject {
Q_OBJECT
friend class OBSPropertiesView;
private:
OBSPropertiesView *view;
obs_property_t *property;
QWidget *widget;
QPointer<QTimer> update_timer;
bool recently_updated = false;
OBSData old_settings_cache;
void BoolChanged(const char *setting);
void IntChanged(const char *setting);
void FloatChanged(const char *setting);
void TextChanged(const char *setting);
bool PathChanged(const char *setting);
void ListChanged(const char *setting);
bool ColorChangedInternal(const char *setting, bool supportAlpha);
bool ColorChanged(const char *setting);
bool ColorAlphaChanged(const char *setting);
bool FontChanged(const char *setting);
void GroupChanged(const char *setting);
void EditableListChanged();
void ButtonClicked();
void TogglePasswordText(bool checked);
public:
inline WidgetInfo(OBSPropertiesView *view_, obs_property_t *prop,
QWidget *widget_)
: view(view_),
property(prop),
widget(widget_)
{
}
~WidgetInfo()
{
if (update_timer) {
update_timer->stop();
QMetaObject::invokeMethod(update_timer, "timeout");
update_timer->deleteLater();
}
}
public slots:
void ControlChanged();
/* editable list */
void EditListAdd();
void EditListAddText();
void EditListAddFiles();
void EditListAddDir();
void EditListRemove();
void EditListEdit();
void EditListUp();
void EditListDown();
};
/* ------------------------------------------------------------------------- */
class OBSPropertiesView : public VScrollArea {
Q_OBJECT
friend class WidgetInfo;
using properties_delete_t = decltype(&obs_properties_destroy);
using properties_t =
std::unique_ptr<obs_properties_t, properties_delete_t>;
private:
QWidget *widget = nullptr;
properties_t properties;
OBSData settings;
OBSWeakObjectAutoRelease weakObj;
void *rawObj = nullptr;
std::string type;
PropertiesReloadCallback reloadCallback;
PropertiesUpdateCallback callback = nullptr;
PropertiesVisualUpdateCb visUpdateCb = nullptr;
int minSize;
std::vector<std::unique_ptr<WidgetInfo>> children;
std::string lastFocused;
QWidget *lastWidget = nullptr;
bool deferUpdate;
bool enableDefer = true;
template<typename Sender, typename SenderParent, typename... Args>
QWidget *NewWidget(obs_property_t *prop, Sender *widget,
void (SenderParent::*signal)(Args...));
QWidget *AddCheckbox(obs_property_t *prop);
QWidget *AddText(obs_property_t *prop, QFormLayout *layout,
QLabel *&label);
void AddPath(obs_property_t *prop, QFormLayout *layout, QLabel **label);
void AddInt(obs_property_t *prop, QFormLayout *layout, QLabel **label);
void AddFloat(obs_property_t *prop, QFormLayout *layout,
QLabel **label);
QWidget *AddList(obs_property_t *prop, bool &warning);
void AddEditableList(obs_property_t *prop, QFormLayout *layout,
QLabel *&label);
QWidget *AddButton(obs_property_t *prop);
void AddColorInternal(obs_property_t *prop, QFormLayout *layout,
QLabel *&label, bool supportAlpha);
void AddColor(obs_property_t *prop, QFormLayout *layout,
QLabel *&label);
void AddColorAlpha(obs_property_t *prop, QFormLayout *layout,
QLabel *&label);
void AddFont(obs_property_t *prop, QFormLayout *layout, QLabel *&label);
void AddFrameRate(obs_property_t *prop, bool &warning,
QFormLayout *layout, QLabel *&label);
void AddGroup(obs_property_t *prop, QFormLayout *layout);
void AddProperty(obs_property_t *property, QFormLayout *layout);
void resizeEvent(QResizeEvent *event) override;
void GetScrollPos(int &h, int &v, int &hend, int &vend);
void SetScrollPos(int h, int v, int old_hend, int old_vend);
private slots:
void RefreshProperties();
public slots:
void ReloadProperties();
void SignalChanged();
signals:
void PropertiesResized();
void Changed();
void PropertiesRefreshed();
public:
OBSPropertiesView(OBSData settings, obs_object_t *obj,
PropertiesReloadCallback reloadCallback,
PropertiesUpdateCallback callback,
PropertiesVisualUpdateCb cb = nullptr,
int minSize = 0);
OBSPropertiesView(OBSData settings, void *obj,
PropertiesReloadCallback reloadCallback,
PropertiesUpdateCallback callback,
PropertiesVisualUpdateCb cb = nullptr,
int minSize = 0);
OBSPropertiesView(OBSData settings, const char *type,
PropertiesReloadCallback reloadCallback,
int minSize = 0);
#define obj_constructor(type) \
inline OBSPropertiesView(OBSData settings, obs_##type##_t *type, \
PropertiesReloadCallback reloadCallback, \
PropertiesUpdateCallback callback, \
PropertiesVisualUpdateCb cb = nullptr, \
int minSize = 0) \
: OBSPropertiesView(settings, (obs_object_t *)type, \
reloadCallback, callback, cb, minSize) \
{ \
}
obj_constructor(source);
obj_constructor(output);
obj_constructor(encoder);
obj_constructor(service);
#undef obj_constructor
inline obs_data_t *GetSettings() const { return settings; }
inline void UpdateSettings()
{
if (callback)
callback(OBSGetStrongRef(weakObj), nullptr, settings);
else if (visUpdateCb)
visUpdateCb(OBSGetStrongRef(weakObj), settings);
}
inline bool DeferUpdate() const { return deferUpdate; }
inline void SetDeferrable(bool deferrable) { enableDefer = deferrable; }
inline OBSObject GetObject() const { return OBSGetStrongRef(weakObj); }
#define Def_IsObject(type) \
inline bool IsObject(obs_##type##_t *type) const \
{ \
OBSObject obj = OBSGetStrongRef(weakObj); \
return obj.Get() == (obs_object_t *)type; \
}
/* clang-format off */
Def_IsObject(source)
Def_IsObject(output)
Def_IsObject(encoder)
Def_IsObject(service)
/* clang-format on */
#undef Def_IsObject
};
} // namespace advss
#endif

View File

@@ -0,0 +1,63 @@
#pragma once
#include <QComboBox>
#include <QLabel>
#include <QSpinBox>
#include <QStackedWidget>
#include <QWidget>
#include <obs.h>
#include <media-io/frame-rate.h>
#include <vector>
#ifdef _MSC_VER
#pragma warning(disable : 4505)
#endif
static bool operator!=(const media_frames_per_second &a,
const media_frames_per_second &b)
{
return a.numerator != b.numerator || a.denominator != b.denominator;
}
static bool operator==(const media_frames_per_second &a,
const media_frames_per_second &b)
{
return !(a != b);
}
using frame_rate_range_t =
std::pair<media_frames_per_second, media_frames_per_second>;
using frame_rate_ranges_t = std::vector<frame_rate_range_t>;
class OBSFrameRatePropertyWidget : public QWidget {
Q_OBJECT
public:
frame_rate_ranges_t fps_ranges;
QComboBox *modeSelect = nullptr;
QStackedWidget *modeDisplay = nullptr;
QWidget *labels = nullptr;
QLabel *currentFPS = nullptr;
QLabel *timePerFrame = nullptr;
QLabel *minLabel = nullptr;
QLabel *maxLabel = nullptr;
QComboBox *simpleFPS = nullptr;
QComboBox *fpsRange = nullptr;
QSpinBox *numEdit = nullptr;
QSpinBox *denEdit = nullptr;
bool updating = false;
const char *name = nullptr;
obs_data_t *settings = nullptr;
QLabel *warningLabel = nullptr;
OBSFrameRatePropertyWidget() = default;
};

View File

@@ -133,7 +133,9 @@ bool Section::eventFilter(QObject *obj, QEvent *event)
void Section::SetupAnimations()
{
delete _toggleAnimation;
if (_toggleAnimation) {
_toggleAnimation->deleteLater();
}
_toggleAnimation = new QParallelAnimationGroup(this);
_toggleAnimation->addAnimation(

View File

@@ -1,4 +1,5 @@
#include "status-control.hpp"
#include "log-helper.hpp"
#include "obs-module-helper.hpp"
#include "path-helpers.hpp"
#include "plugin-state-helpers.hpp"
@@ -11,10 +12,30 @@
#include <QPalette>
#include <QToolBar>
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(30, 0, 0)
#include <QDockWidget>
static bool obs_frontend_add_dock_by_id(const char *id, const char *title,
QWidget *widget)
{
widget->setObjectName(id);
auto dock = new QDockWidget();
dock->setWindowTitle(title);
dock->setWidget(widget);
dock->setFloating(true);
dock->setVisible(false);
dock->setFeatures(QDockWidget::DockWidgetClosable |
QDockWidget::DockWidgetMovable |
QDockWidget::DockWidgetFloatable);
return !!obs_frontend_add_dock(dock);
}
#endif
namespace advss {
void OpenSettingsWindow();
StatusDock *dock = nullptr;
static QString colorToString(const QColor &color)
{
@@ -133,6 +154,7 @@ void StatusControl::SetStarted()
_status->setText(obs_module_text("AdvSceneSwitcher.status.active"));
if (_pulse) {
_pulse->deleteLater();
_pulse = nullptr;
}
SetStatusStyleSheet(false);
_setToStopped = false;
@@ -168,16 +190,12 @@ void StatusControl::SetStatusStyleSheet(bool stopped) const
_status->setStyleSheet(style);
}
StatusDock::StatusDock(QWidget *parent) : OBSDock(parent)
StatusDockWidget::StatusDockWidget(QWidget *parent) : QFrame(parent)
{
setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
setFeatures(DockWidgetClosable | DockWidgetMovable |
DockWidgetFloatable);
// Setting a fixed object name is crucial for OBS to be able to restore
// the docks position, if the dock is not floating
setObjectName("Adv-ss-dock");
setFrameShape(QFrame::StyledPanel);
setFrameShadow(QFrame::Sunken);
QAction *action = new QAction;
auto action = new QAction;
action->setProperty("themeID", QVariant(QString::fromUtf8("cogsIcon")));
action->connect(action, &QAction::triggered, OpenSettingsWindow);
const auto path = QString::fromStdString(GetDataFilePath(
@@ -195,29 +213,21 @@ StatusDock::StatusDock(QWidget *parent) : OBSDock(parent)
statusControl->ButtonLayout()->setStretchFactor(statusControl->Button(),
100);
QVBoxLayout *layout = new QVBoxLayout;
auto layout = new QVBoxLayout;
layout->addWidget(statusControl);
layout->setContentsMargins(0, 0, 0, 0);
// QFrame wrapper is necessary to avoid dock being partially
// transparent
QFrame *wrapper = new QFrame;
wrapper->setFrameShape(QFrame::StyledPanel);
wrapper->setFrameShadow(QFrame::Sunken);
wrapper->setLayout(layout);
setWidget(wrapper);
setFloating(true);
hide();
setLayout(layout);
}
void SetupDock()
{
dock = new StatusDock(
static_cast<QMainWindow *>(obs_frontend_get_main_window()));
// Added for cosmetic reasons to avoid brief flash of dock window on startup
dock->setVisible(false);
obs_frontend_add_dock(dock);
auto dock = new StatusDockWidget();
if (!obs_frontend_add_dock_by_id(
"advss-status-dock",
obs_module_text("AdvSceneSwitcher.windowTitle"), dock)) {
blog(LOG_INFO, "failed to register status dock!");
dock->deleteLater();
}
}
} // namespace advss

View File

@@ -1,6 +1,4 @@
#pragma once
#include "obs-dock.hpp"
#include <QPushButton>
#include <QLabel>
#include <QTimer>
@@ -38,11 +36,11 @@ private:
bool _setToStopped = true;
};
class StatusDock : public OBSDock {
class StatusDockWidget : public QFrame {
Q_OBJECT
public:
StatusDock(QWidget *parent = 0);
StatusDockWidget(QWidget *parent = 0);
};
void SetupDock();

View File

@@ -160,4 +160,12 @@ void QeueUITask(void (*task)(void *param), void *param)
obs_queue_task(OBS_TASK_UI, task, param, false);
}
bool IsCursorInWidgetArea(QWidget *widget)
{
const auto cursorPos = QCursor::pos();
const auto widgetPos = widget->mapFromGlobal(cursorPos);
const auto widgetRect = widget->rect();
return widgetRect.contains(widgetPos);
}
} // namespace advss

View File

@@ -33,4 +33,6 @@ EXPORT std::string GetThemeTypeName();
void QeueUITask(void (*task)(void *param), void *param);
bool IsCursorInWidgetArea(QWidget *widget);
} // namespace advss

View File

@@ -76,7 +76,7 @@ std::string ToString(double value)
}
void listAddClicked(QListWidget *list, QWidget *newWidget,
QPushButton *addButton, QObject *addHighlight)
QObject *addHighlight)
{
if (!list || !newWidget) {
return;

View File

@@ -23,7 +23,6 @@ std::string ToString(double value);
/* Legacy helpers */
void listAddClicked(QListWidget *list, QWidget *newWidget,
QPushButton *addButton = nullptr,
QObject *addHighlight = nullptr);
bool listMoveUp(QListWidget *list);
bool listMoveDown(QListWidget *list);

View File

@@ -54,6 +54,7 @@ void Variable::Save(obs_data_t *obj) const
std::string Variable::Value(bool updateLastUsed) const
{
std::lock_guard<std::mutex> lock(_mutex);
if (updateLastUsed) {
UpdateLastUsed();
}
@@ -73,6 +74,7 @@ std::optional<int> Variable::IntValue() const
void Variable::SetValue(const std::string &value)
{
std::lock_guard<std::mutex> lock(_mutex);
_previousValue = _value;
_value = value;
@@ -298,8 +300,7 @@ VariableSelectionDialog::VariableSelectionDialog(QWidget *parent)
QWidget *GetSettingsWindow();
bool VariableSelectionDialog::AskForVariable(QWidget *parent,
std::string &varName)
bool VariableSelectionDialog::AskForVariable(std::string &varName)
{
VariableSelectionDialog dialog(GetSettingsWindow());
dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));

View File

@@ -3,10 +3,11 @@
#include "item-selection-helpers.hpp"
#include "resizing-text-edit.hpp"
#include <string>
#include <optional>
#include <QStringList>
#include <mutex>
#include <obs-data.h>
#include <optional>
#include <string>
#include <QStringList>
namespace advss {
@@ -52,6 +53,7 @@ private:
int _valueChangeCount = 0;
mutable std::chrono::high_resolution_clock::time_point _lastUsed;
mutable std::chrono::high_resolution_clock::time_point _lastChanged;
mutable std::mutex _mutex;
friend VariableSelection;
friend VariableSettingsDialog;
@@ -87,7 +89,7 @@ class VariableSelectionDialog : public QDialog {
public:
VariableSelectionDialog(QWidget *parent);
static bool AskForVariable(QWidget *parent, std::string &variableName);
static bool AskForVariable(std::string &variableName);
private:
VariableSelection *_variableSelection;

View File

@@ -172,6 +172,8 @@ target_sources(
utils/striped-frame.hpp
utils/text-helpers.cpp
utils/text-helpers.hpp
utils/transform-setting.cpp
utils/transform-setting.hpp
utils/transition-selection.cpp
utils/transition-selection.hpp
utils/websocket-helpers.cpp

View File

@@ -18,6 +18,8 @@ bool MacroActionSceneTransform::_registered = MacroActionFactory::Register(
const static std::map<MacroActionSceneTransform::Action, std::string> actions = {
{MacroActionSceneTransform::Action::MANUAL_TRANSFORM,
"AdvSceneSwitcher.action.sceneTransform.type.manual"},
{MacroActionSceneTransform::Action::SINGLE_TRANSFORM_SETTING,
"AdvSceneSwitcher.action.sceneTransform.type.setSingleSetting"},
{MacroActionSceneTransform::Action::RESET,
"AdvSceneSwitcher.action.sceneTransform.type.reset"},
{MacroActionSceneTransform::Action::ROTATE,
@@ -200,49 +202,52 @@ void MacroActionSceneTransform::Transform(obs_scene_item *item)
}
switch (_action) {
case MacroActionSceneTransform::Action::RESET:
case Action::RESET:
obs_sceneitem_defer_update_begin(item);
reset(item);
obs_sceneitem_defer_update_end(item);
break;
case MacroActionSceneTransform::Action::ROTATE:
case Action::ROTATE:
rotate(item, _rotation);
break;
case MacroActionSceneTransform::Action::FLIP_HORIZONTAL: {
case Action::FLIP_HORIZONTAL: {
vec2 scale;
vec2_set(&scale, -1.0f, 1.0f);
flip(item, scale);
break;
}
case MacroActionSceneTransform::Action::FLIP_VERTICAL: {
case Action::FLIP_VERTICAL: {
vec2 scale;
vec2_set(&scale, 1.0f, -1.0f);
flip(item, scale);
break;
}
case MacroActionSceneTransform::Action::FIT_TO_SCREEN:
case Action::FIT_TO_SCREEN:
centerAlign(item, OBS_BOUNDS_SCALE_INNER);
break;
case MacroActionSceneTransform::Action::STRETCH_TO_SCREEN:
case Action::STRETCH_TO_SCREEN:
centerAlign(item, OBS_BOUNDS_STRETCH);
break;
case MacroActionSceneTransform::Action::CENTER_TO_SCREEN:
case Action::CENTER_TO_SCREEN:
center(item, CenterType::SCREEN);
break;
case MacroActionSceneTransform::Action::CENTER_VERTICALLY:
case Action::CENTER_VERTICALLY:
center(item, CenterType::VERTICAL);
break;
case MacroActionSceneTransform::Action::CENTER_HORIZONTALLY:
case Action::CENTER_HORIZONTALLY:
center(item, CenterType::HORIZONTAL);
break;
case MacroActionSceneTransform::Action::MANUAL_TRANSFORM:
ApplySettings(_settings); // Resolves variables
case Action::MANUAL_TRANSFORM:
ApplySettings(_transformString); // Resolves variables
obs_sceneitem_defer_update_begin(item);
obs_sceneitem_set_info(item, &_info);
obs_sceneitem_set_crop(item, &_crop);
obs_sceneitem_defer_update_end(item);
break;
case Action::SINGLE_TRANSFORM_SETTING:
SetTransformSetting(item, _settingSelection,
_singleSettingsValue);
}
}
@@ -270,7 +275,9 @@ bool MacroActionSceneTransform::Save(obs_data_t *obj) const
_scene.Save(obj);
_source.Save(obj);
_rotation.Save(obj, "rotation");
_settings.Save(obj, "settings");
_transformString.Save(obj, "settings");
_singleSettingsValue.Save(obj, "singleSettingsValue");
_settingSelection.Save(obj);
return true;
}
@@ -293,13 +300,15 @@ bool MacroActionSceneTransform::Load(obs_data_t *obj)
_scene.Load(obj);
_source.Load(obj);
_rotation.Load(obj, "rotation");
_settings.Load(obj, "settings");
_transformString.Load(obj, "settings");
_singleSettingsValue.Load(obj, "singleSettingsValue");
_settingSelection.Load(obj);
// Convert old data format
// TODO: Remove in future version
if (!obs_data_has_user_value(obj, "settings")) {
LoadTransformState(obj, _info, _crop);
_settings = ConvertSettings();
_transformString = ConvertSettings();
}
return true;
}
@@ -326,8 +335,9 @@ void MacroActionSceneTransform::ResolveVariablesToFixedValues()
{
_scene.ResolveVariables();
_source.ResolveVariables();
_settings.ResolveVariables();
_transformString.ResolveVariables();
_rotation.ResolveVariables();
_singleSettingsValue.ResolveVariables();
}
std::string MacroActionSceneTransform::ConvertSettings()
@@ -381,9 +391,13 @@ MacroActionSceneTransformEdit::MacroActionSceneTransformEdit(
_sources(new SceneItemSelectionWidget(parent)),
_action(new QComboBox()),
_rotation(new VariableDoubleSpinBox()),
_getSettings(new QPushButton(obs_module_text(
_getTransform(new QPushButton(obs_module_text(
"AdvSceneSwitcher.action.sceneTransform.getTransform"))),
_settings(new VariableTextEdit(this)),
_getCurrentValue(new QPushButton(obs_module_text(
"AdvSceneSwitcher.action.sceneTransform.getCurrentValue"))),
_transformString(new VariableTextEdit(this)),
_settingSelection(new TransformSettingSelection(this)),
_singleSettingValue(new VariableLineEdit(this)),
_buttonLayout(new QHBoxLayout())
{
_rotation->setMinimum(-360);
@@ -405,28 +419,40 @@ MacroActionSceneTransformEdit::MacroActionSceneTransformEdit(
_rotation,
SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
this, SLOT(RotationChanged(const NumberVariable<double> &)));
QWidget::connect(_getSettings, SIGNAL(clicked()), this,
QWidget::connect(_getTransform, SIGNAL(clicked()), this,
SLOT(GetSettingsClicked()));
QWidget::connect(_settings, SIGNAL(textChanged()), this,
SLOT(SettingsChanged()));
QWidget::connect(_getCurrentValue, SIGNAL(clicked()), this,
SLOT(GetCurrentValueClicked()));
QWidget::connect(_transformString, SIGNAL(textChanged()), this,
SLOT(TransformStringChanged()));
QWidget::connect(_singleSettingValue, SIGNAL(editingFinished()), this,
SLOT(SettingValueChanged()));
QWidget::connect(
_settingSelection,
SIGNAL(SelectionChanged(const TransformSetting &)), this,
SLOT(SettingSelectionChanged(const TransformSetting &)));
QHBoxLayout *entryLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes}, {"{{rotation}}", _rotation},
{"{{sources}}", _sources}, {"{{action}}", _action},
{"{{settings}}", _settings}, {"{{getSettings}}", _getSettings},
{"{{scenes}}", _scenes},
{"{{rotation}}", _rotation},
{"{{sources}}", _sources},
{"{{action}}", _action},
{"{{settingSelection}}", _settingSelection},
{"{{singleSettingValue}}", _singleSettingValue},
};
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.action.sceneTransform.entry"),
entryLayout, widgetPlaceholders);
_buttonLayout->addWidget(_getSettings);
_buttonLayout->addWidget(_getTransform);
_buttonLayout->addWidget(_getCurrentValue);
_buttonLayout->addStretch();
QVBoxLayout *mainLayout = new QVBoxLayout;
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLayout);
mainLayout->addWidget(_settings);
mainLayout->addWidget(_transformString);
mainLayout->addLayout(_buttonLayout);
setLayout(mainLayout);
@@ -446,28 +472,31 @@ void MacroActionSceneTransformEdit::UpdateEntryData()
_action->setCurrentIndex(
_action->findData(static_cast<int>(_entryData->_action)));
_rotation->SetValue(_entryData->_rotation);
_settings->setPlainText(_entryData->_settings);
_transformString->setPlainText(_entryData->_transformString);
UpdateSettingSelection();
_settingSelection->SetSetting(_entryData->_settingSelection);
_singleSettingValue->setText(_entryData->_singleSettingsValue);
SetWidgetVisibility();
}
void MacroActionSceneTransformEdit::SceneChanged(const SceneSelection &s)
{
if (_loading || !_entryData) {
return;
{
GUARD_LOADING_AND_LOCK();
_entryData->_scene = s;
}
auto lock = LockContext();
_entryData->_scene = s;
UpdateSettingSelection();
}
void MacroActionSceneTransformEdit::SourceChanged(const SceneItemSelection &item)
{
if (_loading || !_entryData) {
return;
{
GUARD_LOADING_AND_LOCK();
_entryData->_source = item;
}
auto lock = LockContext();
_entryData->_source = item;
UpdateSettingSelection();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
adjustSize();
@@ -476,11 +505,7 @@ void MacroActionSceneTransformEdit::SourceChanged(const SceneItemSelection &item
void MacroActionSceneTransformEdit::ActionChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_action = static_cast<MacroActionSceneTransform::Action>(
_action->itemData(idx).toInt());
SetWidgetVisibility();
@@ -488,11 +513,7 @@ void MacroActionSceneTransformEdit::ActionChanged(int idx)
void MacroActionSceneTransformEdit::RotationChanged(const DoubleVariable &value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_rotation = value;
}
@@ -508,17 +529,52 @@ void MacroActionSceneTransformEdit::GetSettingsClicked()
}
auto settings = GetSceneItemTransform(items[0]);
_settings->setPlainText(FormatJsonString(settings));
_transformString->setPlainText(FormatJsonString(settings));
}
void MacroActionSceneTransformEdit::SettingsChanged()
void MacroActionSceneTransformEdit::GetCurrentValueClicked()
{
if (_loading || !_entryData) {
if (_loading || !_entryData || !_entryData->_scene.GetScene(false)) {
return;
}
auto lock = LockContext();
_entryData->_settings = _settings->toPlainText().toStdString();
auto items = _entryData->_source.GetSceneItems(_entryData->_scene);
if (items.empty()) {
return;
}
auto value = GetTransformSettingValue(items.at(0),
_entryData->_settingSelection);
if (!value) {
return;
}
_singleSettingValue->setText(QString::fromStdString(*value));
SettingValueChanged();
}
void MacroActionSceneTransformEdit::TransformStringChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_transformString =
_transformString->toPlainText().toStdString();
adjustSize();
updateGeometry();
}
void MacroActionSceneTransformEdit::SettingSelectionChanged(
const TransformSetting &setting)
{
GUARD_LOADING_AND_LOCK();
_entryData->_settingSelection = setting;
}
void MacroActionSceneTransformEdit::SettingValueChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_singleSettingsValue =
_singleSettingValue->text().toStdString();
adjustSize();
updateGeometry();
@@ -530,13 +586,39 @@ void MacroActionSceneTransformEdit::SetWidgetVisibility()
_buttonLayout,
_entryData->_action ==
MacroActionSceneTransform::Action::MANUAL_TRANSFORM);
_settings->setVisible(
_transformString->setVisible(
_entryData->_action ==
MacroActionSceneTransform::Action::MANUAL_TRANSFORM);
_rotation->setVisible(_entryData->_action ==
MacroActionSceneTransform::Action::ROTATE);
_getTransform->setVisible(
_entryData->_action ==
MacroActionSceneTransform::Action::MANUAL_TRANSFORM);
_getCurrentValue->setVisible(
_entryData->_action ==
MacroActionSceneTransform::Action::SINGLE_TRANSFORM_SETTING);
_settingSelection->setVisible(
_entryData->_action ==
MacroActionSceneTransform::Action::SINGLE_TRANSFORM_SETTING);
_singleSettingValue->setVisible(
_entryData->_action ==
MacroActionSceneTransform::Action::SINGLE_TRANSFORM_SETTING);
adjustSize();
updateGeometry();
}
void MacroActionSceneTransformEdit::UpdateSettingSelection() const
{
if (!_entryData) {
_settingSelection->SetSource(nullptr);
return;
}
auto items = _entryData->_source.GetSceneItems(_entryData->_scene);
if (items.empty()) {
_settingSelection->SetSource(nullptr);
return;
}
_settingSelection->SetSource(items.at(0));
}
} // namespace advss

View File

@@ -2,6 +2,7 @@
#include "macro-action-edit.hpp"
#include "scene-selection.hpp"
#include "scene-item-selection.hpp"
#include "transform-setting.hpp"
#include "variable-text-edit.hpp"
#include "variable-spinbox.hpp"
@@ -33,13 +34,16 @@ public:
CENTER_VERTICALLY,
CENTER_HORIZONTALLY,
MANUAL_TRANSFORM = 100,
SINGLE_TRANSFORM_SETTING,
};
Action _action = Action::RESET;
SceneSelection _scene;
SceneItemSelection _source;
StringVariable _settings = "";
StringVariable _transformString = "";
DoubleVariable _rotation = 90.0;
StringVariable _singleSettingsValue = "";
TransformSetting _settingSelection;
private:
void Transform(obs_scene_item *);
@@ -76,19 +80,26 @@ private slots:
void ActionChanged(int);
void RotationChanged(const NumberVariable<double> &value);
void GetSettingsClicked();
void SettingsChanged();
void GetCurrentValueClicked();
void TransformStringChanged();
void SettingSelectionChanged(const TransformSetting &);
void SettingValueChanged();
signals:
void HeaderInfoChanged(const QString &);
private:
void SetWidgetVisibility();
void UpdateSettingSelection() const;
SceneSelectionWidget *_scenes;
SceneItemSelectionWidget *_sources;
QComboBox *_action;
VariableDoubleSpinBox *_rotation;
QPushButton *_getSettings;
VariableTextEdit *_settings;
QPushButton *_getTransform;
QPushButton *_getCurrentValue;
VariableTextEdit *_transformString;
TransformSettingSelection *_settingSelection;
VariableLineEdit *_singleSettingValue;
QHBoxLayout *_buttonLayout;
std::shared_ptr<MacroActionSceneTransform> _entryData;

View File

@@ -52,16 +52,6 @@ static const std::map<MacroConditionMedia::State, std::string> mediaStates = {
"AdvSceneSwitcher.mediaTab.states.any"},
};
MacroConditionMedia::~MacroConditionMedia()
{
OBSSourceAutoRelease mediasource =
obs_weak_source_get_source(_source.GetSource());
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
signal_handler_disconnect(sh, "media_stopped", MediaStopped, this);
signal_handler_disconnect(sh, "media_ended", MediaEnded, this);
signal_handler_disconnect(sh, "media_next", MediaNext, this);
}
bool MacroConditionMedia::CheckTime()
{
OBSSourceAutoRelease s =
@@ -71,7 +61,7 @@ bool MacroConditionMedia::CheckTime()
bool match = false;
switch (_restriction) {
switch (_timeRestriction) {
case Time::TIME_RESTRICTION_NONE:
match = true;
break;
@@ -152,7 +142,21 @@ bool MacroConditionMedia::CheckMediaMatch()
if (!_source.GetSource()) {
return false;
}
bool matched = CheckState() && CheckTime();
bool matched = false;
switch (_checkType) {
case CheckType::STATE:
matched = CheckState();
break;
case CheckType::TIME:
matched = CheckTime();
break;
case CheckType::LEGACY:
matched = CheckState() && CheckTime();
break;
default:
break;
}
// reset for next check
_stopped = false;
@@ -168,16 +172,50 @@ void MacroConditionMedia::HandleSceneChange()
_lastConfigureScene = GetCurrentScene();
}
MacroConditionMedia::MacroConditionMedia(const MacroConditionMedia &other)
: MacroCondition(other.GetMacro()),
_sourceType(other._sourceType),
_checkType(other._checkType),
_state(other._state),
_timeRestriction(other._timeRestriction),
_scene(other._scene),
_source(other._source),
_rawSource(other._rawSource),
_sourceGroup(),
_time(other._time),
_lastConfigureScene(other._lastConfigureScene)
{
ResetSignalHandler();
}
MacroConditionMedia &
MacroConditionMedia::operator=(const MacroConditionMedia &other)
{
_sourceType = other._sourceType;
_checkType = other._checkType;
_state = other._state;
_timeRestriction = other._timeRestriction;
_scene = other._scene;
_source = other._source;
_rawSource = other._rawSource;
_time = other._time;
_lastConfigureScene = other._lastConfigureScene;
ResetSignalHandler();
return *this;
}
bool MacroConditionMedia::CheckCondition()
{
bool match = false;
switch (_sourceType) {
case Type::ANY:
case SourceType::ANY:
for (auto &source : _sourceGroup) {
match = match || source.CheckCondition();
}
break;
case Type::ALL: {
case SourceType::ALL: {
bool res = true;
for (auto &source : _sourceGroup) {
res = res && source.CheckCondition();
@@ -185,7 +223,7 @@ bool MacroConditionMedia::CheckCondition()
match = res;
break;
}
case Type::SOURCE:
case SourceType::SOURCE:
match = CheckMediaMatch();
break;
default:
@@ -205,10 +243,12 @@ bool MacroConditionMedia::Save(obs_data_t *obj) const
_source.Save(obj);
_scene.Save(obj);
obs_data_set_int(obj, "sourceType", static_cast<int>(_sourceType));
obs_data_set_int(obj, "checkType", static_cast<int>(_checkType));
obs_data_set_int(obj, "state", static_cast<int>(_state));
obs_data_set_int(obj, "restriction", static_cast<int>(_restriction));
obs_data_set_int(obj, "restriction",
static_cast<int>(_timeRestriction));
_time.Save(obj);
obs_data_set_int(obj, "version", 0);
obs_data_set_int(obj, "version", 1);
return true;
}
@@ -245,7 +285,7 @@ void MacroConditionMedia::UpdateMediaSourcesOfSceneList()
for (auto &source : mediaSources) {
MacroConditionMedia cond(*this);
cond._sourceType = Type::SOURCE;
cond._sourceType = SourceType::SOURCE;
cond._source.SetSource(source);
_sourceGroup.push_back(cond);
}
@@ -256,28 +296,31 @@ bool MacroConditionMedia::Load(obs_data_t *obj)
MacroCondition::Load(obj);
_source.Load(obj);
_scene.Load(obj);
_sourceType = static_cast<Type>(obs_data_get_int(obj, "sourceType"));
_state = static_cast<MacroConditionMedia::State>(
obs_data_get_int(obj, "state"));
_restriction = static_cast<MacroConditionMedia::Time>(
obs_data_get_int(obj, "restriction"));
_sourceType =
static_cast<SourceType>(obs_data_get_int(obj, "sourceType"));
_state = static_cast<State>(obs_data_get_int(obj, "state"));
_checkType = static_cast<CheckType>(obs_data_get_int(obj, "checkType"));
_timeRestriction =
static_cast<Time>(obs_data_get_int(obj, "restriction"));
_time.Load(obj);
if (_sourceType == Type::SOURCE) {
OBSSourceAutoRelease mediasource =
obs_weak_source_get_source(_source.GetSource());
signal_handler_t *sh =
obs_source_get_signal_handler(mediasource);
signal_handler_connect(sh, "media_stopped", MediaStopped, this);
signal_handler_connect(sh, "media_ended", MediaEnded, this);
signal_handler_connect(sh, "media_next", MediaNext, this);
if (_sourceType == SourceType::SOURCE) {
ResetSignalHandler();
}
UpdateMediaSourcesOfSceneList();
if (!obs_data_has_user_value(obj, "version")) {
if (_state ==
MacroConditionMedia::State::OBS_MEDIA_STATE_ENDED) {
_state = MacroConditionMedia::State::PLAYLIST_ENDED;
if (_state == State::OBS_MEDIA_STATE_ENDED) {
_state = State::PLAYLIST_ENDED;
}
}
if (obs_data_get_int(obj, "version") < 1) {
if (IsUsingLegacyCheck()) {
_checkType = CheckType::LEGACY;
} else if (_state == State::ANY) {
_checkType = CheckType::TIME;
} else {
_checkType = CheckType::STATE;
}
}
return true;
@@ -286,16 +329,16 @@ bool MacroConditionMedia::Load(obs_data_t *obj)
std::string MacroConditionMedia::GetShortDesc() const
{
switch (_sourceType) {
case Type::SOURCE:
case SourceType::SOURCE:
return _source.ToString();
case Type::ANY:
case SourceType::ANY:
if (_scene.GetScene(false)) {
return obs_module_text(
"AdvSceneSwitcher.condition.media.anyOnScene") +
std::string(" ") + _scene.ToString();
}
break;
case Type::ALL:
case SourceType::ALL:
if (_scene.GetScene(false)) {
return obs_module_text(
"AdvSceneSwitcher.condition.media.allOnScene") +
@@ -308,27 +351,21 @@ std::string MacroConditionMedia::GetShortDesc() const
return "";
}
void MacroConditionMedia::ClearSignalHandler()
bool MacroConditionMedia::IsUsingLegacyCheck() const
{
OBSSourceAutoRelease mediasource =
obs_weak_source_get_source(_source.GetSource());
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
signal_handler_disconnect(sh, "media_stopped", MediaStopped, this);
signal_handler_disconnect(sh, "media_ended", MediaEnded, this);
signal_handler_disconnect(sh, "media_next", MediaNext, this);
return _state != State::ANY &&
_timeRestriction != Time::TIME_RESTRICTION_NONE;
}
void MacroConditionMedia::ResetSignalHandler()
{
_signals.clear();
OBSSourceAutoRelease mediasource =
obs_weak_source_get_source(_source.GetSource());
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
signal_handler_disconnect(sh, "media_stopped", MediaStopped, this);
signal_handler_disconnect(sh, "media_ended", MediaEnded, this);
signal_handler_disconnect(sh, "media_next", MediaNext, this);
signal_handler_connect(sh, "media_stopped", MediaStopped, this);
signal_handler_connect(sh, "media_ended", MediaEnded, this);
signal_handler_connect(sh, "media_next", MediaNext, this);
_signals.emplace_back(sh, "media_stopped", MediaStopped, this);
_signals.emplace_back(sh, "media_ended", MediaEnded, this);
_signals.emplace_back(sh, "media_next", MediaNext, this);
}
void MacroConditionMedia::MediaStopped(void *data, calldata_t *)
@@ -361,17 +398,28 @@ void MacroConditionMedia::MediaNext(void *data, calldata_t *)
media->_next = true;
}
static void populateMediaTimes(QComboBox *list)
static void populateSateSelection(QComboBox *list, bool addLegacyEntries)
{
for (auto entry : mediaTimeRestrictions) {
list->addItem(obs_module_text(entry.second.c_str()));
for (const auto &[value, name] : mediaStates) {
if (value == MacroConditionMedia::State::ANY &&
!addLegacyEntries) {
continue;
}
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
}
static void populateMediaStates(QComboBox *list)
static void populateTimeRestrictionSelection(QComboBox *list,
bool addLegacyEntries)
{
for (auto entry : mediaStates) {
list->addItem(obs_module_text(entry.second.c_str()));
for (const auto &[value, name] : mediaTimeRestrictions) {
if (value == MacroConditionMedia::Time::TIME_RESTRICTION_NONE &&
!addLegacyEntries) {
continue;
}
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
}
@@ -380,19 +428,33 @@ static void populateSourceTypes(QComboBox *list)
list->clear();
list->addItem(
obs_module_text("AdvSceneSwitcher.condition.media.source"),
static_cast<int>(MacroConditionMedia::Type::SOURCE));
static_cast<int>(MacroConditionMedia::SourceType::SOURCE));
list->addItem(
obs_module_text("AdvSceneSwitcher.condition.media.anyOnScene"),
static_cast<int>(MacroConditionMedia::Type::ANY));
static_cast<int>(MacroConditionMedia::SourceType::ANY));
list->addItem(
obs_module_text("AdvSceneSwitcher.condition.media.allOnScene"),
static_cast<int>(MacroConditionMedia::Type::ALL));
static_cast<int>(MacroConditionMedia::SourceType::ALL));
}
static void populateCheckTypes(QComboBox *list)
{
list->clear();
list->addItem(
obs_module_text(
"AdvSceneSwitcher.condition.media.checkType.state"),
static_cast<int>(MacroConditionMedia::CheckType::STATE));
list->addItem(
obs_module_text(
"AdvSceneSwitcher.condition.media.checkType.time"),
static_cast<int>(MacroConditionMedia::CheckType::TIME));
}
MacroConditionMediaEdit::MacroConditionMediaEdit(
QWidget *parent, std::shared_ptr<MacroConditionMedia> entryData)
: QWidget(parent),
_sourceTypes(new QComboBox()),
_checkTypes(new QComboBox()),
_scenes(new SceneSelectionWidget(window(), true, true, true, true,
true)),
_sources(new SourceSelectionWidget(this, QStringList(), true)),
@@ -409,6 +471,8 @@ MacroConditionMediaEdit::MacroConditionMediaEdit(
QWidget::connect(_sourceTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(SourceTypeChanged(int)));
QWidget::connect(_checkTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(CheckTypeChanged(int)));
QWidget::connect(_sources,
SIGNAL(SourceChanged(const SourceSelection &)), this,
SLOT(SourceChanged(const SourceSelection &)));
@@ -422,20 +486,28 @@ MacroConditionMediaEdit::MacroConditionMediaEdit(
SLOT(TimeChanged(const Duration &)));
populateSourceTypes(_sourceTypes);
populateMediaStates(_states);
populateMediaTimes(_timeRestrictions);
populateCheckTypes(_checkTypes);
const bool isUsingLegacyCheck = entryData->_checkType ==
MacroConditionMedia::CheckType::LEGACY;
populateSateSelection(_states, isUsingLegacyCheck);
populateTimeRestrictionSelection(_timeRestrictions, isUsingLegacyCheck);
auto layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{sourceTypes}}", _sourceTypes},
{"{{checkTypes}}", _checkTypes},
{"{{mediaSources}}", _sources},
{"{{scenes}}", _scenes},
{"{{states}}", _states},
{"{{timeRestrictions}}", _timeRestrictions},
{"{{time}}", _time},
};
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.media.entry"),
layout, widgetPlaceholders);
PlaceWidgets(
obs_module_text(
isUsingLegacyCheck
? "AdvSceneSwitcher.condition.media.layout.legacy"
: "AdvSceneSwitcher.condition.media.layout"),
layout, widgetPlaceholders);
setLayout(layout);
_entryData = entryData;
@@ -445,15 +517,12 @@ MacroConditionMediaEdit::MacroConditionMediaEdit(
void MacroConditionMediaEdit::SourceTypeChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_sourceType = static_cast<MacroConditionMedia::Type>(
GUARD_LOADING_AND_LOCK();
_entryData->_sourceType = static_cast<MacroConditionMedia::SourceType>(
_sourceTypes->itemData(idx).toInt());
if (_entryData->_sourceType == MacroConditionMedia::Type::SOURCE) {
if (_entryData->_sourceType ==
MacroConditionMedia::SourceType::SOURCE) {
_entryData->_sourceGroup.clear();
}
@@ -464,16 +533,19 @@ void MacroConditionMediaEdit::SourceTypeChanged(int idx)
SetWidgetVisibility();
}
void MacroConditionMediaEdit::CheckTypeChanged(int idx)
{
GUARD_LOADING_AND_LOCK();
_entryData->_checkType = static_cast<MacroConditionMedia::CheckType>(
_checkTypes->itemData(idx).toInt());
SetWidgetVisibility();
}
void MacroConditionMediaEdit::SourceChanged(const SourceSelection &source)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_sourceGroup.clear();
_entryData->_sourceType = MacroConditionMedia::Type::SOURCE;
_entryData->ClearSignalHandler();
_entryData->_sourceType = MacroConditionMedia::SourceType::SOURCE;
_entryData->_source = source;
_entryData->ResetSignalHandler();
emit HeaderInfoChanged(
@@ -483,40 +555,20 @@ void MacroConditionMediaEdit::SourceChanged(const SourceSelection &source)
void MacroConditionMediaEdit::SceneChanged(const SceneSelection &s)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_scene = s;
_entryData->UpdateMediaSourcesOfSceneList();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
MacroConditionMedia::State getMediaStateFromIdx(int idx)
{
if (idx < static_cast<int>(
MacroConditionMedia::State::LAST_OBS_MEDIA_STATE)) {
return static_cast<MacroConditionMedia::State>(idx);
} else {
return static_cast<MacroConditionMedia::State>(
idx -
static_cast<int>(
MacroConditionMedia::State::LAST_OBS_MEDIA_STATE) +
custom_media_states_offset);
}
}
void MacroConditionMediaEdit::StateChanged(int index)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_state = getMediaStateFromIdx(index);
if (_entryData->_sourceType != MacroConditionMedia::Type::SOURCE) {
GUARD_LOADING_AND_LOCK();
_entryData->_state = static_cast<MacroConditionMedia::State>(
_states->itemData(index).toInt());
if (_entryData->_sourceType !=
MacroConditionMedia::SourceType::SOURCE) {
_entryData->UpdateMediaSourcesOfSceneList();
}
}
@@ -527,7 +579,9 @@ void MacroConditionMediaEdit::TimeRestrictionChanged(int index)
return;
}
if (static_cast<MacroConditionMedia::Time>(index) ==
const auto timeRestriction = static_cast<MacroConditionMedia::Time>(
_timeRestrictions->itemData(index).toInt());
if (timeRestriction ==
MacroConditionMedia::Time::TIME_RESTRICTION_NONE) {
_time->setDisabled(true);
} else {
@@ -535,9 +589,9 @@ void MacroConditionMediaEdit::TimeRestrictionChanged(int index)
}
auto lock = LockContext();
_entryData->_restriction =
static_cast<MacroConditionMedia::Time>(index);
if (_entryData->_sourceType != MacroConditionMedia::Type::SOURCE) {
_entryData->_timeRestriction = timeRestriction;
if (_entryData->_sourceType !=
MacroConditionMedia::SourceType::SOURCE) {
_entryData->UpdateMediaSourcesOfSceneList();
}
}
@@ -550,7 +604,8 @@ void MacroConditionMediaEdit::TimeChanged(const Duration &dur)
auto lock = LockContext();
_entryData->_time = dur;
if (_entryData->_sourceType != MacroConditionMedia::Type::SOURCE) {
if (_entryData->_sourceType !=
MacroConditionMedia::SourceType::SOURCE) {
_entryData->UpdateMediaSourcesOfSceneList();
}
}
@@ -558,20 +613,25 @@ void MacroConditionMediaEdit::TimeChanged(const Duration &dur)
void MacroConditionMediaEdit::SetWidgetVisibility()
{
_sources->setVisible(_entryData->_sourceType ==
MacroConditionMedia::Type::SOURCE);
MacroConditionMedia::SourceType::SOURCE);
_scenes->setVisible(_entryData->_sourceType !=
MacroConditionMedia::Type::SOURCE);
}
MacroConditionMedia::SourceType::SOURCE);
static int getIdxFromMediaState(MacroConditionMedia::State state)
{
if (state < MacroConditionMedia::State::LAST_OBS_MEDIA_STATE) {
return static_cast<int>(state);
} else {
return static_cast<int>(state) - custom_media_states_offset +
static_cast<int>(
MacroConditionMedia::State::LAST_OBS_MEDIA_STATE);
if (_entryData->_checkType == MacroConditionMedia::CheckType::LEGACY) {
_checkTypes->hide();
return;
}
_states->setVisible(_entryData->_checkType ==
MacroConditionMedia::CheckType::STATE);
_timeRestrictions->setVisible(_entryData->_checkType ==
MacroConditionMedia::CheckType::TIME);
_time->setVisible(_entryData->_checkType ==
MacroConditionMedia::CheckType::TIME);
adjustSize();
updateGeometry();
}
void MacroConditionMediaEdit::UpdateEntryData()
@@ -580,15 +640,18 @@ void MacroConditionMediaEdit::UpdateEntryData()
return;
}
_checkTypes->setCurrentIndex(_checkTypes->findData(
static_cast<int>(_entryData->_checkType)));
_sourceTypes->setCurrentIndex(_sourceTypes->findData(
static_cast<int>(_entryData->_sourceType)));
_sources->SetSource(_entryData->_source);
_scenes->SetScene(_entryData->_scene);
_states->setCurrentIndex(getIdxFromMediaState(_entryData->_state));
_timeRestrictions->setCurrentIndex(
static_cast<int>(_entryData->_restriction));
_states->setCurrentIndex(
_states->findData(static_cast<int>(_entryData->_state)));
_timeRestrictions->setCurrentIndex(_timeRestrictions->findData(
static_cast<int>(_entryData->_timeRestriction)));
_time->SetDuration(_entryData->_time);
if (_entryData->_restriction ==
if (_entryData->_timeRestriction ==
MacroConditionMedia::Time::TIME_RESTRICTION_NONE) {
_time->setDisabled(true);
}

View File

@@ -5,18 +5,18 @@
#include "source-selection.hpp"
#include <limits>
#include <obs.hpp>
#include <QWidget>
#include <QComboBox>
#include <QCheckBox>
namespace advss {
constexpr auto custom_media_states_offset = 100;
class MacroConditionMedia : public MacroCondition {
public:
MacroConditionMedia(Macro *m) : MacroCondition(m) {}
~MacroConditionMedia();
MacroConditionMedia(const MacroConditionMedia &);
MacroConditionMedia &operator=(const MacroConditionMedia &);
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
@@ -26,19 +26,17 @@ public:
{
return std::make_shared<MacroConditionMedia>(m);
}
void ClearSignalHandler();
void ResetSignalHandler();
void UpdateMediaSourcesOfSceneList();
static void MediaStopped(void *data, calldata_t *);
static void MediaEnded(void *data, calldata_t *);
static void MediaNext(void *data, calldata_t *);
enum class Type {
SOURCE,
ANY,
ALL,
};
Type _sourceType = Type::SOURCE;
enum class SourceType { SOURCE, ANY, ALL };
SourceType _sourceType = SourceType::SOURCE;
enum class CheckType { STATE, TIME, LEGACY = 1000 };
CheckType _checkType = CheckType::STATE;
enum class State {
// OBS's internal states
@@ -53,19 +51,19 @@ public:
// Just a marker
LAST_OBS_MEDIA_STATE,
// states added for use in the plugin
PLAYLIST_ENDED = custom_media_states_offset,
ANY,
PLAYLIST_ENDED = 100,
ANY
};
State _state = State::OBS_MEDIA_STATE_NONE;
State _state = State::OBS_MEDIA_STATE_PLAYING;
enum class Time {
TIME_RESTRICTION_NONE,
TIME_RESTRICTION_SHORTER,
TIME_RESTRICTION_LONGER,
TIME_RESTRICTION_REMAINING_SHORTER,
TIME_RESTRICTION_REMAINING_LONGER,
TIME_RESTRICTION_REMAINING_LONGER
};
Time _restriction = Time::TIME_RESTRICTION_NONE;
Time _timeRestriction = Time::TIME_RESTRICTION_REMAINING_SHORTER;
SceneSelection _scene;
SourceSelection _source;
@@ -74,12 +72,15 @@ public:
Duration _time;
private:
bool IsUsingLegacyCheck() const;
bool CheckTime();
bool CheckState();
bool CheckPlaylistEnd(const obs_media_state);
bool CheckMediaMatch();
void HandleSceneChange();
std::vector<OBSSignal> _signals;
bool _stopped = false;
bool _ended = false;
bool _next = false;
@@ -110,6 +111,7 @@ public:
}
private slots:
void CheckTypeChanged(int);
void SourceTypeChanged(int);
void SourceChanged(const SourceSelection &);
void SceneChanged(const SceneSelection &);
@@ -121,6 +123,7 @@ signals:
protected:
QComboBox *_sourceTypes;
QComboBox *_checkTypes;
SceneSelectionWidget *_scenes;
SourceSelectionWidget *_sources;
QComboBox *_states;

View File

@@ -26,7 +26,7 @@ public:
ProcessConfig _procConfig;
bool _checkExitCode = true;
int _exitCodeToCheck = 0;
Duration _timeout = Duration(0.1);
Duration _timeout = Duration(1.0);
private:
void RunProcess();

View File

@@ -1,6 +1,7 @@
#include "macro-condition-scene-transform.hpp"
#include "layout-helpers.hpp"
#include "json-helpers.hpp"
#include "math-helpers.hpp"
#include "text-helpers.hpp"
#include "scene-item-transform-helpers.hpp"
@@ -15,12 +16,28 @@ bool MacroConditionSceneTransform::_registered =
MacroConditionSceneTransformEdit::Create,
"AdvSceneSwitcher.condition.sceneTransform"});
const static std::map<MacroConditionSceneTransform::CondType, std::string>
static const std::map<MacroConditionSceneTransform::SettingsType, std::string>
settingsTypes = {
{MacroConditionSceneTransform::SettingsType::ALL,
"AdvSceneSwitcher.condition.sceneTransform.settingsType.all"},
{MacroConditionSceneTransform::SettingsType::SINGLE,
"AdvSceneSwitcher.condition.sceneTransform.settingsType.single"},
};
static const std::map<MacroConditionSceneTransform::Compare, std::string>
compareTypes = {
{MacroConditionSceneTransform::Compare::LESS,
"AdvSceneSwitcher.condition.sceneTransform.compare.less"},
{MacroConditionSceneTransform::Compare::EQUAL,
"AdvSceneSwitcher.condition.sceneTransform.compare.equal"},
{MacroConditionSceneTransform::Compare::MORE,
"AdvSceneSwitcher.condition.sceneTransform.compare.more"},
};
static const std::map<MacroConditionSceneTransform::Condition, std::string>
conditionTypes = {
{MacroConditionSceneTransform::CondType::MATCHES,
"AdvSceneSwitcher.condition.sceneTransform.entry.type.matches"},
{MacroConditionSceneTransform::CondType::CHANGED,
"AdvSceneSwitcher.condition.sceneTransform.entry.type.changed"},
{MacroConditionSceneTransform::Condition::MATCHES,
"AdvSceneSwitcher.condition.sceneTransform.condition.match"},
{MacroConditionSceneTransform::Condition::CHANGED,
"AdvSceneSwitcher.condition.sceneTransform.condition.changed"},
};
static bool doesTransformOfAnySceneItemMatch(
@@ -64,21 +81,17 @@ didTransformOfAnySceneItemChange(const std::vector<OBSSceneItem> &items,
return ret;
}
bool MacroConditionSceneTransform::CheckCondition()
bool MacroConditionSceneTransform::CheckAllSettings(
const std::vector<OBSSceneItem> &items)
{
auto items = _source.GetSceneItems(_scene);
if (items.empty()) {
return false;
}
std::string newVariable = "";
bool ret = false;
switch (_type) {
case CondType::MATCHES:
ret = doesTransformOfAnySceneItemMatch(items, _settings, _regex,
newVariable);
switch (_condition) {
case Condition::MATCHES:
ret = doesTransformOfAnySceneItemMatch(items, _transformString,
_regex, newVariable);
break;
case CondType::CHANGED:
case Condition::CHANGED:
ret = didTransformOfAnySceneItemChange(
items, _previousTransform, newVariable);
break;
@@ -88,17 +101,143 @@ bool MacroConditionSceneTransform::CheckCondition()
}
SetVariableValue(newVariable);
SetTempVarValue("settings", newVariable);
return ret;
}
bool MacroConditionSceneTransform::AnySceneItemTransformSettingChanged(
const std::vector<OBSSceneItem> &items)
{
bool ret = false;
if (_previousSettingValues.size() < items.size()) {
_previousSettingValues.resize(items.size());
return true;
}
std::vector<std::string> varValues;
for (size_t i = 0; i < items.size(); i++) {
const auto &item = items[i];
auto &previousValue = _previousSettingValues[i];
const auto currentValue =
GetTransformSettingValue(item, _setting);
if (!currentValue) {
continue;
}
varValues.emplace_back(*currentValue);
if (*currentValue == previousValue) {
continue;
}
previousValue = *currentValue;
ret = true;
}
SetTempVarHelper(varValues);
return ret;
}
static bool compareValue(MacroConditionSceneTransform::Compare compareMethod,
double value1, double value2)
{
switch (compareMethod) {
case MacroConditionSceneTransform::Compare::LESS:
return value1 < value2;
case MacroConditionSceneTransform::Compare::EQUAL:
return DoubleEquals(value1, value2, 0.00001);
case MacroConditionSceneTransform::Compare::MORE:
return value1 > value2;
default:
break;
}
return false;
}
bool MacroConditionSceneTransform::AnySceneItemTransformSettingMatches(
const std::vector<OBSSceneItem> &items)
{
bool ret = false;
std::vector<std::string> varValues;
for (const auto &item : items) {
const auto currentValue =
GetTransformSettingValue(item, _setting);
if (!currentValue) {
continue;
}
try {
if (compareValue(_compare, std::stod(*currentValue),
std::stod(_singleSetting))) {
ret = true;
}
} catch (std::invalid_argument &) {
} catch (std::out_of_range &) {
}
varValues.emplace_back(*currentValue);
}
SetTempVarHelper(varValues);
return ret;
}
void MacroConditionSceneTransform::SetTempVarHelper(
const std::vector<std::string> &values)
{
std::string varValue;
bool addSeperator = false;
for (const auto &value : values) {
if (addSeperator) {
varValue += ";";
}
varValue += value;
addSeperator = true;
}
SetTempVarValue("setting", varValue);
}
bool MacroConditionSceneTransform::CheckSingleSetting(
const std::vector<OBSSceneItem> &items)
{
if (_condition == Condition::CHANGED) {
return AnySceneItemTransformSettingChanged(items);
}
return AnySceneItemTransformSettingMatches(items);
}
bool MacroConditionSceneTransform::CheckCondition()
{
auto items = _source.GetSceneItems(_scene);
if (items.empty()) {
return false;
}
switch (_settingsType) {
case SettingsType::ALL:
return CheckAllSettings(items);
case SettingsType::SINGLE:
return CheckSingleSetting(items);
default:
break;
}
return false;
}
bool MacroConditionSceneTransform::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
_scene.Save(obj);
_source.Save(obj);
_settings.Save(obj, "settings");
_transformString.Save(obj, "transformString");
_singleSetting.Save(obj, "singleSetting");
_regex.Save(obj);
obs_data_set_int(obj, "type", static_cast<int>(_type));
_setting.Save(obj);
obs_data_set_int(obj, "settingsType", static_cast<int>(_settingsType));
obs_data_set_int(obj, "compare", static_cast<int>(_compare));
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
obs_data_set_int(obj, "version", 1);
return true;
}
@@ -114,14 +253,32 @@ bool MacroConditionSceneTransform::Load(obs_data_t *obj)
MacroCondition::Load(obj);
_scene.Load(obj);
_source.Load(obj);
_settings.Load(obj, "settings");
_transformString.Load(obj, "transformString");
_singleSetting.Load(obj, "singleSetting");
_regex.Load(obj);
_type = static_cast<CondType>(obs_data_get_int(obj, "type"));
_setting.Load(obj);
SetSettingsType(static_cast<SettingsType>(
obs_data_get_int(obj, "settingsType")));
_compare = static_cast<Compare>(obs_data_get_int(obj, "compare"));
SetCondition(
static_cast<Condition>(obs_data_get_int(obj, "condition")));
// TOOD: remove in future version
if (obs_data_has_user_value(obj, "regex")) {
_regex.CreateBackwardsCompatibleRegex(
obs_data_get_bool(obj, "regex"));
}
if (!obs_data_has_user_value(obj, "version")) {
SetCondition(
static_cast<Condition>(obs_data_get_int(obj, "type")));
_transformString.Load(obj, "settings");
SetSettingsType(SettingsType::ALL);
_compare = Compare::EQUAL;
}
SetupTempVars();
return true;
}
@@ -134,10 +291,41 @@ std::string MacroConditionSceneTransform::GetShortDesc() const
return _scene.ToString() + " - " + _source.ToString();
}
static inline void populateTypesSelection(QComboBox *list)
void MacroConditionSceneTransform::SetSettingsType(SettingsType type)
{
for (const auto &entry : conditionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
_settingsType = type;
SetupTempVars();
}
void MacroConditionSceneTransform::SetCondition(Condition condition)
{
_condition = condition;
_previousTransform.clear();
_previousSettingValues.clear();
}
void MacroConditionSceneTransform::SetupTempVars()
{
MacroCondition::SetupTempVars();
if (_settingsType == SettingsType::ALL) {
AddTempvar(
"settings",
obs_module_text(
"AdvSceneSwitcher.tempVar.transform.settings"));
} else {
AddTempvar(
"setting",
obs_module_text(
"AdvSceneSwitcher.tempVar.transform.setting"));
}
}
template<class T>
static inline void populateSelection(QComboBox *list,
const std::map<T, std::string> &map)
{
for (const auto &[_, name] : map) {
list->addItem(obs_module_text(name.c_str()));
}
}
@@ -148,13 +336,22 @@ MacroConditionSceneTransformEdit::MacroConditionSceneTransformEdit(
_scenes(new SceneSelectionWidget(window(), true, false, false, true)),
_sources(new SceneItemSelectionWidget(
parent, true, SceneItemSelectionWidget::Placeholder::ANY)),
_types(new QComboBox()),
_settingsType(new QComboBox()),
_compare(new QComboBox()),
_conditions(new QComboBox()),
_getSettings(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.sceneTransform.getTransform"))),
_settings(new VariableTextEdit(this)),
_regex(new RegexConfigWidget(parent))
_getCurrentValue(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.sceneTransform.getCurrentValue"))),
_transformString(new VariableTextEdit(this)),
_singleSettingValue(new VariableLineEdit(this)),
_regex(new RegexConfigWidget(parent)),
_settingSelection(new TransformSettingSelection(this)),
_compareLayout(new QHBoxLayout())
{
populateTypesSelection(_types);
populateSelection(_settingsType, settingsTypes);
populateSelection(_compare, compareTypes);
populateSelection(_conditions, conditionTypes);
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &)));
@@ -163,45 +360,62 @@ MacroConditionSceneTransformEdit::MacroConditionSceneTransformEdit(
QWidget::connect(_sources,
SIGNAL(SceneItemChanged(const SceneItemSelection &)),
this, SLOT(SourceChanged(const SceneItemSelection &)));
QWidget::connect(_types, SIGNAL(currentIndexChanged(int)), this,
SLOT(TypeChanged(int)));
QWidget::connect(_settingsType, SIGNAL(currentIndexChanged(int)), this,
SLOT(SettingsTypeChanged(int)));
QWidget::connect(_compare, SIGNAL(currentIndexChanged(int)), this,
SLOT(CompareChanged(int)));
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(
_settingSelection,
SIGNAL(SelectionChanged(const TransformSetting &)), this,
SLOT(SettingSelectionChanged(const TransformSetting &)));
QWidget::connect(_getSettings, SIGNAL(clicked()), this,
SLOT(GetSettingsClicked()));
QWidget::connect(_settings, SIGNAL(textChanged()), this,
SLOT(SettingsChanged()));
QWidget::connect(_getCurrentValue, SIGNAL(clicked()), this,
SLOT(GetCurrentValueClicked()));
QWidget::connect(_transformString, SIGNAL(textChanged()), this,
SLOT(TransformChanged()));
QWidget::connect(_singleSettingValue, SIGNAL(editingFinished()), this,
SLOT(SettingValueChanged()));
QWidget::connect(_regex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(RegexChanged(const RegexConfig &)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes},
{"{{sources}}", _sources},
{"{{types}}", _types},
{"{{settings}}", _settings},
{"{{compare}}", _compare},
{"{{setting}}", _settingSelection},
{"{{settingsType}}", _settingsType},
{"{{conditions}}", _conditions},
{"{{transformString}}", _transformString},
{"{{singleSettingValue}}", _singleSettingValue},
{"{{settings}}", _singleSettingValue},
{"{{getSettings}}", _getSettings},
{"{{getCurrentValue}}", _getCurrentValue},
{"{{regex}}", _regex},
};
QHBoxLayout *line1Layout = new QHBoxLayout;
auto entryLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.sceneTransform.entry"),
entryLayout, widgetPlaceholders);
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.sceneTransform.entry.line1"),
line1Layout, widgetPlaceholders);
QHBoxLayout *line2Layout = new QHBoxLayout;
"AdvSceneSwitcher.condition.sceneTransform.entry.singleSettingCompare"),
_compareLayout, widgetPlaceholders, false);
auto optionsLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.sceneTransform.entry.line2"),
line2Layout, widgetPlaceholders, false);
QHBoxLayout *line3Layout = new QHBoxLayout;
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.sceneTransform.entry.line3"),
line3Layout, widgetPlaceholders);
"AdvSceneSwitcher.condition.sceneTransform.entry.options"),
optionsLayout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(line1Layout);
mainLayout->addLayout(line2Layout);
mainLayout->addLayout(line3Layout);
mainLayout->addLayout(entryLayout);
mainLayout->addLayout(_compareLayout);
mainLayout->addWidget(_transformString);
mainLayout->addLayout(optionsLayout);
setLayout(mainLayout);
_entryData = entryData;
@@ -217,52 +431,75 @@ void MacroConditionSceneTransformEdit::UpdateEntryData()
_scenes->SetScene(_entryData->_scene);
_sources->SetSceneItem(_entryData->_source);
_types->setCurrentIndex(static_cast<int>(_entryData->_type));
_settingsType->setCurrentIndex(
static_cast<int>(_entryData->GetSettingsType()));
_compare->setCurrentIndex(static_cast<int>(_entryData->_compare));
_conditions->setCurrentIndex(
static_cast<int>(_entryData->GetCondition()));
_regex->SetRegexConfig(_entryData->_regex);
_settings->setPlainText(_entryData->_settings);
UpdateSettingSelection();
_settingSelection->SetSetting(_entryData->_setting);
_transformString->setPlainText(_entryData->_transformString);
_singleSettingValue->setText(_entryData->_singleSetting);
SetWidgetVisibility();
}
void MacroConditionSceneTransformEdit::SceneChanged(const SceneSelection &s)
{
if (_loading || !_entryData) {
return;
{
GUARD_LOADING_AND_LOCK();
_entryData->_scene = s;
}
auto lock = LockContext();
_entryData->_scene = s;
UpdateSettingSelection();
}
void MacroConditionSceneTransformEdit::SourceChanged(
const SceneItemSelection &item)
{
if (_loading || !_entryData) {
return;
{
GUARD_LOADING_AND_LOCK();
_entryData->_source = item;
}
auto lock = LockContext();
_entryData->_source = item;
UpdateSettingSelection();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
adjustSize();
updateGeometry();
}
void MacroConditionSceneTransformEdit::TypeChanged(int index)
void MacroConditionSceneTransformEdit::SettingsTypeChanged(int type)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_type =
static_cast<MacroConditionSceneTransform::CondType>(index);
GUARD_LOADING_AND_LOCK();
_entryData->SetSettingsType(
static_cast<MacroConditionSceneTransform::SettingsType>(type));
SetWidgetVisibility();
}
void MacroConditionSceneTransformEdit::CompareChanged(int type)
{
GUARD_LOADING_AND_LOCK();
_entryData->_compare =
static_cast<MacroConditionSceneTransform::Compare>(type);
}
void MacroConditionSceneTransformEdit::ConditionChanged(int index)
{
GUARD_LOADING_AND_LOCK();
_entryData->SetCondition(
static_cast<MacroConditionSceneTransform::Condition>(index));
SetWidgetVisibility();
}
void MacroConditionSceneTransformEdit::SettingSelectionChanged(
const TransformSetting &setting)
{
GUARD_LOADING_AND_LOCK();
_entryData->_setting = setting;
}
void MacroConditionSceneTransformEdit::GetSettingsClicked()
{
if (_loading || !_entryData || !_entryData->_scene.GetScene(false)) {
return;
}
@@ -276,17 +513,47 @@ void MacroConditionSceneTransformEdit::GetSettingsClicked()
if (_entryData->_regex.Enabled()) {
settings = EscapeForRegex(settings);
}
_settings->setPlainText(settings);
_transformString->setPlainText(settings);
adjustSize();
updateGeometry();
}
void MacroConditionSceneTransformEdit::SettingsChanged()
void MacroConditionSceneTransformEdit::GetCurrentValueClicked()
{
if (_loading || !_entryData) {
if (_loading || !_entryData || !_entryData->_scene.GetScene(false)) {
return;
}
auto lock = LockContext();
_entryData->_settings = _settings->toPlainText().toStdString();
auto items = _entryData->_source.GetSceneItems(_entryData->_scene);
if (items.empty()) {
return;
}
auto value =
GetTransformSettingValue(items.at(0), _entryData->_setting);
if (!value) {
return;
}
_singleSettingValue->setText(QString::fromStdString(*value));
SettingValueChanged();
}
void MacroConditionSceneTransformEdit::TransformChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_transformString =
_transformString->toPlainText().toStdString();
adjustSize();
updateGeometry();
}
void MacroConditionSceneTransformEdit::SettingValueChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_singleSetting = _singleSettingValue->text().toStdString();
adjustSize();
updateGeometry();
@@ -294,11 +561,7 @@ void MacroConditionSceneTransformEdit::SettingsChanged()
void MacroConditionSceneTransformEdit::RegexChanged(const RegexConfig &conf)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
GUARD_LOADING_AND_LOCK();
_entryData->_regex = conf;
adjustSize();
@@ -307,14 +570,61 @@ void MacroConditionSceneTransformEdit::RegexChanged(const RegexConfig &conf)
void MacroConditionSceneTransformEdit::SetWidgetVisibility()
{
const bool showSettingsAndRegex =
_entryData->_type ==
MacroConditionSceneTransform::CondType::MATCHES;
_settings->setVisible(showSettingsAndRegex);
_getSettings->setVisible(showSettingsAndRegex);
_regex->setVisible(showSettingsAndRegex);
const auto condition = _entryData->GetCondition();
const auto settingsType = _entryData->GetSettingsType();
_transformString->setVisible(
condition == MacroConditionSceneTransform::Condition::MATCHES);
_regex->setVisible(
condition == MacroConditionSceneTransform::Condition::MATCHES &&
settingsType ==
MacroConditionSceneTransform::SettingsType::ALL);
_getSettings->setVisible(
condition == MacroConditionSceneTransform::Condition::MATCHES &&
settingsType ==
MacroConditionSceneTransform::SettingsType::ALL);
_getCurrentValue->setVisible(
condition == MacroConditionSceneTransform::Condition::MATCHES &&
settingsType ==
MacroConditionSceneTransform::SettingsType::SINGLE);
SetLayoutVisible(
_compareLayout,
condition == MacroConditionSceneTransform::Condition::MATCHES &&
settingsType == MacroConditionSceneTransform::
SettingsType::SINGLE);
_settingSelection->setVisible(
settingsType ==
MacroConditionSceneTransform::SettingsType::SINGLE);
if (condition == MacroConditionSceneTransform::Condition::MATCHES &&
settingsType ==
MacroConditionSceneTransform::SettingsType::SINGLE) {
RemoveStretchIfPresent(_compareLayout);
} else if (condition ==
MacroConditionSceneTransform::Condition::CHANGED &&
settingsType ==
MacroConditionSceneTransform::SettingsType::SINGLE) {
AddStretchIfNecessary(_compareLayout);
}
_transformString->setVisible(
condition == MacroConditionSceneTransform::Condition::MATCHES &&
settingsType ==
MacroConditionSceneTransform::SettingsType::ALL);
adjustSize();
updateGeometry();
}
void MacroConditionSceneTransformEdit::UpdateSettingSelection() const
{
if (!_entryData) {
_settingSelection->SetSource(nullptr);
return;
}
auto items = _entryData->_source.GetSceneItems(_entryData->_scene);
if (items.empty()) {
_settingSelection->SetSource(nullptr);
return;
}
_settingSelection->SetSource(items.at(0));
}
} // namespace advss

View File

@@ -1,9 +1,10 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "scene-selection.hpp"
#include "scene-item-selection.hpp"
#include "variable-text-edit.hpp"
#include "regex-config.hpp"
#include "scene-item-selection.hpp"
#include "scene-selection.hpp"
#include "transform-setting.hpp"
#include "variable-text-edit.hpp"
#include <QSpinBox>
#include <QCheckBox>
@@ -23,19 +24,38 @@ public:
return std::make_shared<MacroConditionSceneTransform>(m);
}
enum class CondType {
MATCHES,
CHANGED,
};
enum class SettingsType { ALL, SINGLE };
enum class Condition { MATCHES, CHANGED };
enum class Compare { LESS, EQUAL, MORE };
void SetSettingsType(SettingsType type);
SettingsType GetSettingsType() const { return _settingsType; }
void SetCondition(Condition condition);
Condition GetCondition() const { return _condition; }
SceneSelection _scene;
SceneItemSelection _source;
CondType _type = CondType::MATCHES;
Compare _compare = Compare::EQUAL;
RegexConfig _regex;
StringVariable _settings = "";
StringVariable _transformString = "";
StringVariable _singleSetting = "";
TransformSetting _setting;
private:
void SetupTempVars();
bool CheckAllSettings(const std::vector<OBSSceneItem> &);
bool CheckSingleSetting(const std::vector<OBSSceneItem> &);
bool
AnySceneItemTransformSettingChanged(const std::vector<OBSSceneItem> &);
bool
AnySceneItemTransformSettingMatches(const std::vector<OBSSceneItem> &);
void SetTempVarHelper(const std::vector<std::string> &values);
SettingsType _settingsType = SettingsType::SINGLE;
Condition _condition = Condition::MATCHES;
std::vector<std::string> _previousTransform;
std::vector<std::string> _previousSettingValues;
static bool _registered;
static const std::string id;
@@ -62,25 +82,36 @@ public:
private slots:
void SceneChanged(const SceneSelection &);
void SourceChanged(const SceneItemSelection &);
void TypeChanged(int type);
void SettingsTypeChanged(int type);
void CompareChanged(int type);
void ConditionChanged(int type);
void SettingSelectionChanged(const TransformSetting &);
void GetSettingsClicked();
void SettingsChanged();
void GetCurrentValueClicked();
void TransformChanged();
void SettingValueChanged();
void RegexChanged(const RegexConfig &);
signals:
void HeaderInfoChanged(const QString &);
protected:
SceneSelectionWidget *_scenes;
SceneItemSelectionWidget *_sources;
QComboBox *_types;
QPushButton *_getSettings;
VariableTextEdit *_settings;
RegexConfigWidget *_regex;
std::shared_ptr<MacroConditionSceneTransform> _entryData;
private:
void SetWidgetVisibility();
void UpdateSettingSelection() const;
SceneSelectionWidget *_scenes;
SceneItemSelectionWidget *_sources;
QComboBox *_settingsType;
QComboBox *_compare;
QComboBox *_conditions;
QPushButton *_getSettings;
QPushButton *_getCurrentValue;
VariableTextEdit *_transformString;
VariableLineEdit *_singleSettingValue;
RegexConfigWidget *_regex;
TransformSettingSelection *_settingSelection;
QHBoxLayout *_compareLayout;
std::shared_ptr<MacroConditionSceneTransform> _entryData;
bool _loading = true;
};

View File

@@ -0,0 +1,373 @@
#include "transform-setting.hpp"
#include "obs-module-helper.hpp"
#include "math-helpers.hpp"
#include "scene-item-transform-helpers.hpp"
#include "utility.hpp"
#include <nlohmann/json.hpp>
#include <QLayout>
Q_DECLARE_METATYPE(advss::TransformSetting);
namespace advss {
TransformSetting::TransformSetting(const std::string &id,
const std::string &nestedId)
: _id(id),
_nestedId(nestedId)
{
}
bool TransformSetting::Save(obs_data_t *obj) const
{
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "id", _id.c_str());
obs_data_set_string(data, "nestedId", _nestedId.c_str());
obs_data_set_obj(obj, "transformSetting", data);
return true;
}
bool TransformSetting::Load(obs_data_t *obj)
{
OBSDataAutoRelease data = obs_data_get_obj(obj, "transformSetting");
_id = obs_data_get_string(data, "id");
_nestedId = obs_data_get_string(data, "nestedId");
return true;
}
bool TransformSetting::operator==(const TransformSetting &other) const
{
return _id == other._id && _nestedId == other._nestedId;
}
static void
getSoruceTransformSettingsHelper(std::vector<TransformSetting> &settings,
const std::string &jsonString,
const std::string &nestedId = "")
{
try {
auto transform = nlohmann::json::parse(jsonString);
for (const auto &[key, value] : transform.items()) {
if (value.is_object() && nestedId.empty()) {
getSoruceTransformSettingsHelper(
settings, value.dump(), key);
} else {
settings.emplace_back(key, nestedId);
}
}
} catch (const nlohmann::json::exception &) {
return;
}
}
std::vector<TransformSetting> GetSoruceTransformSettings(obs_scene_item *source)
{
std::vector<TransformSetting> settings;
auto jsonString = GetSceneItemTransform(source);
getSoruceTransformSettingsHelper(settings, jsonString);
return settings;
}
std::optional<std::string>
GetTransformSettingValue(obs_scene_item *source,
const TransformSetting &setting)
{
auto jsonString = GetSceneItemTransform(source);
if (setting.GetNestedID().empty()) {
return GetJsonField(jsonString, setting.GetID());
}
auto nestedJsonString = GetJsonField(jsonString, setting.GetNestedID());
if (nestedJsonString.has_value()) {
return GetJsonField(*nestedJsonString, setting.GetID());
}
return {};
}
template<class T>
static void logConversionError(T, const char *func, const char *target,
const char *value)
{
if (std::is_same<T, std::invalid_argument>::value) {
blog(LOG_WARNING, "%s invalid %s value (%s)", func, target,
value);
} else {
blog(LOG_WARNING, "%s value out of range for %s (%s)", func,
target, value);
}
}
static void handleCrop(struct obs_sceneitem_crop &crop, const std::string &id,
const std::string &value)
{
int val = 0;
try {
val = std::stoi(value);
} catch (const std::invalid_argument &e) {
logConversionError(e, __func__, id.c_str(), value.c_str());
return;
} catch (const std::out_of_range &e) {
logConversionError(e, __func__, id.c_str(), value.c_str());
return;
}
if (id == "left") {
crop.left = val;
} else if (id == "top") {
crop.top = val;
} else if (id == "right") {
crop.right = val;
} else if (id == "bottom") {
crop.bottom = val;
}
}
static void handlePosOrScaleOrBounds(struct obs_transform_info &info,
const TransformSetting &setting,
const std::string &value)
{
float val = 0;
try {
val = std::stof(value);
} catch (const std::invalid_argument &e) {
logConversionError(
e, __func__,
(setting.GetID() + "-" + setting.GetNestedID()).c_str(),
value.c_str());
return;
} catch (const std::out_of_range &e) {
logConversionError(
e, __func__,
(setting.GetID() + "-" + setting.GetNestedID()).c_str(),
value.c_str());
return;
}
struct vec2 *target = nullptr;
if (setting.GetNestedID() == "scale") {
target = &info.scale;
} else if (setting.GetNestedID() == "bounds") {
target = &info.bounds;
}
if (setting.GetID() == "x") {
target->x = val;
} else if (setting.GetID() == "y") {
target->y = val;
}
}
static void handleRot(struct obs_transform_info &info, const std::string &value)
{
float val = 0;
try {
val = std::stof(value);
} catch (const std::invalid_argument &e) {
logConversionError(e, __func__, "rot", value.c_str());
return;
} catch (const std::out_of_range &e) {
logConversionError(e, __func__, "rot", value.c_str());
return;
}
info.rot = val;
}
void SetTransformSetting(obs_scene_item *source,
const TransformSetting &setting,
const std::string &value)
{
auto id = setting.GetID();
struct obs_transform_info info = {};
struct obs_sceneitem_crop crop = {};
obs_sceneitem_get_info(source, &info);
obs_sceneitem_get_crop(source, &crop);
if (id == "alignment") {
try {
auto val = std::stoul(value);
info.alignment = val;
} catch (const std::invalid_argument &e) {
logConversionError(e, __func__, id.c_str(),
value.c_str());
} catch (const std::out_of_range &e) {
logConversionError(e, __func__, id.c_str(),
value.c_str());
}
} else if (id == "left" || id == "top" || id == "right" ||
id == "bottom") {
handleCrop(crop, id, value);
} else if (id == "bounds_alignment") {
uint32_t val = 0;
try {
val = std::stoul(value);
info.bounds_alignment = val;
} catch (const std::invalid_argument &e) {
logConversionError(e, __func__, id.c_str(),
value.c_str());
} catch (const std::out_of_range &e) {
logConversionError(e, __func__, id.c_str(),
value.c_str());
}
} else if (id == "bounds_type") {
try {
obs_bounds_type val =
static_cast<obs_bounds_type>(std::stoul(value));
info.bounds_type = val;
} catch (const std::invalid_argument &e) {
logConversionError(e, __func__, id.c_str(),
value.c_str());
} catch (const std::out_of_range &e) {
logConversionError(e, __func__, id.c_str(),
value.c_str());
}
} else if (id == "x" || id == "y") {
handlePosOrScaleOrBounds(info, setting, value);
} else if (id == "width" || id == "height") {
float val = 0;
try {
val = std::stof(value);
} catch (const std::invalid_argument &e) {
logConversionError(
e, __func__,
(setting.GetID() + "-" + setting.GetNestedID())
.c_str(),
value.c_str());
return;
} catch (const std::out_of_range &e) {
logConversionError(
e, __func__,
(setting.GetID() + "-" + setting.GetNestedID())
.c_str(),
value.c_str());
return;
}
auto source2 = obs_sceneitem_get_source(source);
if (setting.GetID() == "width") {
val = val / obs_source_get_width(source2);
handlePosOrScaleOrBounds(info,
TransformSetting("x", "scale"),
std::to_string(val));
} else if (setting.GetID() == "height") {
val = val / obs_source_get_height(source2);
handlePosOrScaleOrBounds(info,
TransformSetting("y", "scale"),
std::to_string(val));
}
} else if (id == "rot") {
handleRot(info, value);
}
obs_sceneitem_defer_update_begin(source);
obs_sceneitem_set_info(source, &info);
obs_sceneitem_set_crop(source, &crop);
obs_sceneitem_defer_update_end(source);
obs_sceneitem_force_update_transform(source);
}
TransformSettingSelection::TransformSettingSelection(QWidget *parent)
: QWidget(parent),
_settings(new FilterComboBox(
this, obs_module_text("AdvSceneSwitcher.setting.select")))
{
_settings->setSizeAdjustPolicy(QComboBox::AdjustToContents);
_settings->setMaximumWidth(350);
QWidget::connect(_settings, SIGNAL(currentIndexChanged(int)), this,
SLOT(SelectionIdxChanged(int)));
auto layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(_settings);
setLayout(layout);
}
void TransformSettingSelection::SetSource(obs_scene_item *source)
{
_settings->clear();
Populate(source);
}
void TransformSettingSelection::SetSetting(const TransformSetting &setting)
{
QVariant variant;
variant.setValue(setting);
_settings->setCurrentIndex(_settings->findData(variant));
}
void TransformSettingSelection::SelectionIdxChanged(int idx)
{
if (idx == -1) {
return;
}
auto setting = _settings->itemData(idx).value<TransformSetting>();
emit SelectionChanged(setting);
}
static QString mapIdToLocale(const TransformSetting &setting)
{
static const std::unordered_map<std::string_view, std::string_view>
idMap = {
{"alignment",
"AdvSceneSwitcher.setting.transform.alignment"},
{"bottom",
"AdvSceneSwitcher.setting.transform.cropBottom"},
{"left", "AdvSceneSwitcher.setting.transform.cropLeft"},
{"right",
"AdvSceneSwitcher.setting.transform.cropRight"},
{"top", "AdvSceneSwitcher.setting.transform.cropTop"},
{"bounds_alignment",
"AdvSceneSwitcher.setting.transform.boundingBoxAlign"},
{"bounds_type",
"AdvSceneSwitcher.setting.transform.boundingBoxType"},
{"rot", "AdvSceneSwitcher.setting.transform.rotation"},
{"height", "AdvSceneSwitcher.setting.transform.height"},
{"width", "AdvSceneSwitcher.setting.transform.width"},
{"x", "x"},
{"y", "y"},
};
static const std::unordered_map<std::string_view, std::string_view>
nestedIdMap = {
{"bounds",
"AdvSceneSwitcher.setting.transform.boundingBoxSize"},
{"pos", "AdvSceneSwitcher.setting.transform.position"},
{"scale", "AdvSceneSwitcher.setting.transform.scale"},
{"size", "AdvSceneSwitcher.setting.transform.size"},
};
auto idNameIt = idMap.find(setting.GetID());
QString name = idNameIt == idMap.end()
? QString::fromStdString(setting.GetID())
: obs_module_text(idNameIt->second.data());
if (!setting.GetNestedID().empty()) {
auto idNameIt = nestedIdMap.find(setting.GetNestedID());
const auto nestedName =
idNameIt == nestedIdMap.end()
? QString::fromStdString(setting.GetID())
: obs_module_text(idNameIt->second.data());
name = "[" + nestedName + "] " + name;
}
return name;
}
void TransformSettingSelection::Populate(obs_scene_item *source)
{
auto settings = GetSoruceTransformSettings(source);
for (const auto &setting : settings) {
QVariant variant;
variant.setValue(setting);
auto name = mapIdToLocale(setting);
_settings->addItem(name, variant);
}
_settings->setCurrentIndex(-1);
TransformSetting emptySetting;
emit SelectionChanged(emptySetting);
adjustSize();
updateGeometry();
}
} // namespace advss

View File

@@ -0,0 +1,58 @@
#pragma once
#include "filter-combo-box.hpp"
#include <obs.hpp>
#include <optional>
#include <string>
#include <vector>
#include <QWidget>
#include <QLabel>
namespace advss {
class TransformSetting {
public:
TransformSetting() = default;
TransformSetting(const std::string &id, const std::string &nestedId);
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetID() const { return _id; }
std::string GetNestedID() const { return _nestedId; }
EXPORT bool operator==(const TransformSetting &other) const;
private:
std::string _id = "";
std::string _nestedId = "";
friend class TransformSettingSelection;
};
std::vector<TransformSetting>
GetSoruceTransformSettings(obs_scene_item *source);
std::optional<std::string>
GetTransformSettingValue(obs_scene_item *source,
const TransformSetting &setting);
void SetTransformSetting(obs_scene_item *source,
const TransformSetting &setting,
const std::string &value);
class TransformSettingSelection : public QWidget {
Q_OBJECT
public:
TransformSettingSelection(QWidget *parent = nullptr);
void SetSource(obs_scene_item *);
void SetSetting(const TransformSetting &);
private slots:
void SelectionIdxChanged(int);
signals:
void SelectionChanged(const TransformSetting &);
private:
void Populate(obs_scene_item *);
FilterComboBox *_settings;
};
} // namespace advss

View File

@@ -70,6 +70,9 @@ static bool setupDeviceObservers()
} catch (const libremidi::driver_error &error) {
blog(LOG_WARNING, "Failed to setup midi device observers: %s",
error.what());
} catch (const std::exception &error) {
blog(LOG_WARNING, "Failed to setup midi device observers: %s",
error.what());
}
return true;
}

View File

@@ -285,7 +285,7 @@ setTempVarsHelper(obs_data_t *data,
setTempVarsHelper(jsonStr, setVar);
}
bool MacroConditionTwitch::CheckChannelGenericEvents(TwitchToken &token)
bool MacroConditionTwitch::CheckChannelGenericEvents()
{
if (!_eventBuffer) {
return false;
@@ -315,7 +315,7 @@ bool MacroConditionTwitch::CheckChannelGenericEvents(TwitchToken &token)
return false;
}
bool MacroConditionTwitch::CheckChannelLiveEvents(TwitchToken &token)
bool MacroConditionTwitch::CheckChannelLiveEvents()
{
if (!_eventBuffer) {
return false;
@@ -541,13 +541,13 @@ bool MacroConditionTwitch::CheckCondition()
case Condition::USER_UNBAN_EVENT:
case Condition::USER_MODERATOR_ADDITION_EVENT:
case Condition::USER_MODERATOR_DELETION_EVENT:
return CheckChannelGenericEvents(*token);
return CheckChannelGenericEvents();
case Condition::STREAM_ONLINE_LIVE_EVENT:
case Condition::STREAM_ONLINE_PLAYLIST_EVENT:
case Condition::STREAM_ONLINE_WATCHPARTY_EVENT:
case Condition::STREAM_ONLINE_PREMIERE_EVENT:
case Condition::STREAM_ONLINE_RERUN_EVENT:
return CheckChannelLiveEvents(*token);
return CheckChannelLiveEvents();
case Condition::LIVE_POLLING: {
auto info = _channel.GetLiveInfo(*token);
if (!info) {

View File

@@ -107,8 +107,8 @@ public:
bool _clearBufferOnMatch = false;
private:
bool CheckChannelGenericEvents(TwitchToken &token);
bool CheckChannelLiveEvents(TwitchToken &token);
bool CheckChannelGenericEvents();
bool CheckChannelLiveEvents();
bool CheckChatMessages(TwitchToken &token);
void RegisterEventSubscription();

View File

@@ -103,7 +103,7 @@ static std::vector<USBDeviceInfo> pollUSBDevices()
return result;
}
static int hotplugCallback(struct libusb_context *ctx,
static int hotplugCallback(struct libusb_context *,
struct libusb_device *device,
libusb_hotplug_event event, void *user_data)
{

View File

@@ -122,6 +122,12 @@ bool MacroConditionVideo::CheckShouldBeSkipped()
return false;
}
MacroConditionVideo::MacroConditionVideo(Macro *m)
: QObject(),
MacroCondition(m, true)
{
}
bool MacroConditionVideo::CheckCondition()
{
if (!_video.ValidSelection()) {
@@ -133,6 +139,10 @@ bool MacroConditionVideo::CheckCondition()
return _lastMatchResult;
}
if (!FileInputIsUpToDate()) {
LoadImageFromFile();
}
if (_blockUntilScreenshotDone) {
GetScreenshot(true);
}
@@ -231,7 +241,10 @@ void MacroConditionVideo::GetScreenshot(bool blocking)
bool MacroConditionVideo::LoadImageFromFile()
{
if (!_matchImage.load(QString::fromStdString(_file))) {
const QFileInfo info(QString::fromStdString(_file));
_loadedFileLastModified = info.lastModified();
_loadedFile = _file;
if (!_matchImage.load(info.absoluteFilePath())) {
blog(LOG_WARNING, "Cannot load image data from file '%s'",
_file.c_str());
(&_matchImage)->~QImage();
@@ -244,6 +257,8 @@ bool MacroConditionVideo::LoadImageFromFile()
_matchImage.convertToFormat(QImage::Format::Format_RGBA8888);
_patternMatchParameters.image = _matchImage;
_patternImageData = CreatePatternData(_matchImage);
emit InputFileChanged();
return true;
}
@@ -291,6 +306,16 @@ bool MacroConditionVideo::ScreenshotContainsPattern()
return count > 0;
}
bool MacroConditionVideo::FileInputIsUpToDate() const
{
if (!requiresFileInput(_condition)) {
return true;
}
const QFileInfo info(QString::fromStdString(_file));
return (_loadedFileLastModified == info.lastModified()) &&
(_file == _loadedFile);
}
bool MacroConditionVideo::OutputChanged()
{
if (!_patternMatchParameters.useForChangedCheck) {
@@ -1165,6 +1190,13 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
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,
[this]() {
UpdatePreviewTooltip();
_previewDialog.PatternMatchParametersChanged(
_entryData->_patternMatchParameters);
});
populateVideoInputSelection(_videoInputTypes);
populateConditionSelection(_condition);

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