Compare commits

...

34 Commits

Author SHA1 Message Date
WarmUpTill
1affe9dce3 Fix macro segment list entries not resizing properly 2025-05-27 22:37:46 +02:00
WarmUpTill
406e3c1855 Cleanup
* Add variable support to process condition
* Display unresolved variable value in Window action
* Add tooltips to indicate that variables are supported
2025-05-27 22:37:46 +02:00
WarmUpTill
c05dd40c4c Add option to change various OBS video settings 2025-05-27 22:37:46 +02:00
WarmUpTill
9a86ecac42 Add option to disable widget caching 2025-05-27 22:37:46 +02:00
WarmUpTill
721a786e79 Rework preview dialog to improve performance
Object detection and OCR models were constantly being re-initialized
each frame due to copy in the signal / slot handling
2025-05-27 22:37:46 +02:00
WarmUpTill
32d29875ed Move responsibility of cascade init to ObjDetectParameters 2025-05-27 22:37:46 +02:00
warmuptill
0e5f56b562 Add tesseract config file support 2025-05-27 22:37:46 +02:00
WarmUpTill
5490fabf92 Add hotkey to create new macro 2025-05-27 22:37:46 +02:00
WarmUpTill
5e3ab19940 Add ProfileSelectionWidget
Refreshes list of profiles when widget becomes visible to support widget
caching.
2025-05-27 22:37:46 +02:00
WarmUpTill
1c94a1ab44 Add X and Y position tempvars to cursor condition 2025-05-27 22:37:46 +02:00
WarmUpTill
34baa56134 Update process list on visibility change
This is done to support widget caching
2025-05-27 22:37:46 +02:00
WarmUpTill
5ce4171773 Add MonitorSelectionWidget
Refreshes list of monitors when widget becomes visible to support widget
caching
2025-05-27 22:37:46 +02:00
WarmUpTill
c281c6db83 Add WindowSelectionWidget
Refreshes list of windows when widget becomes visible to support widget
caching
2025-05-27 22:37:46 +02:00
WarmUpTill
c43439ee64 Add option to keep selection when FilterComboBox entry doesn't exist 2025-05-27 22:37:46 +02:00
WarmUpTill
a84731b8fe Rework TransitionSelectionWidget to support widget caching 2025-05-27 22:37:46 +02:00
WarmUpTill
61fbff5821 Fix SetSourceSetting() not changing settings if the default value is set
This won't help in scenarios in which even a default value does not
exist, e.g. "text" for the text sources, as here it is unclear which
value type to use
2025-05-27 22:37:46 +02:00
WarmUpTill
1381654fed Add Add support for widget caching to FilterSelectionWidget 2025-05-27 22:37:46 +02:00
WarmUpTill
9805601c07 Add support for widget caching to SceneItemSelectionWidget 2025-05-27 22:37:46 +02:00
WarmUpTill
fcf57ee031 Add support for widget caching to SceneSelectionWidget 2025-05-27 22:37:46 +02:00
WarmUpTill
ba3e87a761 Rework SourceSelectionWidget to support widget caching 2025-05-27 22:37:46 +02:00
WarmUpTill
3dd3f576c3 Add hint that parallel condition check feature is experimental 2025-05-27 22:37:46 +02:00
WarmUpTill
7403a18e96 Cleanup 2025-05-27 22:37:46 +02:00
warmuptill
56494480ba Cache macro segment widgets for each macro 2025-05-27 22:37:46 +02:00
warmuptill
cdc5d16e95 Allow changing Tesseract model directory
This will prevent custom models being deleted when installing a new
version of the plugin, as the plugin's data directory might get wiped on
some operating systems.
2025-05-27 22:37:46 +02:00
warmuptill
27aed79305 Enable building with OCR support in Ubuntu environment 2025-05-27 22:37:46 +02:00
WarmUpTill
7ec95e33eb Hide outdated video condition options
The "throttle" and "reduce matching latency" optiohns were introduced
before "short circuit evaluation" was available and are now outdated.

Both don't behave as expected with this option enabled.
The throttling effect can be better achieved with an additional "Timer"
condition.
2025-05-27 22:37:46 +02:00
WarmUpTill
98260b25a1 Add option to toggle the pause state of a macro 2025-05-27 22:37:46 +02:00
WarmUpTill
45b37de9f7 Add MQTT connection tab 2025-05-27 20:23:07 +02:00
WarmUpTill
027a3e9074 Add MQTT condition and action 2025-05-27 20:23:07 +02:00
WarmUpTill
cab50e0922 Add MQTT helpers 2025-05-27 20:23:07 +02:00
WarmUpTill
88514e209d CI: build paho.mqtt.cpp libs 2025-05-27 20:23:07 +02:00
WarmUpTill
4ed7f13b72 Add paho.mqtt.cpp dependency 2025-05-27 20:23:07 +02:00
WarmUpTill
0fb11ac274 Add copy / move to MessageDispatcher 2025-05-27 20:23:07 +02:00
WarmUpTill
942933290a Remove unnecessary include 2025-05-27 20:23:07 +02:00
97 changed files with 4046 additions and 765 deletions

View File

@@ -10,4 +10,6 @@ package 'libxss-dev'
package 'libopencv-dev' package 'libopencv-dev'
package 'libtesseract-dev' package 'libtesseract-dev'
package 'libproc2-dev' package 'libproc2-dev'
package 'libusb-1.0-0-dev' package 'libusb-1.0-0-dev'
package 'libpaho-mqttpp-dev'
package 'libpaho-mqtt-dev'

View File

@@ -478,6 +478,33 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
unset MACOSX_DEPLOYMENT_TARGET unset MACOSX_DEPLOYMENT_TARGET
popd popd
local mqtt_dir="${project_root}/deps/paho.mqtt.cpp"
local mqtt_build_dir="${mqtt_dir}/build_${target##*-}"
local -a mqtt_cmake_args=(
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
-DCMAKE_PREFIX_PATH="${advss_dep_path};${_plugin_deps}"
-DCMAKE_INSTALL_PREFIX="${advss_dep_path}"
-DPAHO_BUILD_SHARED=OFF
-DPAHO_BUILD_STATIC=ON
-DPAHO_WITH_MQTT_C=ON
-DOPENSSL_ROOT_DIR="${advss_dep_path}"
-DPAHO_WITH_SSL=OFF # TODO: figure out linking issues with openssl
)
pushd ${mqtt_dir}
log_info "Configure paho.mqtt.cpp ..."
cmake -S . -B ${mqtt_build_dir} ${mqtt_cmake_args}
log_info "Building paho.mqtt.cpp ..."
cmake --build ${mqtt_build_dir} --config Release
log_info "Installing paho.mqtt.cpp ..."
cmake --install ${mqtt_build_dir} --prefix "${advss_dep_path}" --config Release
popd
;; ;;
linux) linux)
# Nothing to do for now # Nothing to do for now

View File

@@ -219,6 +219,37 @@ function Build {
} else { } else {
Log-Information "Failed to locate msbuild.exe - skipping libusb build" Log-Information "Failed to locate msbuild.exe - skipping libusb build"
} }
Push-Location -Stack BuildMqttTemp
Ensure-Location $ProjectRoot
$MqttPath = "${ProjectRoot}/deps/paho.mqtt.cpp"
$MqttBuildPath = "${MqttPath}/build"
# Explicitly disable PkgConfig and tiff as it will lead build errors
$MqttCmakeArgs = @(
"-DCMAKE_BUILD_TYPE=${Configuration}"
"-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}"
"-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}"
"-DPAHO_WITH_MQTT_C=ON"
)
Log-Information "Configuring paho.mqtt.cpp..."
Invoke-External cmake -S ${MqttPath} -B ${MqttBuildPath} @MqttCmakeArgs
$MqttCmakeArgs = @(
'--config', "${Configuration}"
)
if ( $VerbosePreference -eq 'Continue' ) {
$MqttCmakeArgs += ('--verbose')
}
Log-Information "Building paho.mqtt.cpp..."
Invoke-External cmake --build "${MqttBuildPath}" @MqttCmakeArgs
Log-Information "Install paho.mqtt.cpp..."
Invoke-External cmake --install "${MqttBuildPath}" --prefix "${ADVSSDepPath}" @MqttCmakeArgs
} }
Build Build

3
.gitmodules vendored
View File

@@ -34,3 +34,6 @@
[submodule "deps/jsoncons"] [submodule "deps/jsoncons"]
path = deps/jsoncons path = deps/jsoncons
url = https://github.com/danielaparker/jsoncons.git url = https://github.com/danielaparker/jsoncons.git
[submodule "deps/paho.mqtt.cpp"]
path = deps/paho.mqtt.cpp
url = https://github.com/eclipse-paho/paho.mqtt.cpp.git

View File

@@ -194,12 +194,12 @@ AdvSceneSwitcher.condition.video.modelLoadFail="Modelldaten konnten nicht gelade
AdvSceneSwitcher.condition.video.type.main="OBS's Haupt-Ausgabe" AdvSceneSwitcher.condition.video.type.main="OBS's Haupt-Ausgabe"
AdvSceneSwitcher.condition.video.type.source="Quelle" AdvSceneSwitcher.condition.video.type.source="Quelle"
AdvSceneSwitcher.condition.video.type.scene="Szene" AdvSceneSwitcher.condition.video.type.scene="Szene"
AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" AdvSceneSwitcher.condition.video.layout="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}"
AdvSceneSwitcher.condition.video.entry.modelPath="Modelldaten (Haar-Kaskaden-Klassifikator): {{modelDataPath}}" AdvSceneSwitcher.condition.video.layout.modelPath="Modelldaten (Haar-Kaskaden-Klassifikator): {{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimale Anzahl von Nachbarn: {{minNeighbors}}" AdvSceneSwitcher.condition.video.layout.minNeighbor="Minimale Anzahl von Nachbarn: {{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduzieren Sie die CPU-Belastung, indem Sie die Prüfung nur alle {{throttleCount}} Millisekunden" AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Reduzieren Sie die CPU-Belastung, indem Sie die Prüfung nur alle {{throttleCount}} Millisekunden"
AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Kontrolle nur im Bereich durchführen" AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Kontrolle nur im Bereich durchführen"
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
AdvSceneSwitcher.condition.video.minSize="Minimale Größe:" AdvSceneSwitcher.condition.video.minSize="Minimale Größe:"
AdvSceneSwitcher.condition.video.maxSize="Maximale Größe:" AdvSceneSwitcher.condition.video.maxSize="Maximale Größe:"
AdvSceneSwitcher.condition.video.selectArea="Bereich auswählen" AdvSceneSwitcher.condition.video.selectArea="Bereich auswählen"
@@ -752,9 +752,9 @@ AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="Starte/Stoppe den Erweite
AdvSceneSwitcher.hotkey.macro.pause="Pausiere Makro %1" AdvSceneSwitcher.hotkey.macro.pause="Pausiere Makro %1"
AdvSceneSwitcher.hotkey.macro.unpause="Makro %1 nicht mehr pausieren" AdvSceneSwitcher.hotkey.macro.unpause="Makro %1 nicht mehr pausieren"
AdvSceneSwitcher.hotkey.macro.togglePause="Pause von Makro %1 togglen" AdvSceneSwitcher.hotkey.macro.togglePause="Pause von Makro %1 togglen"
AdvSceneSwitcher.hotkey.upMacroSegmentHotkey="Makro-Segmentauswahl nach oben verschieben" AdvSceneSwitcher.hotkey.macro.segment.up="Makro-Segmentauswahl nach oben verschieben"
AdvSceneSwitcher.hotkey.downMacroSegmentHotkey="Makro-Segmentauswahl nach unten verschieben" AdvSceneSwitcher.hotkey.macro.segment.down="Makro-Segmentauswahl nach unten verschieben"
AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey="Ausgewähltes Makro-Segment entfernen" AdvSceneSwitcher.hotkey.macro.segment.remove="Ausgewähltes Makro-Segment entfernen"
AdvSceneSwitcher.askBackup="Neue Version des Erweiterten Automatischen Szenenwechslers wurde erkannt.\nSoll ein Backup der alten Einstellungen angelegt werden?" AdvSceneSwitcher.askBackup="Neue Version des Erweiterten Automatischen Szenenwechslers wurde erkannt.\nSoll ein Backup der alten Einstellungen angelegt werden?"
AdvSceneSwitcher.askForMacro="Makro auswählen {{macroSelection}}" AdvSceneSwitcher.askForMacro="Makro auswählen {{macroSelection}}"

View File

@@ -42,6 +42,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailure="Display warni
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage="<html><body>Loading of the following plugin libraries was unsuccessful, which could result in some Advanced Scene Switcher functions not being available:%1Check the OBS logs for details.<br>This message can be disabled on the General tab.</body></html>" AdvSceneSwitcher.generalTab.generalBehavior.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.warnCorruptedInstallMessage="The plugin installation seems to be corrupted and might crash!\nPlease make sure the plugin was installed correctly!"
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="Hide tabs which can be represented via macros" AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="Hide tabs which can be represented via macros"
AdvSceneSwitcher.generalTab.generalBehavior.disableMacroWidgetCache="Disable macro widget caching"
AdvSceneSwitcher.generalTab.matchBehavior="Match behavior" AdvSceneSwitcher.generalTab.matchBehavior="Match behavior"
AdvSceneSwitcher.generalTab.priority="Priority" AdvSceneSwitcher.generalTab.priority="Priority"
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)" AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
@@ -132,6 +133,18 @@ AdvSceneSwitcher.twitchConnectionTab.no="No"
AdvSceneSwitcher.twitchConnectionTab.removeSingleConnectionPopup.text="Are you sure you want to remove \"%1\"?" 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?" AdvSceneSwitcher.twitchConnectionTab.removeMultipleConnectionsPopup.text="Are you sure you want to remove %1 Twitch connections?"
# MQTT Connections Tab
AdvSceneSwitcher.mqttConnectionTab.title="MQTT Connections"
AdvSceneSwitcher.mqttConnectionTab.help="MQTT connections can be used to communicate with other devices.\n\nClick on the highlighted plus symbol to add a new connection."
AdvSceneSwitcher.mqttConnectionTab.mqttonnectionAddButton.tooltip="Add new MQTT connection"
AdvSceneSwitcher.mqttConnectionTab.mqttConnectionRemoveButton.tooltip="Remove selected MQTT connections"
AdvSceneSwitcher.mqttConnectionTab.name.header="Name"
AdvSceneSwitcher.mqttConnectionTab.address.header="Address"
AdvSceneSwitcher.mqttConnectionTab.topics.header="Topic subscription count"
AdvSceneSwitcher.mqttConnectionTab.status.header="Status"
AdvSceneSwitcher.mqttConnectionTab.removeSingleConnectionPopup.text="Are you sure you want to remove \"%1\"?"
AdvSceneSwitcher.mqttConnectionTab.removeMultipleConnectionsPopup.text="Are you sure you want to remove %1 connections?"
# Macro Tab # Macro Tab
AdvSceneSwitcher.macroTab.title="Macro" AdvSceneSwitcher.macroTab.title="Macro"
AdvSceneSwitcher.macroTab.macros="Macros" AdvSceneSwitcher.macroTab.macros="Macros"
@@ -203,7 +216,7 @@ AdvSceneSwitcher.macroTab.newMacroCheckInParallel="Evaluate conditions of new ma
AdvSceneSwitcher.macroTab.checkInParallel.tooltip="If this option is not enabled, this macro's conditions will be evaluated after the preceeding macro's condition check has completed.\nSo, if there are three macros - Macro A, Macro B, and Macro C - in the macro list the order the conditions in are evaluated is:\n • Macro A's conditions\n • Macro B's conditions\n • Macro C's conditions\nIf this option is enabled, the order might differ.\nEnabling this option can be useful for scenarios in which you have multiple long running condition checks, which you don't want to result in \"blocking\" other macros." AdvSceneSwitcher.macroTab.checkInParallel.tooltip="If this option is not enabled, this macro's conditions will be evaluated after the preceeding macro's condition check has completed.\nSo, if there are three macros - Macro A, Macro B, and Macro C - in the macro list the order the conditions in are evaluated is:\n • Macro A's conditions\n • Macro B's conditions\n • Macro C's conditions\nIf this option is enabled, the order might differ.\nEnabling this option can be useful for scenarios in which you have multiple long running condition checks, which you don't want to result in \"blocking\" other macros."
AdvSceneSwitcher.macroTab.saveSettingsOnMacroChange="Save settings when switching between macros" AdvSceneSwitcher.macroTab.saveSettingsOnMacroChange="Save settings when switching between macros"
AdvSceneSwitcher.macroTab.saveSettingsOnMacroChange.tooltip="Saving the settings can be an expensive operation if you are working with a very large scene collection.\nIn this case, it might make sense to disable this option." AdvSceneSwitcher.macroTab.saveSettingsOnMacroChange.tooltip="Saving the settings can be an expensive operation if you are working with a very large scene collection.\nIn this case, it might make sense to disable this option."
AdvSceneSwitcher.macroTab.currentCheckInParallel="Evaluate conditions of selected macro in parallel to other macros" AdvSceneSwitcher.macroTab.currentCheckInParallel="Evaluate conditions of selected macro in parallel to other macros (Experimental)"
AdvSceneSwitcher.macroTab.currentRegisterHotkeys="Register hotkeys to control the pause state of selected macro" AdvSceneSwitcher.macroTab.currentRegisterHotkeys="Register hotkeys to control the pause state of selected macro"
AdvSceneSwitcher.macroTab.currentUseShortCircuitEvaluation="Enable short circuit evaluation of macro conditions for currently selected macro" AdvSceneSwitcher.macroTab.currentUseShortCircuitEvaluation="Enable short circuit evaluation of macro conditions for currently selected macro"
AdvSceneSwitcher.macroTab.shortCircuit.tooltip="Enabling short circuit evaluation might improve the performance, as some condition checks are skipped, if the overall macro cannot be evaluated to \"true\" anymore.\nHowever, please note that condition checks, which are skipped over, will also not update their duration modifier checks." AdvSceneSwitcher.macroTab.shortCircuit.tooltip="Enabling short circuit evaluation might improve the performance, as some condition checks are skipped, if the overall macro cannot be evaluated to \"true\" anymore.\nHowever, please note that condition checks, which are skipped over, will also not update their duration modifier checks."
@@ -311,7 +324,7 @@ AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="During transi
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}{{regex}}" AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}{{regex}}"
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}" AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
AdvSceneSwitcher.condition.window="Window" AdvSceneSwitcher.condition.window="Window"
AdvSceneSwitcher.condition.window.entry.window="{{checkTitle}}Window title matches{{windowRegex}}{{windows}}" AdvSceneSwitcher.condition.window.entry.window="{{checkTitle}}Window title matches{{windows}}{{windowRegex}}"
AdvSceneSwitcher.condition.window.entry.fullscreen="{{fullscreen}}Window is fullscreen" AdvSceneSwitcher.condition.window.entry.fullscreen="{{fullscreen}}Window is fullscreen"
AdvSceneSwitcher.condition.window.entry.maximized="{{maximized}}Window is maximized" AdvSceneSwitcher.condition.window.entry.maximized="{{maximized}}Window is maximized"
AdvSceneSwitcher.condition.window.entry.focused="{{focused}}Window is focused" AdvSceneSwitcher.condition.window.entry.focused="{{focused}}Window is focused"
@@ -380,6 +393,13 @@ AdvSceneSwitcher.condition.video.patternMatchSuccessFullImage="Full image matche
AdvSceneSwitcher.condition.video.objectMatchFail="Object was not found!" AdvSceneSwitcher.condition.video.objectMatchFail="Object was not found!"
AdvSceneSwitcher.condition.video.objectMatchSuccess="Object is highlighted in red" AdvSceneSwitcher.condition.video.objectMatchSuccess="Object is highlighted in red"
AdvSceneSwitcher.condition.video.ocrMatchSuccess="Detected text:\n\n%1" AdvSceneSwitcher.condition.video.ocrMatchSuccess="Detected text:\n\n%1"
AdvSceneSwitcher.condition.video.ocrMatchFail="Could not detect any text!"
AdvSceneSwitcher.condition.video.ocrUseConfigFile="Use custom tesseract config file"
AdvSceneSwitcher.condition.video.ocrOpenConfigFile="Open"
AdvSceneSwitcher.condition.video.ocrOpenConfig.createFailed="Could not create the config file!"
AdvSceneSwitcher.condition.video.ocrOpenConfig.openFailed="Could not open the config file!"
AdvSceneSwitcher.condition.video.ocrConfigReload="Reload configuration file"
AdvSceneSwitcher.condition.video.ocrConfigHint="Tesseract config files consist of lines with parameter-value pairs (space separated).\nFor example:\n\ntessedit_char_blacklist\t\t\t\t\"abc\"\nlanguage_model_penalty_non_dict_word\t0"
AdvSceneSwitcher.condition.video.modelLoadFail="Model data could not be loaded!" AdvSceneSwitcher.condition.video.modelLoadFail="Model data could not be loaded!"
AdvSceneSwitcher.condition.video.selectColor="Select Color" AdvSceneSwitcher.condition.video.selectColor="Select Color"
AdvSceneSwitcher.condition.video.ocrMode.singleColumn="Single column of text of variable sizes" AdvSceneSwitcher.condition.video.ocrMode.singleColumn="Single column of text of variable sizes"
@@ -399,16 +419,18 @@ AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription="How similar
AdvSceneSwitcher.condition.video.type.main="OBS's main output" AdvSceneSwitcher.condition.video.type.main="OBS's main output"
AdvSceneSwitcher.condition.video.type.source="Source" AdvSceneSwitcher.condition.video.type.source="Source"
AdvSceneSwitcher.condition.video.type.scene="Scene" AdvSceneSwitcher.condition.video.type.scene="Scene"
AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" AdvSceneSwitcher.condition.video.layout="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}"
AdvSceneSwitcher.condition.video.entry.modelPath="Model data (haar cascade classifier):{{modelDataPath}}" AdvSceneSwitcher.condition.video.layout.modelPath="Model data (haar cascade classifier):{{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum neighbors:{{minNeighbors}}" AdvSceneSwitcher.condition.video.layout.minNeighbor="Minimum neighbors:{{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU load by performing check only every{{throttleCount}}milliseconds" AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Reduce CPU load by performing check only every{{throttleCount}}milliseconds"
AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Perform check only in area" AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Perform check only in area"
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
AdvSceneSwitcher.condition.video.entry.orcColorPick="Check for text color:{{textColor}}{{selectColor}}" AdvSceneSwitcher.condition.video.layout.ocrColorPick="Check for text color:{{textColor}}{{selectColor}}"
AdvSceneSwitcher.condition.video.entry.orcTextType="Check for text type:{{textType}}" AdvSceneSwitcher.condition.video.layout.ocrTextType="Check for text type:{{textType}}"
AdvSceneSwitcher.condition.video.entry.orcLanguage="Check for language:{{languageCode}}" AdvSceneSwitcher.condition.video.layout.ocrBaseDir="Tesseract base directory:{{tesseractBaseDir}}"
AdvSceneSwitcher.condition.video.entry.color="Check for color:{{color}}{{selectColor}}" AdvSceneSwitcher.condition.video.layout.ocrLanguage="Check for language:{{languageCode}}"
AdvSceneSwitcher.condition.video.layout.ocrConfig="Config file:{{configFile}}{{openConfigFile}}{{reloadConfig}}{{configFileHint}}"
AdvSceneSwitcher.condition.video.layout.color="Check for color:{{color}}{{selectColor}}"
AdvSceneSwitcher.condition.video.minSize="Minimum size:" AdvSceneSwitcher.condition.video.minSize="Minimum size:"
AdvSceneSwitcher.condition.video.maxSize="Maximum size:" AdvSceneSwitcher.condition.video.maxSize="Maximum size:"
AdvSceneSwitcher.condition.video.selectArea="Select area" AdvSceneSwitcher.condition.video.selectArea="Select area"
@@ -774,9 +796,11 @@ AdvSceneSwitcher.condition.streamDeck.stopListen="Stop listening"
AdvSceneSwitcher.condition.streamDeck.pluginDownload="<html><head/><body><p>The Stream Deck plugin can be found <a href=\"https://github.com/WarmUpTill/advanced-scene-switcher-streamdeck-plugin/releases\"><span style=\" text-decoration: underline; color:#268bd2;\">here on GitHub</span></a>.</p></body></html>" AdvSceneSwitcher.condition.streamDeck.pluginDownload="<html><head/><body><p>The Stream Deck plugin can be found <a href=\"https://github.com/WarmUpTill/advanced-scene-switcher-streamdeck-plugin/releases\"><span style=\" text-decoration: underline; color:#268bd2;\">here on GitHub</span></a>.</p></body></html>"
AdvSceneSwitcher.condition.gameCapture="Game capture" AdvSceneSwitcher.condition.gameCapture="Game capture"
AdvSceneSwitcher.condition.gameCapture.entry="{{sources}}hooked a game." AdvSceneSwitcher.condition.gameCapture.entry="{{sources}}hooked a game."
AdvSceneSwitcher.condition.screenshot="Screenshot" AdvSceneSwitcher.condition.screenshot="Screenshot"
AdvSceneSwitcher.condition.screenshot.entry="A screenshot was taken" AdvSceneSwitcher.condition.screenshot.entry="A screenshot was taken"
AdvSceneSwitcher.condition.mqtt="MQTT"
AdvSceneSwitcher.condition.mqtt.layout.match="Message was received from{{connection}} which matches{{regex}}:"
AdvSceneSwitcher.condition.mqtt.layout.listen="Set message selection to incoming message:{{listenButton}}"
# Macro Actions # Macro Actions
AdvSceneSwitcher.action.unknown="Unknown action" AdvSceneSwitcher.action.unknown="Unknown action"
@@ -911,6 +935,7 @@ AdvSceneSwitcher.action.media.seek.percentage.label="Percentage of total duratio
AdvSceneSwitcher.action.macro="Macro" AdvSceneSwitcher.action.macro="Macro"
AdvSceneSwitcher.action.macro.type.pause="Pause" AdvSceneSwitcher.action.macro.type.pause="Pause"
AdvSceneSwitcher.action.macro.type.unpause="Unpause" AdvSceneSwitcher.action.macro.type.unpause="Unpause"
AdvSceneSwitcher.action.macro.type.togglePause="Toggle pause"
AdvSceneSwitcher.action.macro.type.resetCounter="Reset counter" AdvSceneSwitcher.action.macro.type.resetCounter="Reset counter"
AdvSceneSwitcher.action.macro.type.run="Run macro" AdvSceneSwitcher.action.macro.type.run="Run macro"
AdvSceneSwitcher.action.macro.type.run.conditions.ignore="Do not consider condition state" AdvSceneSwitcher.action.macro.type.run.conditions.ignore="Do not consider condition state"
@@ -1208,6 +1233,23 @@ AdvSceneSwitcher.action.window.entry="{{actions}}{{windows}}{{regex}}"
AdvSceneSwitcher.action.log="Log" AdvSceneSwitcher.action.log="Log"
AdvSceneSwitcher.action.log.placeholder="My log message!" AdvSceneSwitcher.action.log.placeholder="My log message!"
AdvSceneSwitcher.action.log.entry="Write to OBS log:{{logMessage}}" AdvSceneSwitcher.action.log.entry="Write to OBS log:{{logMessage}}"
AdvSceneSwitcher.action.mqtt="MQTT"
AdvSceneSwitcher.action.mqtt.topic="Topic:"
AdvSceneSwitcher.action.mqtt.qos="QoS:"
AdvSceneSwitcher.action.mqtt.retain="Retained message:"
AdvSceneSwitcher.action.mqtt.layout="Send message to{{connection}}"
AdvSceneSwitcher.action.obsSetting="OBS setting"
AdvSceneSwitcher.action.obsSetting.action.setFPSType="Set FPS selection type to"
AdvSceneSwitcher.action.obsSetting.action.setFPSCommonValue="Set FPS common selection to"
AdvSceneSwitcher.action.obsSetting.action.setFPSIntegerValue="Set FPS integer selection to"
AdvSceneSwitcher.action.obsSetting.action.setFPSFractionNumeratorValue="Set fractional FPS numerator selection to"
AdvSceneSwitcher.action.obsSetting.action.setFPSFractionDenominatorValue="Set fractional FPS denominator selection to"
AdvSceneSwitcher.action.obsSetting.action.setBaseCanvasX="Set base resolution X value"
AdvSceneSwitcher.action.obsSetting.action.setBaseCanvasY="Set base resolution Y value"
AdvSceneSwitcher.action.obsSetting.action.setOutputCanvasX="Set output resolution X value"
AdvSceneSwitcher.action.obsSetting.action.setOutputCanvasY="Set output resolution Y value"
AdvSceneSwitcher.action.obsSetting.getCurrentValue="Get current value"
AdvSceneSwitcher.action.obsSettings.layout="{{actions}}{{fpsType}}{{fpsIntValue}}{{fpsStringValue}}{{canvasSizeValue}}{{getCurrentValue}}"
# Hotkey # Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher" AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher"
@@ -1216,9 +1258,10 @@ AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="Toggle Start/Stop for the
AdvSceneSwitcher.hotkey.macro.pause="Pause macro %1" AdvSceneSwitcher.hotkey.macro.pause="Pause macro %1"
AdvSceneSwitcher.hotkey.macro.unpause="Unpause macro %1" AdvSceneSwitcher.hotkey.macro.unpause="Unpause macro %1"
AdvSceneSwitcher.hotkey.macro.togglePause="Toggle pause of macro %1" AdvSceneSwitcher.hotkey.macro.togglePause="Toggle pause of macro %1"
AdvSceneSwitcher.hotkey.upMacroSegmentHotkey="Move macro segment selection up" AdvSceneSwitcher.hotkey.macro.new="Add new macro"
AdvSceneSwitcher.hotkey.downMacroSegmentHotkey="Move macro segment selection down" AdvSceneSwitcher.hotkey.macro.segment.up="Move macro segment selection up"
AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey="Remove selected macro segment" AdvSceneSwitcher.hotkey.macro.segment.down="Move macro segment selection down"
AdvSceneSwitcher.hotkey.macro.segment.remove="Remove selected macro segment"
AdvSceneSwitcher.askBackup="Detected a new version of the Advanced Scene Switcher.\nShould a backup of the old settings be created?" AdvSceneSwitcher.askBackup="Detected a new version of the Advanced Scene Switcher.\nShould a backup of the old settings be created?"
AdvSceneSwitcher.askForMacro="Select macro{{macroSelection}}" AdvSceneSwitcher.askForMacro="Select macro{{macroSelection}}"
@@ -1364,6 +1407,35 @@ AdvSceneSwitcher.osc.message.type.false="False"
AdvSceneSwitcher.osc.message.type.infinity="Infinitum" AdvSceneSwitcher.osc.message.type.infinity="Infinitum"
AdvSceneSwitcher.osc.message.type.null="Nil" AdvSceneSwitcher.osc.message.type.null="Nil"
AdvSceneSwitcher.mqttConnection.startListen="Start listening"
AdvSceneSwitcher.mqttConnection.stopListen="Stop listening"
AdvSceneSwitcher.mqttConnection.select="--select connection--"
AdvSceneSwitcher.mqttConnection.add="Add new connection"
AdvSceneSwitcher.mqttConnection.configure="Configure connection settings"
AdvSceneSwitcher.mqttConnection.invalid="Invalid connection selection"
AdvSceneSwitcher.mqttConnection.name="Name:"
AdvSceneSwitcher.mqttConnection.address="Address:"
AdvSceneSwitcher.mqttConnection.username="Username:"
AdvSceneSwitcher.mqttConnection.password="Password:"
AdvSceneSwitcher.mqttConnection.reconnect="Reconnect automatically:"
AdvSceneSwitcher.mqttConnection.reconnectDelay="Automatically reconnect after:"
AdvSceneSwitcher.mqttConnection.connectOnStart="Connect on startup:"
AdvSceneSwitcher.mqttConnection.test="Test connection"
AdvSceneSwitcher.mqttConnection.status.connected="Connected!"
AdvSceneSwitcher.mqttConnection.status.connecting="Connecting ..."
AdvSceneSwitcher.mqttConnection.status.disconnected="Could not connect!"
AdvSceneSwitcher.mqttConnection.topic="Topic"
AdvSceneSwitcher.mqttConnection.qos="QoS"
AdvSceneSwitcher.mqttConnection.topics="Topic subscriptions:"
AdvSceneSwitcher.mqttConnection.inputWarning.emptyTopic="Topic cannot be empty."
AdvSceneSwitcher.mqttConnection.inputWarning.emptyTopic.title="Input Error"
AdvSceneSwitcher.mqttConnection.inputWarning.duplicateTopic="This topic already exists."
AdvSceneSwitcher.mqttConnection.inputWarning.duplicateTopic.title="Duplicate Topic"
AdvSceneSwitcher.mqttConnection.inputWarning.qosRange="QoS must be 0, 1, or 2."
AdvSceneSwitcher.mqttConnection.inputWarning.qosRange.title="Invalid QoS"
AdvSceneSwitcher.mqttConnection.add.layout="Add topic subscription{{topic}}with QoS{{QoS}}"
AdvSceneSwitcher.mqttConnection.add.title="Add MQTT Topic Subscription"
AdvSceneSwitcher.twitchToken.name="Account name:" AdvSceneSwitcher.twitchToken.name="Account name:"
AdvSceneSwitcher.twitchToken.nameNotAvailable="Account already in use" AdvSceneSwitcher.twitchToken.nameNotAvailable="Account already in use"
AdvSceneSwitcher.twitchToken.select="--select Twitch connection--" AdvSceneSwitcher.twitchToken.select="--select Twitch connection--"
@@ -2066,6 +2138,11 @@ AdvSceneSwitcher.tempVar.http.body="Message body"
AdvSceneSwitcher.tempVar.http.error="Error" AdvSceneSwitcher.tempVar.http.error="Error"
AdvSceneSwitcher.tempVar.http.error.description="Empty when no error occurred.\nOther possible values:\n\n * Could not establish connection\n * Failed to bind IP address\n * Failed to read connection\n * Failed to write connection\n * Maximum redirect count exceeded\n * Connection handling canceled\n * SSL connection failed\n * SSL certificate loading failed\n * SSL server verification failed\n * Unsupported HTTP multipart boundary characters\n * Compression failed\n * Connection timed out\n * Proxy connection failed\n * Unknown" AdvSceneSwitcher.tempVar.http.error.description="Empty when no error occurred.\nOther possible values:\n\n * Could not establish connection\n * Failed to bind IP address\n * Failed to read connection\n * Failed to write connection\n * Maximum redirect count exceeded\n * Connection handling canceled\n * SSL connection failed\n * SSL certificate loading failed\n * SSL server verification failed\n * Unsupported HTTP multipart boundary characters\n * Compression failed\n * Connection timed out\n * Proxy connection failed\n * Unknown"
AdvSceneSwitcher.tempVar.mqtt.message="Message"
AdvSceneSwitcher.tempVar.cursor.x="Cursor position (X)"
AdvSceneSwitcher.tempVar.cursor.y="Cursor position (Y)"
AdvSceneSwitcher.selectScene="--select scene--" AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene" AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene" AdvSceneSwitcher.selectCurrentScene="Current Scene"
@@ -2185,13 +2262,12 @@ AdvSceneSwitcher.script.timeout="Script timeout:{{timeout}}"
# This secion is copied from the OBS locale files # This secion is copied from the OBS locale files
# commonly shared locale # OBS commonly shared locale
Browse="Browse" Browse="Browse"
Show="Show" Show="Show"
Hide="Hide" Hide="Hide"
# OBS properties window
# properties window
Basic.PropertiesWindow="Properties for '%1'" Basic.PropertiesWindow="Properties for '%1'"
Basic.PropertiesWindow.AutoSelectFormat="%1 (autoselect: %2)" Basic.PropertiesWindow.AutoSelectFormat="%1 (autoselect: %2)"
Basic.PropertiesWindow.SelectColor="Select color" Basic.PropertiesWindow.SelectColor="Select color"
@@ -2208,7 +2284,7 @@ Basic.PropertiesWindow.AddEditableListFiles="Add files to '%1'"
Basic.PropertiesWindow.AddEditableListEntry="Add entry to '%1'" Basic.PropertiesWindow.AddEditableListEntry="Add entry to '%1'"
Basic.PropertiesWindow.EditEditableListEntry="Edit entry from '%1'" Basic.PropertiesWindow.EditEditableListEntry="Edit entry from '%1'"
# properties view # OBS properties view
Basic.PropertiesView.FPS.Simple="Simple FPS Values" Basic.PropertiesView.FPS.Simple="Simple FPS Values"
Basic.PropertiesView.FPS.Rational="Rational FPS Values" Basic.PropertiesView.FPS.Rational="Rational FPS Values"
Basic.PropertiesView.FPS.ValidFPSRanges="Valid FPS Ranges:" Basic.PropertiesView.FPS.ValidFPSRanges="Valid FPS Ranges:"
@@ -2216,6 +2292,11 @@ Basic.PropertiesView.UrlButton.Text="Open this link in your default web browser?
Basic.PropertiesView.UrlButton.Text.Url="URL: %1" Basic.PropertiesView.UrlButton.Text.Url="URL: %1"
Basic.PropertiesView.UrlButton.OpenUrl="Open URL" Basic.PropertiesView.UrlButton.OpenUrl="Open URL"
# OBS settings
Basic.Settings.Video.FPSCommon="Common FPS Values"
Basic.Settings.Video.FPSInteger="Integer FPS Value"
Basic.Settings.Video.FPSFraction="Fractional FPS Value"
# Legacy tabs below - please don't waste your time adding translations for these :) # Legacy tabs below - please don't waste your time adding translations for these :)
# Transition Tab # Transition Tab
AdvSceneSwitcher.transitionTab.title="Transition" AdvSceneSwitcher.transitionTab.title="Transition"

View File

@@ -154,12 +154,12 @@ AdvSceneSwitcher.condition.video.patternMatchSuccess="El patrón está resaltado
AdvSceneSwitcher.condition.video.objectMatchFail="¡No se encontró el objeto!" AdvSceneSwitcher.condition.video.objectMatchFail="¡No se encontró el objeto!"
AdvSceneSwitcher.condition.video.objectMatchSuccess="El objeto está resaltado en rojo" AdvSceneSwitcher.condition.video.objectMatchSuccess="El objeto está resaltado en rojo"
AdvSceneSwitcher.condition.video.modelLoadFail="¡No se pudieron cargar los datos del modelo!" AdvSceneSwitcher.condition.video.modelLoadFail="¡No se pudieron cargar los datos del modelo!"
AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" AdvSceneSwitcher.condition.video.layout="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}"
AdvSceneSwitcher.condition.video.entry.modelPath="Datos del modelo (haar cascade classifier): {{modelDataPath}}" AdvSceneSwitcher.condition.video.layout.modelPath="Datos del modelo (haar cascade classifier): {{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="Mínimo de vecinos: {{minNeighbors}}" AdvSceneSwitcher.condition.video.layout.minNeighbor="Mínimo de vecinos: {{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduzca la carga de la CPU realizando una comprobación solo cada {{throttleCount}} milisegundos" AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Reduzca la carga de la CPU realizando una comprobación solo cada {{throttleCount}} milisegundos"
AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Realizar comprobación solo en el área" AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Realizar comprobación solo en el área"
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}Realizar comprobación solo en el área {{checkArea}} {{selectArea}}" AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}Realizar comprobación solo en el área {{checkArea}} {{selectArea}}"
AdvSceneSwitcher.condition.video.minSize="Tamaño mínimo:" AdvSceneSwitcher.condition.video.minSize="Tamaño mínimo:"
AdvSceneSwitcher.condition.video.maxSize="Tamaño máximo:" AdvSceneSwitcher.condition.video.maxSize="Tamaño máximo:"
AdvSceneSwitcher.condition.video.selectArea="Seleccionar área" AdvSceneSwitcher.condition.video.selectArea="Seleccionar área"
@@ -640,9 +640,9 @@ AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="Alternar inicio / detenci
AdvSceneSwitcher.hotkey.macro.pause="Pausar macro %1" AdvSceneSwitcher.hotkey.macro.pause="Pausar macro %1"
AdvSceneSwitcher.hotkey.macro.unpause="Despausar macro %1" AdvSceneSwitcher.hotkey.macro.unpause="Despausar macro %1"
AdvSceneSwitcher.hotkey.macro.togglePause="Alternar pausa de macro %1" AdvSceneSwitcher.hotkey.macro.togglePause="Alternar pausa de macro %1"
AdvSceneSwitcher.hotkey.upMacroSegmentHotkey="Mover selección de segmento de macro hacia arriba" AdvSceneSwitcher.hotkey.macro.segment.up="Mover selección de segmento de macro hacia arriba"
AdvSceneSwitcher.hotkey.downMacroSegmentHotkey="Mover selección de segmento de macro hacia abajo" AdvSceneSwitcher.hotkey.macro.segment.down="Mover selección de segmento de macro hacia abajo"
AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey="Eliminar segmento de macro seleccionado" AdvSceneSwitcher.hotkey.macro.segment.remove="Eliminar segmento de macro seleccionado"
AdvSceneSwitcher.askBackup="Se detectó una nueva versión de Advanced Scene Switcher.\n¿Crear una copia de seguridad de la configuración anterior?" AdvSceneSwitcher.askBackup="Se detectó una nueva versión de Advanced Scene Switcher.\n¿Crear una copia de seguridad de la configuración anterior?"
AdvSceneSwitcher.askForMacro="Select macro {{macroSelection}}" AdvSceneSwitcher.askForMacro="Select macro {{macroSelection}}"

View File

@@ -257,14 +257,14 @@ AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription="À quel poi
AdvSceneSwitcher.condition.video.type.main="Sortie principale d'OBS" AdvSceneSwitcher.condition.video.type.main="Sortie principale d'OBS"
AdvSceneSwitcher.condition.video.type.source="Source" AdvSceneSwitcher.condition.video.type.source="Source"
AdvSceneSwitcher.condition.video.type.scene="Scène" AdvSceneSwitcher.condition.video.type.scene="Scène"
AdvSceneSwitcher.condition.video.entry.modelPath="Données du modèle (classificateur de cascade de Haar) :{{modelDataPath}}" AdvSceneSwitcher.condition.video.layout.modelPath="Données du modèle (classificateur de cascade de Haar) :{{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="Nombre minimum de voisins :{{minNeighbors}}" AdvSceneSwitcher.condition.video.layout.minNeighbor="Nombre minimum de voisins :{{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Réduire la charge CPU en effectuant la vérification uniquement toutes les{{throttleCount}}millisecondes" AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Réduire la charge CPU en effectuant la vérification uniquement toutes les{{throttleCount}}millisecondes"
AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Effectuer la vérification uniquement dans la zone" AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Effectuer la vérification uniquement dans la zone"
AdvSceneSwitcher.condition.video.entry.orcColorPick="Vérifier la couleur du texte :{{textColor}}{{selectColor}}" AdvSceneSwitcher.condition.video.layout.ocrColorPick="Vérifier la couleur du texte :{{textColor}}{{selectColor}}"
AdvSceneSwitcher.condition.video.entry.orcTextType="Vérifier le type de texte :{{textType}}" AdvSceneSwitcher.condition.video.layout.ocrTextType="Vérifier le type de texte :{{textType}}"
AdvSceneSwitcher.condition.video.entry.orcLanguage="Vérifier la langue :{{languageCode}}" AdvSceneSwitcher.condition.video.layout.ocrLanguage="Vérifier la langue :{{languageCode}}"
AdvSceneSwitcher.condition.video.entry.color="Vérifier la couleur :{{color}}{{selectColor}}" AdvSceneSwitcher.condition.video.layout.color="Vérifier la couleur :{{color}}{{selectColor}}"
AdvSceneSwitcher.condition.video.minSize="Taille minimale :" AdvSceneSwitcher.condition.video.minSize="Taille minimale :"
AdvSceneSwitcher.condition.video.maxSize="Taille maximale :" AdvSceneSwitcher.condition.video.maxSize="Taille maximale :"
AdvSceneSwitcher.condition.video.selectArea="Sélectionner la zone" AdvSceneSwitcher.condition.video.selectArea="Sélectionner la zone"
@@ -920,9 +920,9 @@ AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="Basculer le démarrage/ar
AdvSceneSwitcher.hotkey.macro.pause="Mettre en pause la macro %1" AdvSceneSwitcher.hotkey.macro.pause="Mettre en pause la macro %1"
AdvSceneSwitcher.hotkey.macro.unpause="Reprendre la macro %1" AdvSceneSwitcher.hotkey.macro.unpause="Reprendre la macro %1"
AdvSceneSwitcher.hotkey.macro.togglePause="Basculer la pause de la macro %1" AdvSceneSwitcher.hotkey.macro.togglePause="Basculer la pause de la macro %1"
AdvSceneSwitcher.hotkey.upMacroSegmentHotkey="Déplacer la sélection du segment de macro vers le haut" AdvSceneSwitcher.hotkey.macro.segment.up="Déplacer la sélection du segment de macro vers le haut"
AdvSceneSwitcher.hotkey.downMacroSegmentHotkey="Déplacer la sélection du segment de macro vers le bas" AdvSceneSwitcher.hotkey.macro.segment.down="Déplacer la sélection du segment de macro vers le bas"
AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey="Supprimer le segment de macro sélectionné" AdvSceneSwitcher.hotkey.macro.segment.remove="Supprimer le segment de macro sélectionné"
AdvSceneSwitcher.askBackup="Une nouvelle version du commutateur de scènes avancé a été détectée.\nSouhaitez-vous créer une sauvegarde des anciens paramètres ?" AdvSceneSwitcher.askBackup="Une nouvelle version du commutateur de scènes avancé a été détectée.\nSouhaitez-vous créer une sauvegarde des anciens paramètres ?"
AdvSceneSwitcher.askForMacro="Sélectionnez la macro{{macroSelection}}" AdvSceneSwitcher.askForMacro="Sélectionnez la macro{{macroSelection}}"

View File

@@ -379,16 +379,14 @@ AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription="一致と
AdvSceneSwitcher.condition.video.type.main="OBS's main output" AdvSceneSwitcher.condition.video.type.main="OBS's main output"
AdvSceneSwitcher.condition.video.type.source="ソース" AdvSceneSwitcher.condition.video.type.source="ソース"
AdvSceneSwitcher.condition.video.type.scene="シーン" AdvSceneSwitcher.condition.video.type.scene="シーン"
; AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" AdvSceneSwitcher.condition.video.layout.modelPath="モデルデータ (そのカスケード分類子):{{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.modelPath="モデルデータ (そのカスケード分類子):{{modelDataPath}}" AdvSceneSwitcher.condition.video.layout.minNeighbor="最小近傍数:{{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="最小近傍数:{{minNeighbors}}" AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}:{{throttleCount}}ミリ秒ごとにのみチェックを実行することでCPU負荷を軽減します"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}:{{throttleCount}}ミリ秒ごとにのみチェックを実行することでCPU負荷を軽減します" AdvSceneSwitcher.condition.video.layout.checkAreaEnable="エリア内のみチェックを実施"
AdvSceneSwitcher.condition.video.entry.checkAreaEnable="エリア内のみチェックを実施" AdvSceneSwitcher.condition.video.layout.ocrColorPick="テキストの色の確認:{{textColor}}{{selectColor}}"
; AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" AdvSceneSwitcher.condition.video.layout.ocrTextType="テキストタイプの確認:{{textType}}"
AdvSceneSwitcher.condition.video.entry.orcColorPick="テキストの色の確認:{{textColor}}{{selectColor}}" AdvSceneSwitcher.condition.video.layout.ocrLanguage="言語の確認:{{languageCode}}"
AdvSceneSwitcher.condition.video.entry.orcTextType="テキストタイプの確認:{{textType}}" AdvSceneSwitcher.condition.video.layout.color="カラーを確認してください:{{color}}{{selectColor}}"
AdvSceneSwitcher.condition.video.entry.orcLanguage="言語の確認:{{languageCode}}"
AdvSceneSwitcher.condition.video.entry.color="カラーを確認してください:{{color}}{{selectColor}}"
AdvSceneSwitcher.condition.video.minSize="最小サイズ:" AdvSceneSwitcher.condition.video.minSize="最小サイズ:"
AdvSceneSwitcher.condition.video.maxSize="最大サイズ:" AdvSceneSwitcher.condition.video.maxSize="最大サイズ:"
AdvSceneSwitcher.condition.video.selectArea="範囲指定" AdvSceneSwitcher.condition.video.selectArea="範囲指定"
@@ -1130,9 +1128,9 @@ AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="高機能シーンスイ
AdvSceneSwitcher.hotkey.macro.pause="マクロ %1 を一時停止します" AdvSceneSwitcher.hotkey.macro.pause="マクロ %1 を一時停止します"
AdvSceneSwitcher.hotkey.macro.unpause="マクロ %1 の一時停止を解除します" AdvSceneSwitcher.hotkey.macro.unpause="マクロ %1 の一時停止を解除します"
AdvSceneSwitcher.hotkey.macro.togglePause="マクロ %1 の一時停止を切り替えます" AdvSceneSwitcher.hotkey.macro.togglePause="マクロ %1 の一時停止を切り替えます"
AdvSceneSwitcher.hotkey.upMacroSegmentHotkey="マクロセグメント選択を上に移動" AdvSceneSwitcher.hotkey.macro.segment.up="マクロセグメント選択を上に移動"
AdvSceneSwitcher.hotkey.downMacroSegmentHotkey="マクロセグメント選択を下に移動" AdvSceneSwitcher.hotkey.macro.segment.down="マクロセグメント選択を下に移動"
AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey="選択したマクロセグメントを削除" AdvSceneSwitcher.hotkey.macro.segment.remove="選択したマクロセグメントを削除"
AdvSceneSwitcher.askBackup="高機能シーンスイッチャーの新しいバージョンが検出されました。\n古い設定のバックアップを作成する必要がありますか" AdvSceneSwitcher.askBackup="高機能シーンスイッチャーの新しいバージョンが検出されました。\n古い設定のバックアップを作成する必要がありますか"
AdvSceneSwitcher.askForMacro="マクロ選択{{macroSelection}}" AdvSceneSwitcher.askForMacro="マクロ選択{{macroSelection}}"

View File

@@ -372,16 +372,16 @@ AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription="Quão semel
AdvSceneSwitcher.condition.video.type.main="Saída principal do OBS" AdvSceneSwitcher.condition.video.type.main="Saída principal do OBS"
AdvSceneSwitcher.condition.video.type.source="Fonte" AdvSceneSwitcher.condition.video.type.source="Fonte"
AdvSceneSwitcher.condition.video.type.scene="Cena" AdvSceneSwitcher.condition.video.type.scene="Cena"
AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" AdvSceneSwitcher.condition.video.layout="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}"
AdvSceneSwitcher.condition.video.entry.modelPath="Dados do modelo (classificador em cascata haar):{{modelDataPath}}" AdvSceneSwitcher.condition.video.layout.modelPath="Dados do modelo (classificador em cascata haar):{{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="Vizinhos mínimos:{{minNeighbors}}" AdvSceneSwitcher.condition.video.layout.minNeighbor="Vizinhos mínimos:{{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduzir carga de CPU realizando a verificação apenas a cada {{throttleCount}} milissegundos" AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}Reduzir carga de CPU realizando a verificação apenas a cada {{throttleCount}} milissegundos"
AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Realizar verificação apenas na área" AdvSceneSwitcher.condition.video.layout.checkAreaEnable="Realizar verificação apenas na área"
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
AdvSceneSwitcher.condition.video.entry.orcColorPick="Verificar cor do texto:{{textColor}}{{selectColor}}" AdvSceneSwitcher.condition.video.layout.ocrColorPick="Verificar cor do texto:{{textColor}}{{selectColor}}"
AdvSceneSwitcher.condition.video.entry.orcTextType="Verificar tipo de texto:{{textType}}" AdvSceneSwitcher.condition.video.layout.ocrTextType="Verificar tipo de texto:{{textType}}"
AdvSceneSwitcher.condition.video.entry.orcLanguage="Verificar idioma:{{languageCode}}" AdvSceneSwitcher.condition.video.layout.ocrLanguage="Verificar idioma:{{languageCode}}"
AdvSceneSwitcher.condition.video.entry.color="Verificar cor:{{color}}{{selectColor}}" AdvSceneSwitcher.condition.video.layout.color="Verificar cor:{{color}}{{selectColor}}"
AdvSceneSwitcher.condition.video.minSize="Tamanho mínimo:" AdvSceneSwitcher.condition.video.minSize="Tamanho mínimo:"
AdvSceneSwitcher.condition.video.maxSize="Tamanho máximo:" AdvSceneSwitcher.condition.video.maxSize="Tamanho máximo:"
AdvSceneSwitcher.condition.video.selectArea="Selecionar área" AdvSceneSwitcher.condition.video.selectArea="Selecionar área"
@@ -1111,9 +1111,9 @@ AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="Alternar Iniciar/Parar pa
AdvSceneSwitcher.hotkey.macro.pause="Pausar macro %1" AdvSceneSwitcher.hotkey.macro.pause="Pausar macro %1"
AdvSceneSwitcher.hotkey.macro.unpause="Retomar macro %1" AdvSceneSwitcher.hotkey.macro.unpause="Retomar macro %1"
AdvSceneSwitcher.hotkey.macro.togglePause="Alternar pausa da macro %1" AdvSceneSwitcher.hotkey.macro.togglePause="Alternar pausa da macro %1"
AdvSceneSwitcher.hotkey.upMacroSegmentHotkey="Mover seleção de segmento de macro para cima" AdvSceneSwitcher.hotkey.macro.segment.up="Mover seleção de segmento de macro para cima"
AdvSceneSwitcher.hotkey.downMacroSegmentHotkey="Mover seleção de segmento de macro para baixo" AdvSceneSwitcher.hotkey.macro.segment.down="Mover seleção de segmento de macro para baixo"
AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey="Remover segmento de macro selecionado" AdvSceneSwitcher.hotkey.macro.segment.remove="Remover segmento de macro selecionado"
AdvSceneSwitcher.askBackup="Detectada uma nova versão do Advanced Scene Switcher.\nDeve ser criado um backup das configurações antigas?" AdvSceneSwitcher.askBackup="Detectada uma nova versão do Advanced Scene Switcher.\nDeve ser criado um backup das configurações antigas?"
AdvSceneSwitcher.askForMacro="Selecionar macro{{macroSelection}}" AdvSceneSwitcher.askForMacro="Selecionar macro{{macroSelection}}"

View File

@@ -139,10 +139,10 @@ AdvSceneSwitcher.condition.video.screenshotFail="Kaynağın ekran görüntüsü
AdvSceneSwitcher.condition.video.patternMatchFail="Desen bulunamadı!" AdvSceneSwitcher.condition.video.patternMatchFail="Desen bulunamadı!"
AdvSceneSwitcher.condition.video.objectMatchFail="Nesne bulunamadı!" AdvSceneSwitcher.condition.video.objectMatchFail="Nesne bulunamadı!"
AdvSceneSwitcher.condition.video.modelLoadFail="Model verileri yüklenemedi!" AdvSceneSwitcher.condition.video.modelLoadFail="Model verileri yüklenemedi!"
AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" AdvSceneSwitcher.condition.video.layout="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}"
AdvSceneSwitcher.condition.video.entry.modelPath="Model verileri (haar kademeli sınıflandırıcı):{{modelDataPath}}" AdvSceneSwitcher.condition.video.layout.modelPath="Model verileri (haar kademeli sınıflandırıcı):{{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum komşular: {{minNeighbors}}" AdvSceneSwitcher.condition.video.layout.minNeighbor="Minimum komşular: {{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}} Yalnızca her seferinde kontrol gerçekleştirerek CPU yükünü azaltın {{throttleCount}} millisaniyeler" AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}} Yalnızca her seferinde kontrol gerçekleştirerek CPU yükünü azaltın {{throttleCount}} millisaniyeler"
AdvSceneSwitcher.condition.stream="Yayınlama" AdvSceneSwitcher.condition.stream="Yayınlama"
AdvSceneSwitcher.condition.stream.state.start="Yayın çalışıyor" AdvSceneSwitcher.condition.stream.state.start="Yayın çalışıyor"
AdvSceneSwitcher.condition.stream.state.stop="Yayın durdu" AdvSceneSwitcher.condition.stream.state.stop="Yayın durdu"

View File

@@ -395,16 +395,16 @@ AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription="颜色与
AdvSceneSwitcher.condition.video.type.main="OBS的主输出" AdvSceneSwitcher.condition.video.type.main="OBS的主输出"
AdvSceneSwitcher.condition.video.type.source="源" AdvSceneSwitcher.condition.video.type.source="源"
AdvSceneSwitcher.condition.video.type.scene="场景" AdvSceneSwitcher.condition.video.type.scene="场景"
AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}" AdvSceneSwitcher.condition.video.layout="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}"
AdvSceneSwitcher.condition.video.entry.modelPath="模型数据 (haar级联分类器): {{modelDataPath}}" AdvSceneSwitcher.condition.video.layout.modelPath="模型数据 (haar级联分类器): {{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="最小区域: {{minNeighbors}}" AdvSceneSwitcher.condition.video.layout.minNeighbor="最小区域: {{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}每隔{{throttleCount}} 毫秒,才执行一次检查,从而减少 CPU 负载." AdvSceneSwitcher.condition.video.layout.throttle="{{throttleEnable}}每隔{{throttleCount}} 毫秒,才执行一次检查,从而减少 CPU 负载."
AdvSceneSwitcher.condition.video.entry.checkAreaEnable="仅在区域内执行检查" AdvSceneSwitcher.condition.video.layout.checkAreaEnable="仅在区域内执行检查"
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}" AdvSceneSwitcher.condition.video.layout.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
AdvSceneSwitcher.condition.video.entry.orcColorPick="文本检查颜色:{{textColor}}{{selectColor}}" AdvSceneSwitcher.condition.video.layout.ocrColorPick="文本检查颜色:{{textColor}}{{selectColor}}"
AdvSceneSwitcher.condition.video.entry.orcTextType="文本检查类型:{{textType}}" AdvSceneSwitcher.condition.video.layout.ocrTextType="文本检查类型:{{textType}}"
AdvSceneSwitcher.condition.video.entry.orcLanguage="语言检查:{{languageCode}}" AdvSceneSwitcher.condition.video.layout.ocrLanguage="语言检查:{{languageCode}}"
AdvSceneSwitcher.condition.video.entry.color="颜色检查:{{color}}{{selectColor}}" AdvSceneSwitcher.condition.video.layout.color="颜色检查:{{color}}{{selectColor}}"
AdvSceneSwitcher.condition.video.minSize="最小尺寸:" AdvSceneSwitcher.condition.video.minSize="最小尺寸:"
AdvSceneSwitcher.condition.video.maxSize="最大尺寸:" AdvSceneSwitcher.condition.video.maxSize="最大尺寸:"
AdvSceneSwitcher.condition.video.selectArea="选择区域" AdvSceneSwitcher.condition.video.selectArea="选择区域"
@@ -1189,9 +1189,9 @@ AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="切换(启动/停止)高
AdvSceneSwitcher.hotkey.macro.pause="停用宏 %1" AdvSceneSwitcher.hotkey.macro.pause="停用宏 %1"
AdvSceneSwitcher.hotkey.macro.unpause="启用宏 %1" AdvSceneSwitcher.hotkey.macro.unpause="启用宏 %1"
AdvSceneSwitcher.hotkey.macro.togglePause="切换(停用/启用)宏 %1" AdvSceneSwitcher.hotkey.macro.togglePause="切换(停用/启用)宏 %1"
AdvSceneSwitcher.hotkey.upMacroSegmentHotkey="上移选择宏" AdvSceneSwitcher.hotkey.macro.segment.up="上移选择宏"
AdvSceneSwitcher.hotkey.downMacroSegmentHotkey="下移选择宏" AdvSceneSwitcher.hotkey.macro.segment.down="下移选择宏"
AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey="删除选定的宏" AdvSceneSwitcher.hotkey.macro.segment.remove="删除选定的宏"
AdvSceneSwitcher.askBackup="检测到新版本的高级场景切换器。\n是否要创建旧版本的设置备份?" AdvSceneSwitcher.askBackup="检测到新版本的高级场景切换器。\n是否要创建旧版本的设置备份?"
AdvSceneSwitcher.askForMacro="选择宏 {{macroSelection}}" AdvSceneSwitcher.askForMacro="选择宏 {{macroSelection}}"

1
deps/paho.mqtt.cpp vendored Submodule

Submodule deps/paho.mqtt.cpp added at 165476b1dc

View File

@@ -181,8 +181,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QComboBox" name="logLevel"> <widget class="QComboBox" name="logLevel"/>
</widget>
</item> </item>
<item> <item>
<spacer name="horizontalSpacer_49"> <spacer name="horizontalSpacer_49">
@@ -295,6 +294,17 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QCheckBox" name="disableMacroWidgetCache">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.disableMacroWidgetCache</string>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_26"> <layout class="QHBoxLayout" name="horizontalLayout_26">
<item> <item>

View File

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

View File

@@ -205,6 +205,16 @@ void AdvSceneSwitcher::on_disableComboBoxFilter_stateChanged(int state)
FilterComboBox::SetFilterBehaviourEnabled(!state); FilterComboBox::SetFilterBehaviourEnabled(!state);
} }
void AdvSceneSwitcher::on_disableMacroWidgetCache_stateChanged(int state)
{
if (loading) {
return;
}
switcher->disableMacroWidgetCache = state;
MacroSegmentList::SetCachingEnabled(!state);
}
void AdvSceneSwitcher::on_warnPluginLoadFailure_stateChanged(int state) void AdvSceneSwitcher::on_warnPluginLoadFailure_stateChanged(int state)
{ {
if (loading) { if (loading) {
@@ -275,12 +285,15 @@ static bool containsSensitiveData(obs_data_t *data)
obs_data_get_array(data, "twitchConnections"); obs_data_get_array(data, "twitchConnections");
OBSDataArrayAutoRelease websocketConnections = OBSDataArrayAutoRelease websocketConnections =
obs_data_get_array(data, "websocketConnections"); obs_data_get_array(data, "websocketConnections");
OBSDataArrayAutoRelease mqttConnections =
obs_data_get_array(data, "mqttConnections");
auto isNotEmpty = [](obs_data_array *array) { auto isNotEmpty = [](obs_data_array *array) {
return obs_data_array_count(array) > 0; return obs_data_array_count(array) > 0;
}; };
return isNotEmpty(twitchTokens) || isNotEmpty(websocketConnections); return isNotEmpty(twitchTokens) || isNotEmpty(websocketConnections) ||
isNotEmpty(mqttConnections);
} }
void AdvSceneSwitcher::on_exportSettings_clicked() void AdvSceneSwitcher::on_exportSettings_clicked()
@@ -537,6 +550,8 @@ void SwitcherData::SaveGeneralSettings(obs_data_t *obj)
obs_data_set_bool(obj, "disableHints", disableHints); obs_data_set_bool(obj, "disableHints", disableHints);
obs_data_set_bool(obj, "disableFilterComboboxFilter", obs_data_set_bool(obj, "disableFilterComboboxFilter",
disableFilterComboboxFilter); disableFilterComboboxFilter);
obs_data_set_bool(obj, "disableMacroWidgetCache",
disableMacroWidgetCache);
obs_data_set_bool(obj, "warnPluginLoadFailure", warnPluginLoadFailure); obs_data_set_bool(obj, "warnPluginLoadFailure", warnPluginLoadFailure);
obs_data_set_bool(obj, "hideLegacyTabs", hideLegacyTabs); obs_data_set_bool(obj, "hideLegacyTabs", hideLegacyTabs);
@@ -594,6 +609,8 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
disableHints = obs_data_get_bool(obj, "disableHints"); disableHints = obs_data_get_bool(obj, "disableHints");
disableFilterComboboxFilter = disableFilterComboboxFilter =
obs_data_get_bool(obj, "disableFilterComboboxFilter"); obs_data_get_bool(obj, "disableFilterComboboxFilter");
disableMacroWidgetCache =
obs_data_get_bool(obj, "disableMacroWidgetCache");
obs_data_set_default_bool(obj, "warnPluginLoadFailure", true); obs_data_set_default_bool(obj, "warnPluginLoadFailure", true);
warnPluginLoadFailure = obs_data_get_bool(obj, "warnPluginLoadFailure"); warnPluginLoadFailure = obs_data_get_bool(obj, "warnPluginLoadFailure");
obs_data_set_default_bool(obj, "hideLegacyTabs", true); obs_data_set_default_bool(obj, "hideLegacyTabs", true);
@@ -889,6 +906,9 @@ void AdvSceneSwitcher::SetupGeneralTab()
switcher->disableFilterComboboxFilter); switcher->disableFilterComboboxFilter);
FilterComboBox::SetFilterBehaviourEnabled( FilterComboBox::SetFilterBehaviourEnabled(
!switcher->disableFilterComboboxFilter); !switcher->disableFilterComboboxFilter);
ui->disableMacroWidgetCache->setChecked(
switcher->disableMacroWidgetCache);
MacroSegmentList::SetCachingEnabled(!switcher->disableMacroWidgetCache);
ui->warnPluginLoadFailure->setChecked(switcher->warnPluginLoadFailure); ui->warnPluginLoadFailure->setChecked(switcher->warnPluginLoadFailure);
ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs); ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs);

View File

@@ -259,7 +259,8 @@ void AudioSwitch::setVolumeLevel(void *data, const float *,
} }
} }
obs_volmeter_t *AddVolmeterToSource(AudioSwitch *entry, obs_weak_source *source) static obs_volmeter_t *addVolmeterToSource(AudioSwitch *entry,
obs_weak_source *source)
{ {
obs_volmeter_t *volmeter = obs_volmeter_create(OBS_FADER_LOG); obs_volmeter_t *volmeter = obs_volmeter_create(OBS_FADER_LOG);
obs_volmeter_add_callback(volmeter, AudioSwitch::setVolumeLevel, entry); obs_volmeter_add_callback(volmeter, AudioSwitch::setVolumeLevel, entry);
@@ -279,7 +280,7 @@ void AudioSwitch::resetVolmeter()
obs_volmeter_remove_callback(volmeter, setVolumeLevel, this); obs_volmeter_remove_callback(volmeter, setVolumeLevel, this);
obs_volmeter_destroy(volmeter); obs_volmeter_destroy(volmeter);
volmeter = AddVolmeterToSource(this, audioSource); volmeter = addVolmeterToSource(this, audioSource);
} }
bool AudioSwitch::initialized() bool AudioSwitch::initialized()
@@ -318,7 +319,7 @@ void AudioSwitch::load(obs_data_t *obj)
duration.Load(obj, "duration"); duration.Load(obj, "duration");
ignoreInactiveSource = obs_data_get_bool(obj, "ignoreInactiveSource"); ignoreInactiveSource = obs_data_get_bool(obj, "ignoreInactiveSource");
volmeter = AddVolmeterToSource(this, audioSource); volmeter = addVolmeterToSource(this, audioSource);
} }
void AudioSwitchFallback::save(obs_data_t *obj) void AudioSwitchFallback::save(obs_data_t *obj)
@@ -349,7 +350,7 @@ AudioSwitch::AudioSwitch(const AudioSwitch &other)
condition(other.condition), condition(other.condition),
duration(other.duration) duration(other.duration)
{ {
volmeter = AddVolmeterToSource(this, other.audioSource); volmeter = addVolmeterToSource(this, other.audioSource);
} }
AudioSwitch::AudioSwitch(AudioSwitch &&other) noexcept AudioSwitch::AudioSwitch(AudioSwitch &&other) noexcept

View File

@@ -12,25 +12,6 @@ bool MacroActionMacro::_registered = MacroActionFactory::Register(
{MacroActionMacro::Create, MacroActionMacroEdit::Create, {MacroActionMacro::Create, MacroActionMacroEdit::Create,
"AdvSceneSwitcher.action.macro"}); "AdvSceneSwitcher.action.macro"});
const static std::map<MacroActionMacro::Action, std::string> actionTypes = {
{MacroActionMacro::Action::PAUSE,
"AdvSceneSwitcher.action.macro.type.pause"},
{MacroActionMacro::Action::UNPAUSE,
"AdvSceneSwitcher.action.macro.type.unpause"},
{MacroActionMacro::Action::RESET_COUNTER,
"AdvSceneSwitcher.action.macro.type.resetCounter"},
{MacroActionMacro::Action::RUN,
"AdvSceneSwitcher.action.macro.type.run"},
{MacroActionMacro::Action::STOP,
"AdvSceneSwitcher.action.macro.type.stop"},
{MacroActionMacro::Action::DISABLE_ACTION,
"AdvSceneSwitcher.action.macro.type.disableAction"},
{MacroActionMacro::Action::ENABLE_ACTION,
"AdvSceneSwitcher.action.macro.type.enableAction"},
{MacroActionMacro::Action::TOGGLE_ACTION,
"AdvSceneSwitcher.action.macro.type.toggleAction"},
};
bool MacroActionMacro::PerformAction() bool MacroActionMacro::PerformAction()
{ {
auto macro = _macro.GetMacro(); auto macro = _macro.GetMacro();
@@ -45,6 +26,9 @@ bool MacroActionMacro::PerformAction()
case Action::UNPAUSE: case Action::UNPAUSE:
macro->SetPaused(false); macro->SetPaused(false);
break; break;
case Action::TOGGLE_PAUSE:
macro->SetPaused(!macro->Paused());
break;
case Action::RESET_COUNTER: case Action::RESET_COUNTER:
macro->ResetRunCount(); macro->ResetRunCount();
break; break;
@@ -210,8 +194,31 @@ void MacroActionMacro::RunActions(Macro *actionMacro) const
static void populateActionSelection(QComboBox *list) static void populateActionSelection(QComboBox *list)
{ {
for (const auto &[_, name] : actionTypes) { static const std::vector<std::pair<MacroActionMacro::Action, std::string>>
list->addItem(obs_module_text(name.c_str())); actions = {
{MacroActionMacro::Action::PAUSE,
"AdvSceneSwitcher.action.macro.type.pause"},
{MacroActionMacro::Action::UNPAUSE,
"AdvSceneSwitcher.action.macro.type.unpause"},
{MacroActionMacro::Action::TOGGLE_PAUSE,
"AdvSceneSwitcher.action.macro.type.togglePause"},
{MacroActionMacro::Action::RESET_COUNTER,
"AdvSceneSwitcher.action.macro.type.resetCounter"},
{MacroActionMacro::Action::RUN,
"AdvSceneSwitcher.action.macro.type.run"},
{MacroActionMacro::Action::STOP,
"AdvSceneSwitcher.action.macro.type.stop"},
{MacroActionMacro::Action::DISABLE_ACTION,
"AdvSceneSwitcher.action.macro.type.disableAction"},
{MacroActionMacro::Action::ENABLE_ACTION,
"AdvSceneSwitcher.action.macro.type.enableAction"},
{MacroActionMacro::Action::TOGGLE_ACTION,
"AdvSceneSwitcher.action.macro.type.toggleAction"},
};
for (const auto &[value, name] : actions) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
} }
} }
@@ -321,7 +328,8 @@ void MacroActionMacroEdit::UpdateEntryData()
if (!_entryData) { if (!_entryData) {
return; return;
} }
_actions->setCurrentIndex(static_cast<int>(_entryData->_action)); _actions->setCurrentIndex(
_actions->findData(static_cast<int>(_entryData->_action)));
_actionIndex->SetValue(_entryData->_actionIndex); _actionIndex->SetValue(_entryData->_actionIndex);
_actionIndex->SetMacro(_entryData->_macro.GetMacro()); _actionIndex->SetMacro(_entryData->_macro.GetMacro());
_macros->SetCurrentMacro(_entryData->_macro); _macros->SetCurrentMacro(_entryData->_macro);
@@ -350,10 +358,11 @@ void MacroActionMacroEdit::MacroChanged(const QString &text)
SetWidgetVisibility(); SetWidgetVisibility();
} }
void MacroActionMacroEdit::ActionChanged(int value) void MacroActionMacroEdit::ActionChanged(int idx)
{ {
GUARD_LOADING_AND_LOCK(); GUARD_LOADING_AND_LOCK();
_entryData->_action = static_cast<MacroActionMacro::Action>(value); _entryData->_action = static_cast<MacroActionMacro::Action>(
_actions->itemData(idx).toInt());
SetWidgetVisibility(); SetWidgetVisibility();
} }

View File

@@ -49,6 +49,7 @@ public:
DISABLE_ACTION, DISABLE_ACTION,
ENABLE_ACTION, ENABLE_ACTION,
TOGGLE_ACTION, TOGGLE_ACTION,
TOGGLE_PAUSE,
}; };
Action _action = Action::RUN; Action _action = Action::RUN;
IntVariable _actionIndex = 1; IntVariable _actionIndex = 1;

View File

@@ -13,6 +13,8 @@
namespace advss { namespace advss {
bool MacroSegmentList::_useCache = true;
MacroSegmentList::MacroSegmentList(QWidget *parent) MacroSegmentList::MacroSegmentList(QWidget *parent)
: QScrollArea(parent), : QScrollArea(parent),
_layout(new QVBoxLayout), _layout(new QVBoxLayout),
@@ -46,6 +48,17 @@ MacroSegmentList::~MacroSegmentList()
_autoScroll = false; _autoScroll = false;
_autoScrollThread.join(); _autoScrollThread.join();
} }
const auto clearWidgetVector =
[](const std::vector<QWidget *> &widgets) {
for (auto widget : widgets) {
widget->deleteLater();
}
};
for (const auto &[_, widgets] : _widgetCache) {
clearWidgetVector(widgets);
}
} }
static bool posIsInScrollbar(const QScrollBar *scrollbar, const QPoint &pos) static bool posIsInScrollbar(const QScrollBar *scrollbar, const QPoint &pos)
@@ -56,7 +69,7 @@ static bool posIsInScrollbar(const QScrollBar *scrollbar, const QPoint &pos)
if (!scrollbar->isVisible()) { if (!scrollbar->isVisible()) {
return false; return false;
} }
const auto geo = scrollbar->geometry(); const auto &geo = scrollbar->geometry();
const auto globalGeo = QRect(scrollbar->mapToGlobal(geo.topLeft()), const auto globalGeo = QRect(scrollbar->mapToGlobal(geo.topLeft()),
scrollbar->mapToGlobal(geo.bottomRight())); scrollbar->mapToGlobal(geo.bottomRight()));
return globalGeo.contains(pos); return globalGeo.contains(pos);
@@ -125,6 +138,53 @@ void MacroSegmentList::Clear(int idx) const
ClearLayout(_contentLayout, idx); ClearLayout(_contentLayout, idx);
} }
void MacroSegmentList::SetCachingEnabled(bool enable)
{
_useCache = enable;
}
void MacroSegmentList::CacheCurrentWidgetsFor(const Macro *macro)
{
if (!_useCache) {
_widgetCache.clear();
return;
}
std::vector<QWidget *> result;
int idx = 0;
QLayoutItem *item;
while ((item = _contentLayout->takeAt(idx))) {
if (!item || !item->widget()) {
continue;
}
auto widget = item->widget();
widget->hide();
result.emplace_back(widget);
}
_widgetCache[macro] = result;
}
bool MacroSegmentList::PopulateWidgetsFromCache(const Macro *macro)
{
if (!_useCache) {
_widgetCache.clear();
return false;
}
auto it = _widgetCache.find(macro);
if (it == _widgetCache.end()) {
return false;
}
for (auto widget : it->second) {
_contentLayout->addWidget(widget);
widget->show();
}
return true;
}
void MacroSegmentList::Highlight(int idx, QColor color) void MacroSegmentList::Highlight(int idx, QColor color)
{ {
auto item = _contentLayout->itemAt(idx); auto item = _contentLayout->itemAt(idx);
@@ -492,7 +552,7 @@ void MacroSegmentList::dropEvent(QDropEvent *event)
void MacroSegmentList::resizeEvent(QResizeEvent *event) void MacroSegmentList::resizeEvent(QResizeEvent *event)
{ {
QWidget::resizeEvent(event); QScrollArea::resizeEvent(event);
SetupVisibleMacroSegmentWidgets(); SetupVisibleMacroSegmentWidgets();
} }

View File

@@ -9,12 +9,14 @@
namespace advss { namespace advss {
class Macro;
class MacroSegmentList : public QScrollArea { class MacroSegmentList : public QScrollArea {
Q_OBJECT Q_OBJECT
public: public:
MacroSegmentList(QWidget *parent = nullptr); MacroSegmentList(QWidget *parent = nullptr);
virtual ~MacroSegmentList(); ~MacroSegmentList();
void SetHelpMsg(const QString &msg) const; void SetHelpMsg(const QString &msg) const;
void SetHelpMsgVisible(bool visible) const; void SetHelpMsgVisible(bool visible) const;
MacroSegmentEdit *WidgetAt(int idx) const; MacroSegmentEdit *WidgetAt(int idx) const;
@@ -24,6 +26,9 @@ public:
void Add(QWidget *widget); void Add(QWidget *widget);
void Remove(int idx) const; void Remove(int idx) const;
void Clear(int idx = 0) const; // Clear all elements >= idx void Clear(int idx = 0) const; // Clear all elements >= idx
static void SetCachingEnabled(bool enable);
void CacheCurrentWidgetsFor(const Macro *);
bool PopulateWidgetsFromCache(const Macro *);
void Highlight(int idx, QColor color = QColor(Qt::green)); void Highlight(int idx, QColor color = QColor(Qt::green));
void SetCollapsed(bool) const; void SetCollapsed(bool) const;
void SetSelection(int idx) const; void SetSelection(int idx) const;
@@ -67,6 +72,9 @@ private:
QLabel *_helpMsg; QLabel *_helpMsg;
bool _checkVisibility = true; bool _checkVisibility = true;
static bool _useCache;
std::unordered_map<const Macro *, std::vector<QWidget *>> _widgetCache;
}; };
} // namespace advss } // namespace advss

View File

@@ -100,6 +100,13 @@ bool AdvSceneSwitcher::AddNewMacro(std::shared_ptr<Macro> &res,
void AdvSceneSwitcher::on_macroAdd_clicked() void AdvSceneSwitcher::on_macroAdd_clicked()
{ {
// Hotkey to add new macro will also use this function.
// Since we don't want the hotkey to have an effect if the macro tab is
// not focused we need to check this here.
if (!MacroTabIsInFocus()) {
return;
}
std::string name; std::string name;
std::shared_ptr<Macro> newMacro; std::shared_ptr<Macro> newMacro;
if (!AddNewMacro(newMacro, name)) { if (!AddNewMacro(newMacro, name)) {
@@ -537,6 +544,11 @@ void AdvSceneSwitcher::on_runMacroOnChange_stateChanged(int value) const
void AdvSceneSwitcher::PopulateMacroActions(Macro &m, uint32_t afterIdx) void AdvSceneSwitcher::PopulateMacroActions(Macro &m, uint32_t afterIdx)
{ {
auto &actions = m.Actions(); auto &actions = m.Actions();
ui->actionsList->SetHelpMsgVisible(actions.size() == 0);
if (ui->actionsList->PopulateWidgetsFromCache(&m)) {
return;
}
// The layout system has not completed geometry propagation yet, so we // The layout system has not completed geometry propagation yet, so we
// can skip those checks for now // can skip those checks for now
@@ -547,7 +559,6 @@ void AdvSceneSwitcher::PopulateMacroActions(Macro &m, uint32_t afterIdx)
actions[afterIdx]->GetId()); actions[afterIdx]->GetId());
ui->actionsList->Add(newEntry); ui->actionsList->Add(newEntry);
} }
ui->actionsList->SetHelpMsgVisible(actions.size() == 0);
// Give the layout system time before enabling the visibility checks and // Give the layout system time before enabling the visibility checks and
// fully constructing the visible macro segments // fully constructing the visible macro segments
@@ -559,6 +570,11 @@ void AdvSceneSwitcher::PopulateMacroActions(Macro &m, uint32_t afterIdx)
void AdvSceneSwitcher::PopulateMacroElseActions(Macro &m, uint32_t afterIdx) void AdvSceneSwitcher::PopulateMacroElseActions(Macro &m, uint32_t afterIdx)
{ {
auto &actions = m.ElseActions(); auto &actions = m.ElseActions();
ui->elseActionsList->SetHelpMsgVisible(actions.size() == 0);
if (ui->elseActionsList->PopulateWidgetsFromCache(&m)) {
return;
}
// The layout system has not completed geometry propagation yet, so we // The layout system has not completed geometry propagation yet, so we
// can skip those checks for now // can skip those checks for now
@@ -569,7 +585,6 @@ void AdvSceneSwitcher::PopulateMacroElseActions(Macro &m, uint32_t afterIdx)
actions[afterIdx]->GetId()); actions[afterIdx]->GetId());
ui->elseActionsList->Add(newEntry); ui->elseActionsList->Add(newEntry);
} }
ui->elseActionsList->SetHelpMsgVisible(actions.size() == 0);
// Give the layout system time before enabling the visibility checks and // Give the layout system time before enabling the visibility checks and
// fully constructing the visible macro segments // fully constructing the visible macro segments
@@ -582,6 +597,11 @@ void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, uint32_t afterIdx)
{ {
bool root = afterIdx == 0; bool root = afterIdx == 0;
auto &conditions = m.Conditions(); auto &conditions = m.Conditions();
ui->conditionsList->SetHelpMsgVisible(conditions.size() == 0);
if (ui->conditionsList->PopulateWidgetsFromCache(&m)) {
return;
}
// The layout system has not completed geometry propagation yet, so we // The layout system has not completed geometry propagation yet, so we
// can skip those checks for now // can skip those checks for now
@@ -594,7 +614,6 @@ void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, uint32_t afterIdx)
ui->conditionsList->Add(newEntry); ui->conditionsList->Add(newEntry);
root = false; root = false;
} }
ui->conditionsList->SetHelpMsgVisible(conditions.size() == 0);
// Give the layout system time before enabling the visibility checks and // Give the layout system time before enabling the visibility checks and
// fully constructing the visible macro segments // fully constructing the visible macro segments
@@ -779,6 +798,10 @@ void AdvSceneSwitcher::MacroSelectionAboutToChange() const
} }
macro->SetElseActionSplitterPosition( macro->SetElseActionSplitterPosition(
ui->macroElseActionSplitter->sizes()); ui->macroElseActionSplitter->sizes());
ui->conditionsList->CacheCurrentWidgetsFor(macro);
ui->actionsList->CacheCurrentWidgetsFor(macro);
ui->elseActionsList->CacheCurrentWidgetsFor(macro);
} }
void AdvSceneSwitcher::MacroSelectionChanged() void AdvSceneSwitcher::MacroSelectionChanged()

View File

@@ -255,6 +255,16 @@ static void removeMacroSegmentHotkeyFunc(void *, obs_hotkey_id, obs_hotkey_t *,
} }
} }
static void addNewMacroHotkeyFunc(void *, obs_hotkey_id, obs_hotkey_t *,
bool pressed)
{
if (pressed && SettingsWindowIsOpened()) {
QMetaObject::invokeMethod(GetSettingsWindow(),
"on_macroAdd_clicked",
Qt::QueuedConnection);
}
}
static void registerHotkeys() static void registerHotkeys()
{ {
switcher->startHotkey = obs_hotkey_register_frontend( switcher->startHotkey = obs_hotkey_register_frontend(
@@ -270,19 +280,21 @@ static void registerHotkeys()
obs_module_text( obs_module_text(
"AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey"), "AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey"),
startStopToggleHotkeyFunc, NULL); startStopToggleHotkeyFunc, NULL);
switcher->newMacroHotkey = obs_hotkey_register_frontend(
"newMacroSwitcherHotkey",
obs_module_text("AdvSceneSwitcher.hotkey.macro.new"),
addNewMacroHotkeyFunc, NULL);
switcher->upMacroSegment = obs_hotkey_register_frontend( switcher->upMacroSegment = obs_hotkey_register_frontend(
"upMacroSegmentSwitcherHotkey", "upMacroSegmentSwitcherHotkey",
obs_module_text("AdvSceneSwitcher.hotkey.upMacroSegmentHotkey"), obs_module_text("AdvSceneSwitcher.hotkey.macro.segment.up"),
upMacroSegmentHotkeyFunc, NULL); upMacroSegmentHotkeyFunc, NULL);
switcher->downMacroSegment = obs_hotkey_register_frontend( switcher->downMacroSegment = obs_hotkey_register_frontend(
"downMacroSegmentSwitcherHotkey", "downMacroSegmentSwitcherHotkey",
obs_module_text( obs_module_text("AdvSceneSwitcher.hotkey.macro.segment.down"),
"AdvSceneSwitcher.hotkey.downMacroSegmentHotkey"),
downMacroSegmentHotkeyFunc, NULL); downMacroSegmentHotkeyFunc, NULL);
switcher->removeMacroSegment = obs_hotkey_register_frontend( switcher->removeMacroSegment = obs_hotkey_register_frontend(
"removeMacroSegmentSwitcherHotkey", "removeMacroSegmentSwitcherHotkey",
obs_module_text( obs_module_text("AdvSceneSwitcher.hotkey.macro.segment.remove"),
"AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey"),
removeMacroSegmentHotkeyFunc, NULL); removeMacroSegmentHotkeyFunc, NULL);
switcher->hotkeysRegistered = true; switcher->hotkeysRegistered = true;
@@ -300,6 +312,7 @@ void SwitcherData::SaveHotkeys(obs_data_t *obj)
saveHotkey(obj, startHotkey, "startHotkey"); saveHotkey(obj, startHotkey, "startHotkey");
saveHotkey(obj, stopHotkey, "stopHotkey"); saveHotkey(obj, stopHotkey, "stopHotkey");
saveHotkey(obj, toggleHotkey, "toggleHotkey"); saveHotkey(obj, toggleHotkey, "toggleHotkey");
saveHotkey(obj, newMacroHotkey, "newMacroHotkey");
saveHotkey(obj, upMacroSegment, "upMacroSegmentHotkey"); saveHotkey(obj, upMacroSegment, "upMacroSegmentHotkey");
saveHotkey(obj, downMacroSegment, "downMacroSegmentHotkey"); saveHotkey(obj, downMacroSegment, "downMacroSegmentHotkey");
saveHotkey(obj, removeMacroSegment, "removeMacroSegmentHotkey"); saveHotkey(obj, removeMacroSegment, "removeMacroSegmentHotkey");
@@ -320,6 +333,7 @@ void SwitcherData::LoadHotkeys(obs_data_t *obj)
loadHotkey(obj, startHotkey, "startHotkey"); loadHotkey(obj, startHotkey, "startHotkey");
loadHotkey(obj, stopHotkey, "stopHotkey"); loadHotkey(obj, stopHotkey, "stopHotkey");
loadHotkey(obj, toggleHotkey, "toggleHotkey"); loadHotkey(obj, toggleHotkey, "toggleHotkey");
loadHotkey(obj, newMacroHotkey, "newMacroHotkey");
loadHotkey(obj, upMacroSegment, "upMacroSegmentHotkey"); loadHotkey(obj, upMacroSegment, "upMacroSegmentHotkey");
loadHotkey(obj, downMacroSegment, "downMacroSegmentHotkey"); loadHotkey(obj, downMacroSegment, "downMacroSegmentHotkey");
loadHotkey(obj, removeMacroSegment, "removeMacroSegmentHotkey"); loadHotkey(obj, removeMacroSegment, "removeMacroSegmentHotkey");

View File

@@ -149,6 +149,7 @@ public:
obs_hotkey_id startHotkey = OBS_INVALID_HOTKEY_ID; obs_hotkey_id startHotkey = OBS_INVALID_HOTKEY_ID;
obs_hotkey_id stopHotkey = OBS_INVALID_HOTKEY_ID; obs_hotkey_id stopHotkey = OBS_INVALID_HOTKEY_ID;
obs_hotkey_id toggleHotkey = OBS_INVALID_HOTKEY_ID; obs_hotkey_id toggleHotkey = OBS_INVALID_HOTKEY_ID;
obs_hotkey_id newMacroHotkey = OBS_INVALID_HOTKEY_ID;
obs_hotkey_id upMacroSegment = OBS_INVALID_HOTKEY_ID; obs_hotkey_id upMacroSegment = OBS_INVALID_HOTKEY_ID;
obs_hotkey_id downMacroSegment = OBS_INVALID_HOTKEY_ID; obs_hotkey_id downMacroSegment = OBS_INVALID_HOTKEY_ID;
obs_hotkey_id removeMacroSegment = OBS_INVALID_HOTKEY_ID; obs_hotkey_id removeMacroSegment = OBS_INVALID_HOTKEY_ID;
@@ -163,6 +164,7 @@ public:
bool warnPluginLoadFailure = true; bool warnPluginLoadFailure = true;
bool disableHints = false; bool disableHints = false;
bool disableFilterComboboxFilter = false; bool disableFilterComboboxFilter = false;
bool disableMacroWidgetCache = false;
bool hideLegacyTabs = true; bool hideLegacyTabs = true;
bool saveWindowGeo = false; bool saveWindowGeo = false;
QPoint windowPos = {}; QPoint windowPos = {};

View File

@@ -62,6 +62,11 @@ void FilterComboBox::SetFilterBehaviourEnabled(bool value)
FilterComboBox::_filteringEnabled = value; FilterComboBox::_filteringEnabled = value;
} }
void FilterComboBox::SetAllowUnmatchedSelection(bool allow)
{
_allowUnmatchedSelection = allow;
}
void FilterComboBox::setCurrentText(const QString &text) void FilterComboBox::setCurrentText(const QString &text)
{ {
if (_filteringEnabled) { if (_filteringEnabled) {
@@ -82,7 +87,7 @@ void FilterComboBox::setItemText(int index, const QString &text)
void FilterComboBox::focusOutEvent(QFocusEvent *event) void FilterComboBox::focusOutEvent(QFocusEvent *event)
{ {
// Reset on invalid selection // Reset on invalid selection
if (findText(currentText()) == -1) { if (!_allowUnmatchedSelection && findText(currentText()) == -1) {
setCurrentIndex(-1); setCurrentIndex(-1);
Emit(-1, ""); Emit(-1, "");
} }

View File

@@ -13,6 +13,7 @@ public:
FilterComboBox(QWidget *parent = nullptr, FilterComboBox(QWidget *parent = nullptr,
const QString &placehodler = ""); const QString &placehodler = "");
static void SetFilterBehaviourEnabled(bool); static void SetFilterBehaviourEnabled(bool);
void SetAllowUnmatchedSelection(bool allow);
void setCurrentText(const QString &text); void setCurrentText(const QString &text);
void setItemText(int index, const QString &text); void setItemText(int index, const QString &text);
@@ -27,6 +28,7 @@ private slots:
private: private:
void Emit(int index, const QString &text); void Emit(int index, const QString &text);
bool _allowUnmatchedSelection = false;
int _lastEmittedIndex = -1; int _lastEmittedIndex = -1;
QString _lastEmittedText; QString _lastEmittedText;

View File

@@ -9,6 +9,27 @@ namespace advss {
template<class T> class MessageDispatcher { template<class T> class MessageDispatcher {
public: public:
MessageDispatcher() = default;
~MessageDispatcher() = default;
MessageDispatcher(const MessageDispatcher &other)
: _clients(other._clients)
{
}
MessageDispatcher(MessageDispatcher &&other) noexcept
: _clients(other._clients)
{
}
MessageDispatcher<T> &operator=(const MessageDispatcher<T> &other)
{
_clients = other._clients;
return *this;
}
MessageDispatcher<T> &operator=(MessageDispatcher<T> &&other) noexcept
{
std::swap(_clients, other._clients);
return *this;
}
[[nodiscard]] std::shared_ptr<MessageBuffer<T>> RegisterClient(); [[nodiscard]] std::shared_ptr<MessageBuffer<T>> RegisterClient();
void DispatchMessage(const T &message); void DispatchMessage(const T &message);

View File

@@ -409,6 +409,13 @@ void SceneSelectionWidget::SetScene(const SceneSelection &s)
_currentSelection = s; _currentSelection = s;
} }
void SceneSelectionWidget::showEvent(QShowEvent *event)
{
FilterComboBox::showEvent(event);
const QSignalBlocker b(this);
Reset();
}
bool SceneSelectionWidget::IsCurrentSceneSelected(const QString &name) bool SceneSelectionWidget::IsCurrentSceneSelected(const QString &name)
{ {
return name == QString::fromStdString((obs_module_text( return name == QString::fromStdString((obs_module_text(
@@ -435,9 +442,8 @@ void SceneSelectionWidget::SelectionChanged(int)
void SceneSelectionWidget::ItemAdd(const QString &) void SceneSelectionWidget::ItemAdd(const QString &)
{ {
blockSignals(true); const QSignalBlocker b(this);
Reset(); Reset();
blockSignals(false);
} }
bool SceneSelectionWidget::NameUsed(const QString &name) bool SceneSelectionWidget::NameUsed(const QString &name)
@@ -462,9 +468,8 @@ void SceneSelectionWidget::ItemRemove(const QString &name)
void SceneSelectionWidget::ItemRename(const QString &, const QString &) void SceneSelectionWidget::ItemRename(const QString &, const QString &)
{ {
blockSignals(true); const QSignalBlocker b(this);
Reset(); Reset();
blockSignals(false);
} }
} // namespace advss } // namespace advss

View File

@@ -45,6 +45,10 @@ public:
bool previous = false, bool current = false, bool previous = false, bool current = false,
bool preview = false); bool preview = false);
EXPORT void SetScene(const SceneSelection &); EXPORT void SetScene(const SceneSelection &);
protected:
void showEvent(QShowEvent *event) override;
signals: signals:
void SceneChanged(const SceneSelection &); void SceneChanged(const SceneSelection &);

View File

@@ -164,7 +164,7 @@ void SourceSelectionWidget::PopulateSelection()
} }
_variablesEndIdx = count(); _variablesEndIdx = count();
AddSelectionGroup(this, _sourceNames); AddSelectionGroup(this, _populateSourcesCallback());
_sourcesEndIdx = count(); _sourcesEndIdx = count();
// Remove last separator // Remove last separator
@@ -172,13 +172,13 @@ void SourceSelectionWidget::PopulateSelection()
setCurrentIndex(-1); setCurrentIndex(-1);
} }
SourceSelectionWidget::SourceSelectionWidget(QWidget *parent, SourceSelectionWidget::SourceSelectionWidget(
const QStringList &sourceNames, QWidget *parent, const std::function<QStringList()> &populate,
bool addVariables) bool addVariables)
: FilterComboBox(parent, : FilterComboBox(parent,
obs_module_text("AdvSceneSwitcher.selectSource")), obs_module_text("AdvSceneSwitcher.selectSource")),
_addVariables(addVariables), _addVariables(addVariables),
_sourceNames(sourceNames) _populateSourcesCallback(populate)
{ {
setDuplicatesEnabled(true); setDuplicatesEnabled(true);
PopulateSelection(); PopulateSelection();
@@ -200,6 +200,11 @@ SourceSelectionWidget::SourceSelectionWidget(QWidget *parent,
void SourceSelectionWidget::SetSource(const SourceSelection &s) void SourceSelectionWidget::SetSource(const SourceSelection &s)
{ {
{
const QSignalBlocker b(this);
PopulateSelection();
}
int idx = -1; int idx = -1;
switch (s.GetType()) { switch (s.GetType()) {
@@ -229,23 +234,23 @@ void SourceSelectionWidget::SetSource(const SourceSelection &s)
_currentSelection = s; _currentSelection = s;
} }
void SourceSelectionWidget::SetSourceNameList(const QStringList &list)
{
_sourceNames = list;
Reset();
}
void SourceSelectionWidget::SelectionChanged(int) void SourceSelectionWidget::SelectionChanged(int)
{ {
_currentSelection = CurrentSelection(); _currentSelection = CurrentSelection();
emit SourceChanged(_currentSelection); emit SourceChanged(_currentSelection);
} }
void SourceSelectionWidget::showEvent(QShowEvent *event)
{
FilterComboBox::showEvent(event);
const QSignalBlocker b(this);
Reset();
}
void SourceSelectionWidget::ItemAdd(const QString &) void SourceSelectionWidget::ItemAdd(const QString &)
{ {
blockSignals(true); const QSignalBlocker b(this);
Reset(); Reset();
blockSignals(false);
} }
bool SourceSelectionWidget::NameUsed(const QString &name) bool SourceSelectionWidget::NameUsed(const QString &name)
@@ -265,9 +270,8 @@ void SourceSelectionWidget::ItemRemove(const QString &name)
void SourceSelectionWidget::ItemRename(const QString &, const QString &) void SourceSelectionWidget::ItemRename(const QString &, const QString &)
{ {
blockSignals(true); const QSignalBlocker b(this);
Reset(); Reset();
blockSignals(false);
} }
} // namespace advss } // namespace advss

View File

@@ -39,13 +39,16 @@ class ADVSS_EXPORT SourceSelectionWidget : public FilterComboBox {
Q_OBJECT Q_OBJECT
public: public:
SourceSelectionWidget(QWidget *parent, const QStringList &sourceNames, SourceSelectionWidget(QWidget *parent,
const std::function<QStringList()> &populate,
bool addVariables = true); bool addVariables = true);
void SetSource(const SourceSelection &); void SetSource(const SourceSelection &);
void SetSourceNameList(const QStringList &);
signals: signals:
void SourceChanged(const SourceSelection &); void SourceChanged(const SourceSelection &);
protected:
void showEvent(QShowEvent *event) override;
private slots: private slots:
void SelectionChanged(int); void SelectionChanged(int);
void ItemAdd(const QString &name); void ItemAdd(const QString &name);
@@ -59,7 +62,7 @@ private:
bool NameUsed(const QString &name); bool NameUsed(const QString &name);
bool _addVariables; bool _addVariables;
QStringList _sourceNames; std::function<QStringList()> _populateSourcesCallback;
SourceSelection _currentSelection; SourceSelection _currentSelection;
// Order of entries // Order of entries

View File

@@ -30,6 +30,7 @@ install_advss_plugin_dependency(...)
add_plugin(http) add_plugin(http)
add_plugin(midi) add_plugin(midi)
add_plugin(mqtt)
add_plugin(openvr) add_plugin(openvr)
add_plugin(stream-deck) add_plugin(stream-deck)
add_plugin(twitch) add_plugin(twitch)

View File

@@ -21,6 +21,8 @@ target_sources(
macro-action-log.hpp macro-action-log.hpp
macro-action-media.cpp macro-action-media.cpp
macro-action-media.hpp macro-action-media.hpp
macro-action-obs-settings.cpp
macro-action-obs-settings.hpp
macro-action-osc.cpp macro-action-osc.cpp
macro-action-osc.hpp macro-action-osc.hpp
macro-action-plugin-state.cpp macro-action-plugin-state.cpp
@@ -181,7 +183,9 @@ target_sources(
utils/websocket-helpers.cpp utils/websocket-helpers.cpp
utils/websocket-helpers.hpp utils/websocket-helpers.hpp
utils/websocket-tab.cpp utils/websocket-tab.cpp
utils/websocket-tab.hpp) utils/websocket-tab.hpp
utils/window-selection.cpp
utils/window-selection.hpp)
if(OS_WINDOWS) if(OS_WINDOWS)
target_sources(${PROJECT_NAME} PRIVATE utils/windows/windows.cpp) target_sources(${PROJECT_NAME} PRIVATE utils/windows/windows.cpp)

View File

@@ -421,10 +421,17 @@ static inline void populateFadeTypeSelection(QComboBox *list)
} }
} }
static QStringList getAudioSourcesList()
{
auto sources = GetAudioSourceNames();
sources.sort();
return sources;
}
MacroActionAudioEdit::MacroActionAudioEdit( MacroActionAudioEdit::MacroActionAudioEdit(
QWidget *parent, std::shared_ptr<MacroActionAudio> entryData) QWidget *parent, std::shared_ptr<MacroActionAudio> entryData)
: QWidget(parent), : QWidget(parent),
_sources(new SourceSelectionWidget(this, QStringList(), true)), _sources(new SourceSelectionWidget(this, getAudioSourcesList, true)),
_actions(new QComboBox), _actions(new QComboBox),
_fadeTypes(new QComboBox), _fadeTypes(new QComboBox),
_syncOffset(new VariableSpinBox), _syncOffset(new VariableSpinBox),
@@ -468,9 +475,6 @@ MacroActionAudioEdit::MacroActionAudioEdit(
_rate->setSuffix("%"); _rate->setSuffix("%");
populateActionSelection(_actions); populateActionSelection(_actions);
auto sources = GetAudioSourceNames();
sources.sort();
_sources->SetSourceNameList(sources);
populateFadeTypeSelection(_fadeTypes); populateFadeTypeSelection(_fadeTypes);
PopulateMonitorTypeSelection(_monitorTypes); PopulateMonitorTypeSelection(_monitorTypes);
@@ -518,7 +522,7 @@ MacroActionAudioEdit::MacroActionAudioEdit(
QWidget::connect(_fadeTypes, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_fadeTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(FadeTypeChanged(int))); SLOT(FadeTypeChanged(int)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = { const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{audioSources}}", _sources}, {"{{audioSources}}", _sources},
{"{{actions}}", _actions}, {"{{actions}}", _actions},
{"{{syncOffset}}", _syncOffset}, {"{{syncOffset}}", _syncOffset},

View File

@@ -185,8 +185,8 @@ void MacroActionFilter::ResolveVariablesToFixedValues()
static inline void populateActionSelection(QComboBox *list) static inline void populateActionSelection(QComboBox *list)
{ {
for (auto entry : actionTypes) { for (const auto &[_, name] : actionTypes) {
list->addItem(obs_module_text(entry.second.c_str())); list->addItem(obs_module_text(name.c_str()));
} }
} }
@@ -199,10 +199,17 @@ static inline void populateSettingsInputMethods(QComboBox *list)
} }
} }
static QStringList getFilterSourcesList()
{
auto sources = GetSourcesWithFilterNames();
sources.sort();
return sources;
}
MacroActionFilterEdit::MacroActionFilterEdit( MacroActionFilterEdit::MacroActionFilterEdit(
QWidget *parent, std::shared_ptr<MacroActionFilter> entryData) QWidget *parent, std::shared_ptr<MacroActionFilter> entryData)
: QWidget(parent), : QWidget(parent),
_sources(new SourceSelectionWidget(this, QStringList(), true)), _sources(new SourceSelectionWidget(this, getFilterSourcesList, true)),
_filters(new FilterSelectionWidget(this, _sources, true)), _filters(new FilterSelectionWidget(this, _sources, true)),
_actions(new QComboBox()), _actions(new QComboBox()),
_getSettings(new QPushButton(obs_module_text( _getSettings(new QPushButton(obs_module_text(
@@ -220,9 +227,6 @@ MacroActionFilterEdit::MacroActionFilterEdit(
_filters->setSizeAdjustPolicy(QComboBox::AdjustToContents); _filters->setSizeAdjustPolicy(QComboBox::AdjustToContents);
populateActionSelection(_actions); populateActionSelection(_actions);
auto sources = GetSourcesWithFilterNames();
sources.sort();
_sources->SetSourceNameList(sources);
_refreshSettingSelection->setToolTip(obs_module_text( _refreshSettingSelection->setToolTip(obs_module_text(
"AdvSceneSwitcher.action.filter.refresh.tooltip")); "AdvSceneSwitcher.action.filter.refresh.tooltip"));

View File

@@ -219,6 +219,13 @@ static inline void populateSelectionTypeSelection(QComboBox *list)
} }
} }
static QStringList getMediaSourcesList()
{
auto sources = GetMediaSourceNames();
sources.sort();
return sources;
}
MacroActionMediaEdit::MacroActionMediaEdit( MacroActionMediaEdit::MacroActionMediaEdit(
QWidget *parent, std::shared_ptr<MacroActionMedia> entryData) QWidget *parent, std::shared_ptr<MacroActionMedia> entryData)
: QWidget(parent), : QWidget(parent),
@@ -229,15 +236,12 @@ MacroActionMediaEdit::MacroActionMediaEdit(
0, 100, 0, 100,
obs_module_text( obs_module_text(
"AdvSceneSwitcher.action.media.seek.percentage.label"))), "AdvSceneSwitcher.action.media.seek.percentage.label"))),
_sources(new SourceSelectionWidget(this, QStringList(), true)), _sources(new SourceSelectionWidget(this, getMediaSourcesList, true)),
_sceneItems(new SceneItemSelectionWidget(parent, false)), _sceneItems(new SceneItemSelectionWidget(parent, false)),
_scenes(new SceneSelectionWidget(this, true, false, true, true, true)) _scenes(new SceneSelectionWidget(this, true, false, true, true, true))
{ {
populateActionSelection(_actions); populateActionSelection(_actions);
populateSelectionTypeSelection(_selectionTypes); populateSelectionTypeSelection(_selectionTypes);
auto sources = GetMediaSourceNames();
sources.sort();
_sources->SetSourceNameList(sources);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int))); SLOT(ActionChanged(int)));

View File

@@ -0,0 +1,387 @@
#include "macro-action-obs-settings.hpp"
#include "layout-helpers.hpp"
#include "profile-helpers.hpp"
#include "ui-helpers.hpp"
#include <obs-frontend-api.h>
#include <util/config-file.h>
namespace advss {
const std::string MacroActionOBSSettings::id = "obs_settings";
bool MacroActionOBSSettings::_registered = MacroActionFactory::Register(
MacroActionOBSSettings::id,
{MacroActionOBSSettings::Create, MacroActionOBSSettingsEdit::Create,
"AdvSceneSwitcher.action.obsSetting"});
const static std::map<MacroActionOBSSettings::Action, std::string> actionTypes = {
{MacroActionOBSSettings::Action::FPS_TYPE,
"AdvSceneSwitcher.action.obsSetting.action.setFPSType"},
{MacroActionOBSSettings::Action::FPS_COMMON_VALUE,
"AdvSceneSwitcher.action.obsSetting.action.setFPSCommonValue"},
{MacroActionOBSSettings::Action::FPS_INT_VALUE,
"AdvSceneSwitcher.action.obsSetting.action.setFPSIntegerValue"},
{MacroActionOBSSettings::Action::FPS_NUM_VALUE,
"AdvSceneSwitcher.action.obsSetting.action.setFPSFractionNumeratorValue"},
{MacroActionOBSSettings::Action::FPS_DEN_VALUE,
"AdvSceneSwitcher.action.obsSetting.action.setFPSFractionDenominatorValue"},
{MacroActionOBSSettings::Action::BASE_CANVAS_X_VALUE,
"AdvSceneSwitcher.action.obsSetting.action.setBaseCanvasX"},
{MacroActionOBSSettings::Action::BASE_CANVAS_Y_VALUE,
"AdvSceneSwitcher.action.obsSetting.action.setBaseCanvasY"},
{MacroActionOBSSettings::Action::OUTPUT_X_VALUE,
"AdvSceneSwitcher.action.obsSetting.action.setOutputCanvasX"},
{MacroActionOBSSettings::Action::OUTPUT_Y_VALUE,
"AdvSceneSwitcher.action.obsSetting.action.setOutputCanvasY"},
};
template<typename Func, typename... Args>
static void setConfigValueHelper(Func func, Args... args)
{
auto config = obs_frontend_get_profile_config();
func(config, args...);
if (config_save(config) != CONFIG_SUCCESS) {
blog(LOG_WARNING, "failed to save config!");
}
}
template<typename Func, typename... Args>
static auto getConfigValueHelper(Func func, Func defaultFunc, Args... args)
-> std::optional<std::invoke_result_t<Func, config_t *, Args...>>
{
using Ret = std::invoke_result_t<Func, config_t *, Args...>;
auto config = obs_frontend_get_profile_config();
if (config_has_user_value(config, args...)) {
return func(config, args...);
}
if (config_has_default_value(config, args...)) {
return defaultFunc(config, args...);
}
return std::optional<Ret>{};
}
bool MacroActionOBSSettings::PerformAction()
{
switch (_action) {
case Action::FPS_TYPE:
setConfigValueHelper(config_set_uint, "Video", "FPSType",
static_cast<int>(_fpsType));
obs_frontend_reset_video();
break;
case Action::FPS_COMMON_VALUE:
setConfigValueHelper(config_set_string, "Video", "FPSCommon",
_fpsStringValue.c_str());
obs_frontend_reset_video();
break;
case Action::FPS_INT_VALUE:
setConfigValueHelper(config_set_uint, "Video", "FPSInt",
static_cast<int>(_fpsIntValue));
obs_frontend_reset_video();
break;
case Action::FPS_NUM_VALUE:
setConfigValueHelper(config_set_uint, "Video", "FPSNum",
static_cast<int>(_fpsIntValue));
obs_frontend_reset_video();
break;
case Action::FPS_DEN_VALUE:
setConfigValueHelper(config_set_uint, "Video", "FPSDen",
static_cast<int>(_fpsIntValue));
obs_frontend_reset_video();
break;
case Action::BASE_CANVAS_X_VALUE:
setConfigValueHelper(config_set_uint, "Video", "BaseCX",
static_cast<int>(_canvasSizeValue));
obs_frontend_reset_video();
break;
case Action::BASE_CANVAS_Y_VALUE:
setConfigValueHelper(config_set_uint, "Video", "BaseCY",
static_cast<int>(_canvasSizeValue));
obs_frontend_reset_video();
break;
case Action::OUTPUT_X_VALUE:
setConfigValueHelper(config_set_uint, "Video", "OutputCX",
static_cast<int>(_canvasSizeValue));
obs_frontend_reset_video();
break;
case Action::OUTPUT_Y_VALUE:
setConfigValueHelper(config_set_uint, "Video", "OutputCY",
static_cast<int>(_canvasSizeValue));
obs_frontend_reset_video();
break;
default:
break;
}
return true;
}
void MacroActionOBSSettings::LogAction() const
{
auto it = actionTypes.find(_action);
if (it != actionTypes.end()) {
ablog(LOG_INFO, "performed action \"%s\"", it->second.c_str());
} else {
blog(LOG_WARNING, "ignored unknown obs setting action %d",
static_cast<int>(_action));
}
}
bool MacroActionOBSSettings::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
_fpsIntValue.Save(obj, "fpsIntValue");
_fpsStringValue.Save(obj, "fpsStringValue");
_canvasSizeValue.Save(obj, "canvasSizeValue");
return true;
}
bool MacroActionOBSSettings::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
_fpsIntValue.Load(obj, "fpsIntValue");
_fpsStringValue.Load(obj, "fpsStringValue");
_canvasSizeValue.Load(obj, "canvasSizeValue");
return true;
}
std::shared_ptr<MacroAction> MacroActionOBSSettings::Create(Macro *m)
{
return std::make_shared<MacroActionOBSSettings>(m);
}
std::shared_ptr<MacroAction> MacroActionOBSSettings::Copy() const
{
return std::make_shared<MacroActionOBSSettings>(*this);
}
void MacroActionOBSSettings::ResolveVariablesToFixedValues()
{
_fpsIntValue.ResolveVariables();
_canvasSizeValue.ResolveVariables();
}
static void populateActionSelection(QComboBox *list)
{
for (const auto &[value, name] : actionTypes) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
}
static void populateFPSTypeSelection(QComboBox *list)
{
static const std::map<MacroActionOBSSettings::FPSType, std::string>
types = {
{MacroActionOBSSettings::FPSType::COMMON,
"Basic.Settings.Video.FPSCommon"},
{MacroActionOBSSettings::FPSType::INTEGER,
"Basic.Settings.Video.FPSInteger"},
{MacroActionOBSSettings::FPSType::FRACTION,
"Basic.Settings.Video.FPSFraction"},
};
for (const auto &[value, name] : types) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
}
MacroActionOBSSettingsEdit::MacroActionOBSSettingsEdit(
QWidget *parent, std::shared_ptr<MacroActionOBSSettings> entryData)
: QWidget(parent),
_actions(new QComboBox(this)),
_fpsType(new QComboBox(this)),
_fpsIntValue(new VariableSpinBox(this)),
_fpsStringValue(new VariableLineEdit(this)),
_canvasSizeValue(new VariableSpinBox(this)),
_getCurrentValue(new QPushButton(obs_module_text(
"AdvSceneSwitcher.action.obsSetting.getCurrentValue")))
{
populateActionSelection(_actions);
populateFPSTypeSelection(_fpsType);
_fpsIntValue->setMaximum(1000);
_canvasSizeValue->setMaximum(100000);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_fpsType, SIGNAL(currentIndexChanged(int)), this,
SLOT(FPSTypeChanged(int)));
QWidget::connect(
_fpsIntValue,
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this, SLOT(FPSIntValueChanged(const NumberVariable<int> &)));
QWidget::connect(_fpsStringValue, SIGNAL(editingFinished()), this,
SLOT(FPSStringValueChanged()));
QWidget::connect(
_canvasSizeValue,
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this,
SLOT(CanvasSizeValueChanged(const NumberVariable<int> &)));
QWidget::connect(_getCurrentValue, SIGNAL(clicked()), this,
SLOT(GetCurrentValueClicked()));
auto layout = new QHBoxLayout();
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.action.obsSettings.layout"),
layout,
{{"{{actions}}", _actions},
{"{{fpsType}}", _fpsType},
{"{{fpsIntValue}}", _fpsIntValue},
{"{{fpsStringValue}}", _fpsStringValue},
{"{{canvasSizeValue}}", _canvasSizeValue},
{"{{getCurrentValue}}", _getCurrentValue}});
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionOBSSettingsEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_actions->setCurrentIndex(
_actions->findData(static_cast<int>(_entryData->_action)));
_fpsType->setCurrentIndex(
_actions->findData(static_cast<int>(_entryData->_fpsType)));
_fpsIntValue->SetValue(_entryData->_fpsIntValue);
_fpsStringValue->setText(_entryData->_fpsStringValue);
_canvasSizeValue->SetValue(_entryData->_canvasSizeValue);
SetWidgetVisibility();
}
void MacroActionOBSSettingsEdit::FPSTypeChanged(int idx)
{
GUARD_LOADING_AND_LOCK();
_entryData->_fpsType = static_cast<MacroActionOBSSettings::FPSType>(
_fpsType->itemData(idx).toInt());
SetWidgetVisibility();
}
void MacroActionOBSSettingsEdit::FPSIntValueChanged(
const NumberVariable<int> &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_fpsIntValue = value;
}
void MacroActionOBSSettingsEdit::FPSStringValueChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_fpsStringValue = _fpsStringValue->text().toStdString();
}
void MacroActionOBSSettingsEdit::CanvasSizeValueChanged(
const NumberVariable<int> &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_canvasSizeValue = value;
}
void MacroActionOBSSettingsEdit::GetCurrentValueClicked()
{
const auto setSpinVideoValue = [this](VariableSpinBox *spin,
const char *name) {
auto value = getConfigValueHelper(config_get_uint,
config_get_default_uint,
"Video", name);
if (!value) {
return;
}
spin->SetFixedValue(static_cast<int>(*value));
};
const auto setFpsValue = [&](const char *name) {
setSpinVideoValue(_fpsIntValue, name);
};
const auto setCanvasValue = [&](const char *name) {
setSpinVideoValue(_canvasSizeValue, name);
};
switch (_entryData->_action) {
case MacroActionOBSSettings::Action::FPS_COMMON_VALUE: {
auto value = getConfigValueHelper(config_get_string,
config_get_default_string,
"Video", "FPSCommon");
if (!value) {
break;
}
_fpsStringValue->setText(QString(*value));
break;
}
case MacroActionOBSSettings::Action::FPS_INT_VALUE:
setFpsValue("FPSInt");
break;
case MacroActionOBSSettings::Action::FPS_NUM_VALUE:
setFpsValue("FPSNum");
break;
case MacroActionOBSSettings::Action::FPS_DEN_VALUE:
setFpsValue("FPSDen");
break;
case MacroActionOBSSettings::Action::BASE_CANVAS_X_VALUE:
setCanvasValue("BaseCX");
break;
case MacroActionOBSSettings::Action::BASE_CANVAS_Y_VALUE:
setCanvasValue("BaseCY");
break;
case MacroActionOBSSettings::Action::OUTPUT_X_VALUE:
setCanvasValue("OutputCX");
break;
case MacroActionOBSSettings::Action::OUTPUT_Y_VALUE:
setCanvasValue("OutputCY");
break;
default:
break;
}
}
void MacroActionOBSSettingsEdit::SetWidgetVisibility()
{
if (!_entryData) {
return;
}
const bool isFPSSelection =
_entryData->_action ==
MacroActionOBSSettings::Action::FPS_TYPE ||
_entryData->_action ==
MacroActionOBSSettings::Action::FPS_COMMON_VALUE ||
_entryData->_action ==
MacroActionOBSSettings::Action::FPS_INT_VALUE ||
_entryData->_action ==
MacroActionOBSSettings::Action::FPS_DEN_VALUE ||
_entryData->_action ==
MacroActionOBSSettings::Action::FPS_NUM_VALUE;
_fpsType->setVisible(_entryData->_action ==
MacroActionOBSSettings::Action::FPS_TYPE);
_fpsIntValue->setVisible(
_entryData->_action ==
MacroActionOBSSettings::Action::FPS_INT_VALUE ||
_entryData->_action ==
MacroActionOBSSettings::Action::FPS_NUM_VALUE ||
_entryData->_action ==
MacroActionOBSSettings::Action::FPS_DEN_VALUE);
_fpsStringValue->setVisible(
_entryData->_action ==
MacroActionOBSSettings::Action::FPS_COMMON_VALUE);
_canvasSizeValue->setVisible(!isFPSSelection);
_getCurrentValue->setVisible(_entryData->_action !=
MacroActionOBSSettings::Action::FPS_TYPE);
}
void MacroActionOBSSettingsEdit::ActionChanged(int idx)
{
GUARD_LOADING_AND_LOCK();
_entryData->_action = static_cast<MacroActionOBSSettings::Action>(
_actions->itemData(idx).toInt());
SetWidgetVisibility();
}
} // namespace advss

View File

@@ -0,0 +1,90 @@
#pragma once
#include "macro-action-edit.hpp"
#include "variable-spinbox.hpp"
#include "variable-line-edit.hpp"
#include <QComboBox>
#include <QHBoxLayout>
#include <chrono>
namespace advss {
class MacroActionOBSSettings : public MacroAction {
public:
MacroActionOBSSettings(Macro *m) : MacroAction(m) {}
bool PerformAction();
void LogAction() const;
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetId() const { return id; };
static std::shared_ptr<MacroAction> Create(Macro *m);
std::shared_ptr<MacroAction> Copy() const;
void ResolveVariablesToFixedValues();
enum class Action {
FPS_TYPE,
FPS_COMMON_VALUE,
FPS_INT_VALUE,
FPS_NUM_VALUE,
FPS_DEN_VALUE,
BASE_CANVAS_X_VALUE,
BASE_CANVAS_Y_VALUE,
OUTPUT_X_VALUE,
OUTPUT_Y_VALUE,
};
Action _action = Action::FPS_COMMON_VALUE;
enum class FPSType {
COMMON,
INTEGER,
FRACTION,
};
FPSType _fpsType = FPSType::INTEGER;
IntVariable _fpsIntValue = 30;
StringVariable _fpsStringValue = "24 NTSC";
IntVariable _canvasSizeValue = 1920;
private:
static bool _registered;
static const std::string id;
};
class MacroActionOBSSettingsEdit : public QWidget {
Q_OBJECT
public:
MacroActionOBSSettingsEdit(
QWidget *parent,
std::shared_ptr<MacroActionOBSSettings> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionOBSSettingsEdit(
parent,
std::dynamic_pointer_cast<MacroActionOBSSettings>(
action));
}
private slots:
void ActionChanged(int);
void FPSTypeChanged(int);
void FPSIntValueChanged(const NumberVariable<int> &);
void FPSStringValueChanged();
void CanvasSizeValueChanged(const NumberVariable<int> &);
void GetCurrentValueClicked();
private:
void SetWidgetVisibility();
QComboBox *_actions;
QComboBox *_fpsType;
VariableSpinBox *_fpsIntValue;
VariableLineEdit *_fpsStringValue;
VariableSpinBox *_canvasSizeValue;
QPushButton *_getCurrentValue;
std::shared_ptr<MacroActionOBSSettings> _entryData;
bool _loading = true;
};
} // namespace advss

View File

@@ -1,6 +1,5 @@
#include "macro-action-profile.hpp" #include "macro-action-profile.hpp"
#include "layout-helpers.hpp" #include "layout-helpers.hpp"
#include "profile-helpers.hpp"
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
@@ -55,20 +54,16 @@ std::shared_ptr<MacroAction> MacroActionProfile::Copy() const
MacroActionProfileEdit::MacroActionProfileEdit( MacroActionProfileEdit::MacroActionProfileEdit(
QWidget *parent, std::shared_ptr<MacroActionProfile> entryData) QWidget *parent, std::shared_ptr<MacroActionProfile> entryData)
: QWidget(parent) : QWidget(parent),
_profiles(new ProfileSelectionWidget(this))
{ {
_profiles = new QComboBox();
PopulateProfileSelection(_profiles);
QWidget::connect(_profiles, SIGNAL(currentTextChanged(const QString &)), QWidget::connect(_profiles, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(ProfileChanged(const QString &))); this, SLOT(ProfileChanged(const QString &)));
QHBoxLayout *mainLayout = new QHBoxLayout; auto layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{profiles}}", _profiles},
};
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.profile.entry"), PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.profile.entry"),
mainLayout, widgetPlaceholders); layout, {{"{{profiles}}", _profiles}});
setLayout(mainLayout); setLayout(layout);
_entryData = entryData; _entryData = entryData;
UpdateEntryData(); UpdateEntryData();
@@ -86,11 +81,7 @@ void MacroActionProfileEdit::UpdateEntryData()
void MacroActionProfileEdit::ProfileChanged(const QString &text) void MacroActionProfileEdit::ProfileChanged(const QString &text)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_profile = text.toStdString(); _entryData->_profile = text.toStdString();
emit HeaderInfoChanged( emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc())); QString::fromStdString(_entryData->GetShortDesc()));

View File

@@ -1,7 +1,6 @@
#pragma once #pragma once
#include "macro-action-edit.hpp" #include "macro-action-edit.hpp"
#include "profile-helpers.hpp"
#include <QComboBox>
namespace advss { namespace advss {
@@ -45,11 +44,10 @@ private slots:
signals: signals:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
protected:
QComboBox *_profiles;
std::shared_ptr<MacroActionProfile> _entryData;
private: private:
ProfileSelectionWidget *_profiles;
std::shared_ptr<MacroActionProfile> _entryData;
bool _loading = true; bool _loading = true;
}; };

View File

@@ -1,6 +1,5 @@
#include "macro-action-projector.hpp" #include "macro-action-projector.hpp"
#include "layout-helpers.hpp" #include "layout-helpers.hpp"
#include "monitor-helpers.hpp"
#include "selection-helpers.hpp" #include "selection-helpers.hpp"
#include "source-helpers.hpp" #include "source-helpers.hpp"
@@ -230,6 +229,13 @@ static void populateWindowTypes(QComboBox *list)
"AdvSceneSwitcher.action.projector.fullscreen")); "AdvSceneSwitcher.action.projector.fullscreen"));
} }
static QStringList getSourcesList()
{
auto sources = GetSourceNames();
sources.sort();
return sources;
}
MacroActionProjectorEdit::MacroActionProjectorEdit( MacroActionProjectorEdit::MacroActionProjectorEdit(
QWidget *parent, std::shared_ptr<MacroActionProjector> entryData) QWidget *parent, std::shared_ptr<MacroActionProjector> entryData)
: QWidget(parent), : QWidget(parent),
@@ -238,8 +244,8 @@ MacroActionProjectorEdit::MacroActionProjectorEdit(
_windowTypes(new QComboBox()), _windowTypes(new QComboBox()),
_scenes(new SceneSelectionWidget(window(), true, false, true, true, _scenes(new SceneSelectionWidget(window(), true, false, true, true,
true)), true)),
_sources(new SourceSelectionWidget(window(), QStringList(), true)), _sources(new SourceSelectionWidget(window(), getSourcesList, true)),
_monitors(new QComboBox()), _monitors(new MonitorSelectionWidget(this)),
_projectorWindowName(new VariableLineEdit(this)), _projectorWindowName(new VariableLineEdit(this)),
_regex(new RegexConfigWidget(this)), _regex(new RegexConfigWidget(this)),
_layout(new QHBoxLayout(this)) _layout(new QHBoxLayout(this))
@@ -247,12 +253,11 @@ MacroActionProjectorEdit::MacroActionProjectorEdit(
populateActionSelection(_actions); populateActionSelection(_actions);
populateWindowTypes(_windowTypes); populateWindowTypes(_windowTypes);
populateSelectionTypes(_types); populateSelectionTypes(_types);
auto sources = GetSourceNames();
sources.sort();
_sources->SetSourceNameList(sources);
_monitors->addItems(GetMonitorNames()); _monitors->addItems(GetMonitorNames());
_monitors->setPlaceholderText( _monitors->setPlaceholderText(
obs_module_text("AdvSceneSwitcher.selectDisplay")); obs_module_text("AdvSceneSwitcher.selectDisplay"));
_monitors->SetAllowUnmatchedSelection(false);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int))); SLOT(ActionChanged(int)));

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "macro-action-edit.hpp" #include "macro-action-edit.hpp"
#include "monitor-helpers.hpp"
#include "regex-config.hpp" #include "regex-config.hpp"
#include "scene-selection.hpp" #include "scene-selection.hpp"
#include "source-selection.hpp" #include "source-selection.hpp"
@@ -88,7 +89,7 @@ private:
QComboBox *_windowTypes; QComboBox *_windowTypes;
SceneSelectionWidget *_scenes; SceneSelectionWidget *_scenes;
SourceSelectionWidget *_sources; SourceSelectionWidget *_sources;
QComboBox *_monitors; MonitorSelectionWidget *_monitors;
VariableLineEdit *_projectorWindowName; VariableLineEdit *_projectorWindowName;
RegexConfigWidget *_regex; RegexConfigWidget *_regex;
QHBoxLayout *_layout; QHBoxLayout *_layout;

View File

@@ -193,12 +193,19 @@ static void populateTargetTypeSelection(QComboBox *list)
list->addItem(obs_module_text("AdvSceneSwitcher.OBSVideoOutput")); list->addItem(obs_module_text("AdvSceneSwitcher.OBSVideoOutput"));
} }
static QStringList getVideoSourcesList()
{
auto sources = GetVideoSourceNames();
sources.sort();
return sources;
}
MacroActionScreenshotEdit::MacroActionScreenshotEdit( MacroActionScreenshotEdit::MacroActionScreenshotEdit(
QWidget *parent, std::shared_ptr<MacroActionScreenshot> entryData) QWidget *parent, std::shared_ptr<MacroActionScreenshot> entryData)
: QWidget(parent), : QWidget(parent),
_scenes(new SceneSelectionWidget(this, true, false, true, true, _scenes(new SceneSelectionWidget(this, true, false, true, true,
true)), true)),
_sources(new SourceSelectionWidget(this, QStringList(), true)), _sources(new SourceSelectionWidget(this, getVideoSourcesList, true)),
_saveType(new QComboBox()), _saveType(new QComboBox()),
_targetType(new QComboBox()), _targetType(new QComboBox()),
_savePath(new FileSelection(FileSelection::Type::WRITE, this)), _savePath(new FileSelection(FileSelection::Type::WRITE, this)),
@@ -206,9 +213,6 @@ MacroActionScreenshotEdit::MacroActionScreenshotEdit(
{ {
setToolTip(obs_module_text( setToolTip(obs_module_text(
"AdvSceneSwitcher.action.screenshot.blackscreenNote")); "AdvSceneSwitcher.action.screenshot.blackscreenNote"));
auto sources = GetVideoSourceNames();
sources.sort();
_sources->SetSourceNameList(sources);
populateSaveTypeSelection(_saveType); populateSaveTypeSelection(_saveType);
populateTargetTypeSelection(_targetType); populateTargetTypeSelection(_targetType);

View File

@@ -274,10 +274,17 @@ static inline void populateModeSelection(QComboBox *list,
} }
} }
static QStringList getSourcesList()
{
auto sources = GetSourceNames();
sources.sort();
return sources;
}
MacroActionSourceEdit::MacroActionSourceEdit( MacroActionSourceEdit::MacroActionSourceEdit(
QWidget *parent, std::shared_ptr<MacroActionSource> entryData) QWidget *parent, std::shared_ptr<MacroActionSource> entryData)
: QWidget(parent), : QWidget(parent),
_sources(new SourceSelectionWidget(this, QStringList(), true)), _sources(new SourceSelectionWidget(this, getSourcesList, true)),
_actions(new QComboBox()), _actions(new QComboBox()),
_settingsButtons(new SourceSettingsButtonSelection(this)), _settingsButtons(new SourceSettingsButtonSelection(this)),
_settingsLayout(new QHBoxLayout()), _settingsLayout(new QHBoxLayout()),
@@ -296,9 +303,7 @@ MacroActionSourceEdit::MacroActionSourceEdit(
obs_module_text("AdvSceneSwitcher.action.source.refresh"))) obs_module_text("AdvSceneSwitcher.action.source.refresh")))
{ {
populateActionSelection(_actions); populateActionSelection(_actions);
auto sources = GetSourceNames();
sources.sort();
_sources->SetSourceNameList(sources);
populateModeSelection(_deinterlaceMode, deinterlaceModes); populateModeSelection(_deinterlaceMode, deinterlaceModes);
populateModeSelection(_deinterlaceOrder, deinterlaceFieldOrders); populateModeSelection(_deinterlaceOrder, deinterlaceFieldOrders);
populateModeSelection(_settingsInputMethods, inputMethods); populateModeSelection(_settingsInputMethods, inputMethods);

View File

@@ -163,15 +163,14 @@ MacroActionWindowEdit::MacroActionWindowEdit(
QWidget *parent, std::shared_ptr<MacroActionWindow> entryData) QWidget *parent, std::shared_ptr<MacroActionWindow> entryData)
: QWidget(parent), : QWidget(parent),
_actions(new QComboBox()), _actions(new QComboBox()),
_windows(new QComboBox()), _windows(new WindowSelectionWidget(this)),
_regex(new RegexConfigWidget(this)), _regex(new RegexConfigWidget(this)),
_infoLayout(new QHBoxLayout()) _infoLayout(new QHBoxLayout())
{ {
populateActionSelection(_actions); populateActionSelection(_actions);
_windows->setEditable(true); _windows->setToolTip(
_windows->setMaxVisibleItems(20); obs_module_text("AdvSceneSwitcher.tooltip.availableVariables"));
PopulateWindowSelection(_windows);
auto focusLimitation = new QLabel(obs_module_text( auto focusLimitation = new QLabel(obs_module_text(
"AdvSceneSwitcher.action.window.type.setFocusWindow.limitation")); "AdvSceneSwitcher.action.window.type.setFocusWindow.limitation"));
@@ -211,19 +210,17 @@ void MacroActionWindowEdit::UpdateEntryData()
if (!_entryData) { if (!_entryData) {
return; return;
} }
_actions->setCurrentIndex(static_cast<int>(_entryData->_action)); _actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_windows->setCurrentText(QString::fromStdString(_entryData->_window)); _windows->setCurrentText(
QString::fromStdString(_entryData->_window.UnresolvedValue()));
_regex->SetRegexConfig(_entryData->_regex); _regex->SetRegexConfig(_entryData->_regex);
SetWidgetVisibility(); SetWidgetVisibility();
} }
void MacroActionWindowEdit::WindowChanged(const QString &text) void MacroActionWindowEdit::WindowChanged(const QString &text)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_window = text.toStdString(); _entryData->_window = text.toStdString();
emit HeaderInfoChanged( emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc())); QString::fromStdString(_entryData->GetShortDesc()));
@@ -231,11 +228,7 @@ void MacroActionWindowEdit::WindowChanged(const QString &text)
void MacroActionWindowEdit::RegexChanged(const RegexConfig &regex) void MacroActionWindowEdit::RegexChanged(const RegexConfig &regex)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_regex = regex; _entryData->_regex = regex;
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();
@@ -252,11 +245,7 @@ void MacroActionWindowEdit::SetWidgetVisibility()
void MacroActionWindowEdit::ActionChanged(int value) void MacroActionWindowEdit::ActionChanged(int value)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_action = static_cast<MacroActionWindow::Action>(value); _entryData->_action = static_cast<MacroActionWindow::Action>(value);
SetWidgetVisibility(); SetWidgetVisibility();
} }

View File

@@ -2,6 +2,7 @@
#include "macro-action-edit.hpp" #include "macro-action-edit.hpp"
#include "regex-config.hpp" #include "regex-config.hpp"
#include "variable-string.hpp" #include "variable-string.hpp"
#include "window-selection.hpp"
namespace advss { namespace advss {
@@ -62,7 +63,7 @@ private:
void SetWidgetVisibility(); void SetWidgetVisibility();
QComboBox *_actions; QComboBox *_actions;
QComboBox *_windows; WindowSelectionWidget *_windows;
RegexConfigWidget *_regex; RegexConfigWidget *_regex;
QHBoxLayout *_infoLayout; QHBoxLayout *_infoLayout;

View File

@@ -292,8 +292,8 @@ bool MacroConditionAudio::Save(obs_data_t *obj) const
return true; return true;
} }
obs_volmeter_t *AddVolmeterToSource(MacroConditionAudio *entry, static obs_volmeter_t *addVolmeterToSource(MacroConditionAudio *entry,
obs_weak_source *source) obs_weak_source *source)
{ {
obs_volmeter_t *volmeter = obs_volmeter_create(OBS_FADER_LOG); obs_volmeter_t *volmeter = obs_volmeter_create(OBS_FADER_LOG);
obs_volmeter_add_callback(volmeter, MacroConditionAudio::SetVolumeLevel, obs_volmeter_add_callback(volmeter, MacroConditionAudio::SetVolumeLevel,
@@ -328,7 +328,7 @@ bool MacroConditionAudio::Load(obs_data_t *obj)
obs_data_get_int(obj, "outputCondition")); obs_data_get_int(obj, "outputCondition"));
_volumeCondition = static_cast<VolumeCondition>( _volumeCondition = static_cast<VolumeCondition>(
obs_data_get_int(obj, "volumeCondition")); obs_data_get_int(obj, "volumeCondition"));
_volmeter = AddVolmeterToSource(this, _audioSource.GetSource()); _volmeter = addVolmeterToSource(this, _audioSource.GetSource());
if (obs_data_get_int(obj, "version") < 2) { if (obs_data_get_int(obj, "version") < 2) {
// Set default values for dB handling // Set default values for dB handling
@@ -379,7 +379,7 @@ void MacroConditionAudio::ResetVolmeter()
obs_volmeter_remove_callback(_volmeter, SetVolumeLevel, this); obs_volmeter_remove_callback(_volmeter, SetVolumeLevel, this);
obs_volmeter_destroy(_volmeter); obs_volmeter_destroy(_volmeter);
_volmeter = AddVolmeterToSource(this, _audioSource.GetSource()); _volmeter = addVolmeterToSource(this, _audioSource.GetSource());
} }
void MacroConditionAudio::SetupTempVars() void MacroConditionAudio::SetupTempVars()
@@ -458,11 +458,18 @@ static inline void populateVolumeConditionSelection(QComboBox *list)
} }
} }
static QStringList getAudioSourcesList()
{
auto sources = GetAudioSourceNames();
sources.sort();
return sources;
}
MacroConditionAudioEdit::MacroConditionAudioEdit( MacroConditionAudioEdit::MacroConditionAudioEdit(
QWidget *parent, std::shared_ptr<MacroConditionAudio> entryData) QWidget *parent, std::shared_ptr<MacroConditionAudio> entryData)
: QWidget(parent), : QWidget(parent),
_checkTypes(new QComboBox()), _checkTypes(new QComboBox()),
_sources(new SourceSelectionWidget(this, QStringList(), true)), _sources(new SourceSelectionWidget(this, getAudioSourcesList, true)),
_condition(new QComboBox()), _condition(new QComboBox()),
_volumePercent(new VariableDoubleSpinBox()), _volumePercent(new VariableDoubleSpinBox()),
_volumeDB(new VariableDoubleSpinBox), _volumeDB(new VariableDoubleSpinBox),
@@ -484,10 +491,6 @@ MacroConditionAudioEdit::MacroConditionAudioEdit(
_syncOffset->setMaximum(20000); _syncOffset->setMaximum(20000);
_syncOffset->setSuffix("ms"); _syncOffset->setSuffix("ms");
auto sources = GetAudioSourceNames();
sources.sort();
_sources->SetSourceNameList(sources);
QWidget::connect(_checkTypes, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_checkTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(CheckTypeChanged(int))); SLOT(CheckTypeChanged(int)));
QWidget::connect( QWidget::connect(

View File

@@ -52,24 +52,26 @@ bool MacroConditionCursor::CheckClick()
bool MacroConditionCursor::CheckCondition() bool MacroConditionCursor::CheckCondition()
{ {
bool ret = false; bool ret = false;
std::pair<int, int> cursorPos = GetCursorPos(); const auto &[x, y] = GetCursorPos();
SetTempVarValue("x", std::to_string(x));
SetTempVarValue("y", std::to_string(y));
switch (_condition) { switch (_condition) {
case Condition::REGION: case Condition::REGION:
ret = cursorPos.first >= _minX && cursorPos.second >= _minY && ret = x >= _minX && y >= _minY && x <= _maxX && y <= _maxY;
cursorPos.first <= _maxX && cursorPos.second <= _maxY; SetVariableValue(std::to_string(x) + " " + std::to_string(y));
SetVariableValue(std::to_string(cursorPos.first) + " " +
std::to_string(cursorPos.second));
break; break;
case Condition::MOVING: case Condition::MOVING:
ret = cursorPos.first != _lastCursorPosition.first || ret = x != _lastX || y != _lastY;
cursorPos.second != _lastCursorPosition.second;
break; break;
case Condition::CLICK: case Condition::CLICK:
ret = CheckClick(); ret = CheckClick();
break; break;
} }
_lastCheckTime = std::chrono::high_resolution_clock::now(); _lastCheckTime = std::chrono::high_resolution_clock::now();
_lastCursorPosition = cursorPos; _lastX = x;
_lastY = y;
if (GetVariableValue().empty()) { if (GetVariableValue().empty()) {
SetVariableValue(ret ? "true" : "false"); SetVariableValue(ret ? "true" : "false");
@@ -109,6 +111,12 @@ bool MacroConditionCursor::Load(obs_data_t *obj)
return true; return true;
} }
void MacroConditionCursor::SetupTempVars()
{
AddTempvar("x", obs_module_text("AdvSceneSwitcher.tempVar.cursor.x"));
AddTempvar("y", obs_module_text("AdvSceneSwitcher.tempVar.cursor.y"));
}
static inline void populateConditionSelection(QComboBox *list) static inline void populateConditionSelection(QComboBox *list)
{ {
for (const auto &[condition, name] : cursorConditionTypes) { for (const auto &[condition, name] : cursorConditionTypes) {

View File

@@ -38,9 +38,12 @@ public:
NumberVariable<int> _minX = 0, _minY = 0, _maxX = 0, _maxY = 0; NumberVariable<int> _minX = 0, _minY = 0, _maxX = 0, _maxY = 0;
private: private:
void SetupTempVars();
bool CheckClick(); bool CheckClick();
std::chrono::high_resolution_clock::time_point _lastCheckTime{}; std::chrono::high_resolution_clock::time_point _lastCheckTime{};
std::pair<int, int> _lastCursorPosition; int _lastX = 0;
int _lastY = 0;
static bool _registered; static bool _registered;
static const std::string id; static const std::string id;

View File

@@ -1,6 +1,5 @@
#include "macro-condition-display.hpp" #include "macro-condition-display.hpp"
#include "layout-helpers.hpp" #include "layout-helpers.hpp"
#include "monitor-helpers.hpp"
#include <QGuiApplication> #include <QGuiApplication>
#include <QScreen> #include <QScreen>
@@ -254,7 +253,7 @@ MacroConditionDisplayEdit::MacroConditionDisplayEdit(
: QWidget(parent), : QWidget(parent),
_conditions(new QComboBox()), _conditions(new QComboBox()),
_compareModes(new QComboBox()), _compareModes(new QComboBox()),
_displays(new QComboBox()), _displays(new MonitorSelectionWidget(this)),
_regex(new RegexConfigWidget()), _regex(new RegexConfigWidget()),
_displayCount(new VariableSpinBox(this)), _displayCount(new VariableSpinBox(this)),
_displayWidth(new VariableSpinBox(this)), _displayWidth(new VariableSpinBox(this)),
@@ -264,8 +263,6 @@ MacroConditionDisplayEdit::MacroConditionDisplayEdit(
{ {
populateConditionSelection(_conditions); populateConditionSelection(_conditions);
populateCompareModeselection(_compareModes); populateCompareModeselection(_compareModes);
_displays->addItems(GetMonitorNames());
_displays->setEditable(true);
_displayWidth->setMaximum(99999); _displayWidth->setMaximum(99999);
_displayHeight->setMaximum(99999); _displayHeight->setMaximum(99999);

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "macro-condition-edit.hpp" #include "macro-condition-edit.hpp"
#include "monitor-helpers.hpp"
#include "variable-line-edit.hpp" #include "variable-line-edit.hpp"
#include "variable-spinbox.hpp" #include "variable-spinbox.hpp"
#include "regex-config.hpp" #include "regex-config.hpp"
@@ -80,7 +81,7 @@ private:
QComboBox *_conditions; QComboBox *_conditions;
QComboBox *_compareModes; QComboBox *_compareModes;
QComboBox *_displays; MonitorSelectionWidget *_displays;
RegexConfigWidget *_regex; RegexConfigWidget *_regex;
VariableSpinBox *_displayCount; VariableSpinBox *_displayCount;
VariableSpinBox *_displayWidth; VariableSpinBox *_displayWidth;

View File

@@ -185,10 +185,17 @@ static inline void populateConditionSelection(QComboBox *list)
} }
} }
static QStringList getFilterSourcesList()
{
auto sources = GetSourcesWithFilterNames();
sources.sort();
return sources;
}
MacroConditionFilterEdit::MacroConditionFilterEdit( MacroConditionFilterEdit::MacroConditionFilterEdit(
QWidget *parent, std::shared_ptr<MacroConditionFilter> entryData) QWidget *parent, std::shared_ptr<MacroConditionFilter> entryData)
: QWidget(parent), : QWidget(parent),
_sources(new SourceSelectionWidget(this, QStringList(), true)), _sources(new SourceSelectionWidget(this, getFilterSourcesList, true)),
_filters(new FilterSelectionWidget(this, _sources, true)), _filters(new FilterSelectionWidget(this, _sources, true)),
_conditions(new QComboBox()), _conditions(new QComboBox()),
_getSettings(new QPushButton(obs_module_text( _getSettings(new QPushButton(obs_module_text(
@@ -200,9 +207,7 @@ MacroConditionFilterEdit::MacroConditionFilterEdit(
obs_module_text("AdvSceneSwitcher.condition.filter.refresh"))) obs_module_text("AdvSceneSwitcher.condition.filter.refresh")))
{ {
populateConditionSelection(_conditions); populateConditionSelection(_conditions);
auto sources = GetSourcesWithFilterNames();
sources.sort();
_sources->SetSourceNameList(sources);
_refreshSettingSelection->setToolTip(obs_module_text( _refreshSettingSelection->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.filter.refresh.tooltip")); "AdvSceneSwitcher.condition.filter.refresh.tooltip"));

View File

@@ -141,7 +141,7 @@ void MacroConditionGameCapture::SetupInitialState(obs_source_t *source)
calldata_free(&cd); calldata_free(&cd);
} }
static QStringList getGameCaptureSourceNames() static QStringList getGameCaptureSourcesList()
{ {
auto sourceEnum = [](void *param, obs_source_t *source) -> bool /* -- */ auto sourceEnum = [](void *param, obs_source_t *source) -> bool /* -- */
{ {
@@ -156,18 +156,16 @@ static QStringList getGameCaptureSourceNames()
QStringList list; QStringList list;
obs_enum_sources(sourceEnum, &list); obs_enum_sources(sourceEnum, &list);
list.sort();
return list; return list;
} }
MacroConditionGameCaptureEdit::MacroConditionGameCaptureEdit( MacroConditionGameCaptureEdit::MacroConditionGameCaptureEdit(
QWidget *parent, std::shared_ptr<MacroConditionGameCapture> entryData) QWidget *parent, std::shared_ptr<MacroConditionGameCapture> entryData)
: QWidget(parent), : QWidget(parent),
_sources(new SourceSelectionWidget(this, QStringList(), true)) _sources(new SourceSelectionWidget(this, getGameCaptureSourcesList,
true))
{ {
auto sources = getGameCaptureSourceNames();
sources.sort();
_sources->SetSourceNameList(sources);
QWidget::connect(_sources, QWidget::connect(_sources,
SIGNAL(SourceChanged(const SourceSelection &)), this, SIGNAL(SourceChanged(const SourceSelection &)), this,
SLOT(SourceChanged(const SourceSelection &))); SLOT(SourceChanged(const SourceSelection &)));

View File

@@ -256,7 +256,7 @@ void MacroConditionMedia::SetTempVarValues(
const auto state = std::get<obs_media_state>(value); const auto state = std::get<obs_media_state>(value);
SetTempVarValue("state", std::to_string(state)); SetTempVarValue("state", std::to_string(state));
} else { } else {
const auto timeInfo = std::get<MediaTimeInfo>(value); const auto &timeInfo = std::get<MediaTimeInfo>(value);
SetTempVarValue("time", std::to_string(timeInfo.time)); SetTempVarValue("time", std::to_string(timeInfo.time));
SetTempVarValue("duration", std::to_string(timeInfo.duration)); SetTempVarValue("duration", std::to_string(timeInfo.duration));
} }
@@ -610,6 +610,13 @@ static void populateCheckTypes(QComboBox *list)
static_cast<int>(MacroConditionMedia::CheckType::TIME)); static_cast<int>(MacroConditionMedia::CheckType::TIME));
} }
static QStringList getMediaSourcesList()
{
auto sources = GetMediaSourceNames();
sources.sort();
return sources;
}
MacroConditionMediaEdit::MacroConditionMediaEdit( MacroConditionMediaEdit::MacroConditionMediaEdit(
QWidget *parent, std::shared_ptr<MacroConditionMedia> entryData) QWidget *parent, std::shared_ptr<MacroConditionMedia> entryData)
: QWidget(parent), : QWidget(parent),
@@ -617,7 +624,7 @@ MacroConditionMediaEdit::MacroConditionMediaEdit(
_checkTypes(new QComboBox()), _checkTypes(new QComboBox()),
_scenes(new SceneSelectionWidget(window(), true, true, true, true, _scenes(new SceneSelectionWidget(window(), true, true, true, true,
true)), true)),
_sources(new SourceSelectionWidget(this, QStringList(), true)), _sources(new SourceSelectionWidget(this, getMediaSourcesList, true)),
_states(new QComboBox()), _states(new QComboBox()),
_timeRestrictions(new QComboBox()), _timeRestrictions(new QComboBox()),
_time(new DurationSelection()) _time(new DurationSelection())
@@ -625,10 +632,6 @@ MacroConditionMediaEdit::MacroConditionMediaEdit(
_states->setToolTip(obs_module_text( _states->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.media.inconsistencyInfo")); "AdvSceneSwitcher.condition.media.inconsistencyInfo"));
auto sources = GetMediaSourceNames();
sources.sort();
_sources->SetSourceNameList(sources);
QWidget::connect(_sourceTypes, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_sourceTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(SourceTypeChanged(int))); SLOT(SourceTypeChanged(int)));
QWidget::connect(_checkTypes, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_checkTypes, SIGNAL(currentIndexChanged(int)), this,

View File

@@ -60,7 +60,7 @@ bool MacroConditionProcess::CheckCondition()
bool MacroConditionProcess::Save(obs_data_t *obj) const bool MacroConditionProcess::Save(obs_data_t *obj) const
{ {
MacroCondition::Save(obj); MacroCondition::Save(obj);
obs_data_set_string(obj, "process", _process.c_str()); _process.Save(obj, "process");
obs_data_set_bool(obj, "focus", _checkFocus); obs_data_set_bool(obj, "focus", _checkFocus);
_regex.Save(obj); _regex.Save(obj);
obs_data_set_int(obj, "version", 1); obs_data_set_int(obj, "version", 1);
@@ -70,7 +70,7 @@ bool MacroConditionProcess::Save(obs_data_t *obj) const
bool MacroConditionProcess::Load(obs_data_t *obj) bool MacroConditionProcess::Load(obs_data_t *obj)
{ {
MacroCondition::Load(obj); MacroCondition::Load(obj);
_process = obs_data_get_string(obj, "process"); _process.Load(obj, "process");
_checkFocus = obs_data_get_bool(obj, "focus"); _checkFocus = obs_data_get_bool(obj, "focus");
// Fall back to partial match regex as default for old version // Fall back to partial match regex as default for old version
if (!obs_data_has_user_value(obj, "version")) { if (!obs_data_has_user_value(obj, "version")) {
@@ -104,6 +104,8 @@ MacroConditionProcessEdit::MacroConditionProcessEdit(
{ {
_processSelection->setEditable(true); _processSelection->setEditable(true);
_processSelection->setMaxVisibleItems(20); _processSelection->setMaxVisibleItems(20);
_processSelection->setToolTip(
obs_module_text("AdvSceneSwitcher.tooltip.availableVariables"));
QWidget::connect(_processSelection, QWidget::connect(_processSelection,
SIGNAL(currentTextChanged(const QString &)), this, SIGNAL(currentTextChanged(const QString &)), this,
@@ -118,7 +120,7 @@ MacroConditionProcessEdit::MacroConditionProcessEdit(
PopulateProcessSelection(_processSelection); PopulateProcessSelection(_processSelection);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = { const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{processes}}", _processSelection}, {"{{processes}}", _processSelection},
{"{{regex}}", _regex}, {"{{regex}}", _regex},
{"{{focused}}", _focused}, {"{{focused}}", _focused},
@@ -146,23 +148,24 @@ MacroConditionProcessEdit::MacroConditionProcessEdit(
void MacroConditionProcessEdit::ProcessChanged(const QString &text) void MacroConditionProcessEdit::ProcessChanged(const QString &text)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_process = text.toStdString(); _entryData->_process = text.toStdString();
emit HeaderInfoChanged( emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc())); QString::fromStdString(_entryData->GetShortDesc()));
} }
void MacroConditionProcessEdit::showEvent(QShowEvent *event)
{
const QSignalBlocker b(_processSelection);
const auto text = _processSelection->currentText();
_processSelection->clear();
PopulateProcessSelection(_processSelection);
_processSelection->setCurrentText(text);
}
void MacroConditionProcessEdit::RegexChanged(const RegexConfig &conf) void MacroConditionProcessEdit::RegexChanged(const RegexConfig &conf)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_regex = conf; _entryData->_regex = conf;
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();
@@ -170,11 +173,7 @@ void MacroConditionProcessEdit::RegexChanged(const RegexConfig &conf)
void MacroConditionProcessEdit::FocusChanged(int state) void MacroConditionProcessEdit::FocusChanged(int state)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_checkFocus = state; _entryData->_checkFocus = state;
SetWidgetVisibility(); SetWidgetVisibility();
} }
@@ -193,6 +192,7 @@ void MacroConditionProcessEdit::SetWidgetVisibility()
} }
SetLayoutVisible(_focusLayout, _entryData->_checkFocus); SetLayoutVisible(_focusLayout, _entryData->_checkFocus);
adjustSize(); adjustSize();
updateGeometry();
} }
void MacroConditionProcessEdit::UpdateEntryData() void MacroConditionProcessEdit::UpdateEntryData()
@@ -201,7 +201,8 @@ void MacroConditionProcessEdit::UpdateEntryData()
return; return;
} }
_processSelection->setCurrentText(_entryData->_process.c_str()); _processSelection->setCurrentText(
_entryData->_process.UnresolvedValue().c_str());
_regex->SetRegexConfig(_entryData->_regex); _regex->SetRegexConfig(_entryData->_regex);
_focused->setChecked(_entryData->_checkFocus); _focused->setChecked(_entryData->_checkFocus);
SetWidgetVisibility(); SetWidgetVisibility();

View File

@@ -1,8 +1,8 @@
#pragma once #pragma once
#include "macro-condition-edit.hpp" #include "macro-condition-edit.hpp"
#include "regex-config.hpp" #include "regex-config.hpp"
#include "variable-string.hpp"
#include <QComboBox>
#include <QCheckBox> #include <QCheckBox>
namespace advss { namespace advss {
@@ -20,7 +20,7 @@ public:
return std::make_shared<MacroConditionProcess>(m); return std::make_shared<MacroConditionProcess>(m);
} }
std::string _process; StringVariable _process;
bool _checkFocus = true; bool _checkFocus = true;
RegexConfig _regex = RegexConfig::PartialMatchRegexConfig(); RegexConfig _regex = RegexConfig::PartialMatchRegexConfig();
@@ -47,6 +47,9 @@ public:
std::dynamic_pointer_cast<MacroConditionProcess>(cond)); std::dynamic_pointer_cast<MacroConditionProcess>(cond));
} }
protected:
void showEvent(QShowEvent *event) override;
private slots: private slots:
void ProcessChanged(const QString &text); void ProcessChanged(const QString &text);
void RegexChanged(const RegexConfig &); void RegexChanged(const RegexConfig &);

View File

@@ -1,6 +1,5 @@
#include "macro-condition-profile.hpp" #include "macro-condition-profile.hpp"
#include "layout-helpers.hpp" #include "layout-helpers.hpp"
#include "profile-helpers.hpp"
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
@@ -43,22 +42,16 @@ std::string MacroConditionProfile::GetShortDesc() const
MacroConditionProfileEdit::MacroConditionProfileEdit( MacroConditionProfileEdit::MacroConditionProfileEdit(
QWidget *parent, std::shared_ptr<MacroConditionProfile> entryData) QWidget *parent, std::shared_ptr<MacroConditionProfile> entryData)
: QWidget(parent), : QWidget(parent),
_profiles(new QComboBox()) _profiles(new ProfileSelectionWidget(this))
{ {
PopulateProfileSelection(_profiles);
QWidget::connect(_profiles, SIGNAL(currentTextChanged(const QString &)), QWidget::connect(_profiles, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(ProfileChanged(const QString &))); this, SLOT(ProfileChanged(const QString &)));
QHBoxLayout *mainLayout = new QHBoxLayout; auto layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{profiles}}", _profiles},
};
PlaceWidgets( PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.profile.entry"), obs_module_text("AdvSceneSwitcher.condition.profile.entry"),
mainLayout, widgetPlaceholders); layout, {{"{{profiles}}", _profiles}});
setLayout(mainLayout); setLayout(layout);
_entryData = entryData; _entryData = entryData;
UpdateEntryData(); UpdateEntryData();
@@ -67,11 +60,7 @@ MacroConditionProfileEdit::MacroConditionProfileEdit(
void MacroConditionProfileEdit::ProfileChanged(const QString &text) void MacroConditionProfileEdit::ProfileChanged(const QString &text)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_profile = text.toStdString(); _entryData->_profile = text.toStdString();
emit HeaderInfoChanged( emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc())); QString::fromStdString(_entryData->GetShortDesc()));

View File

@@ -1,8 +1,6 @@
#pragma once #pragma once
#include "macro-condition-edit.hpp" #include "macro-condition-edit.hpp"
#include "profile-helpers.hpp"
#include <QWidget>
#include <QComboBox>
namespace advss { namespace advss {
@@ -47,11 +45,10 @@ private slots:
signals: signals:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
protected:
QComboBox *_profiles;
std::shared_ptr<MacroConditionProfile> _entryData;
private: private:
ProfileSelectionWidget *_profiles;
std::shared_ptr<MacroConditionProfile> _entryData;
bool _loading = true; bool _loading = true;
}; };

View File

@@ -235,6 +235,7 @@ static QStringList getSlideshowNames()
QStringList list; QStringList list;
obs_enum_sources(sourceEnum, &list); obs_enum_sources(sourceEnum, &list);
list.sort();
return list; return list;
} }
@@ -244,7 +245,7 @@ MacroConditionSlideshowEdit::MacroConditionSlideshowEdit(
_conditions(new QComboBox(this)), _conditions(new QComboBox(this)),
_index(new VariableSpinBox(this)), _index(new VariableSpinBox(this)),
_path(new VariableLineEdit(this)), _path(new VariableLineEdit(this)),
_sources(new SourceSelectionWidget(this, QStringList(), true)), _sources(new SourceSelectionWidget(this, getSlideshowNames, true)),
_regex(new RegexConfigWidget(this)), _regex(new RegexConfigWidget(this)),
_layout(new QHBoxLayout()) _layout(new QHBoxLayout())
{ {
@@ -253,10 +254,6 @@ MacroConditionSlideshowEdit::MacroConditionSlideshowEdit(
_index->setMinimum(1); _index->setMinimum(1);
populateConditionSelection(_conditions); populateConditionSelection(_conditions);
auto sources = getSlideshowNames();
sources.sort();
_sources->SetSourceNameList(sources);
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int))); SLOT(ConditionChanged(int)));
QWidget::connect(_sources, QWidget::connect(_sources,

View File

@@ -229,10 +229,19 @@ static inline void populateSelection(QComboBox *list,
} }
} }
static QStringList getSourcesList()
{
auto sources = GetSourceNames();
sources.sort();
auto scenes = GetSceneNames();
scenes.sort();
return sources + scenes;
}
MacroConditionSourceEdit::MacroConditionSourceEdit( MacroConditionSourceEdit::MacroConditionSourceEdit(
QWidget *parent, std::shared_ptr<MacroConditionSource> entryData) QWidget *parent, std::shared_ptr<MacroConditionSource> entryData)
: QWidget(parent), : QWidget(parent),
_sources(new SourceSelectionWidget(this, QStringList(), true)), _sources(new SourceSelectionWidget(this, getSourcesList, true)),
_conditions(new QComboBox()), _conditions(new QComboBox()),
_getSettings(new QPushButton(obs_module_text( _getSettings(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.source.getSettings"))), "AdvSceneSwitcher.condition.source.getSettings"))),
@@ -246,11 +255,7 @@ MacroConditionSourceEdit::MacroConditionSourceEdit(
{ {
populateSelection(_conditions, sourceConditionTypes); populateSelection(_conditions, sourceConditionTypes);
populateSelection(_sizeCompareMethods, compareMethods); populateSelection(_sizeCompareMethods, compareMethods);
auto sources = GetSourceNames();
sources.sort();
auto scenes = GetSceneNames();
scenes.sort();
_sources->SetSourceNameList(sources + scenes);
_refreshSettingSelection->setToolTip(obs_module_text( _refreshSettingSelection->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.source.refresh.tooltip")); "AdvSceneSwitcher.condition.source.refresh.tooltip"));
_size->setMaximum(999999); _size->setMaximum(999999);

View File

@@ -407,15 +407,16 @@ void MacroConditionTransitionEdit::SetWidgetVisibility()
_entryData->_condition == _entryData->_condition ==
MacroConditionTransition::Condition::DURATION); MacroConditionTransition::Condition::DURATION);
bool addCurrent = _entryData->_condition == _transitions->EnableCurrentEntry(
MacroConditionTransition::Condition::DURATION; _entryData->_condition ==
bool addAny = _entryData->_condition == MacroConditionTransition::Condition::DURATION);
MacroConditionTransition::Condition::STARTED || _transitions->EnableAnyEntry(
_entryData->_condition == _entryData->_condition ==
MacroConditionTransition::Condition::ENDED || MacroConditionTransition::Condition::STARTED ||
_entryData->_condition == _entryData->_condition ==
MacroConditionTransition::Condition::VIDEO_ENDED; MacroConditionTransition::Condition::ENDED ||
_transitions->Repopulate(addCurrent, addAny); _entryData->_condition ==
MacroConditionTransition::Condition::VIDEO_ENDED);
} }
void MacroConditionTransitionEdit::UpdateEntryData() void MacroConditionTransitionEdit::UpdateEntryData()

View File

@@ -2,7 +2,6 @@
#include "layout-helpers.hpp" #include "layout-helpers.hpp"
#include "plugin-state-helpers.hpp" #include "plugin-state-helpers.hpp"
#include "platform-funcs.hpp" #include "platform-funcs.hpp"
#include "selection-helpers.hpp"
#include <regex> #include <regex>
@@ -193,7 +192,7 @@ void MacroConditionWindow::SetupTempVars()
MacroConditionWindowEdit::MacroConditionWindowEdit( MacroConditionWindowEdit::MacroConditionWindowEdit(
QWidget *parent, std::shared_ptr<MacroConditionWindow> entryData) QWidget *parent, std::shared_ptr<MacroConditionWindow> entryData)
: QWidget(parent), : QWidget(parent),
_windowSelection(new QComboBox()), _windowSelection(new WindowSelectionWidget(this)),
_windowRegex(new RegexConfigWidget(this)), _windowRegex(new RegexConfigWidget(this)),
_checkTitle(new QCheckBox()), _checkTitle(new QCheckBox()),
_fullscreen(new QCheckBox()), _fullscreen(new QCheckBox()),
@@ -206,14 +205,14 @@ MacroConditionWindowEdit::MacroConditionWindowEdit(
_focusWindow(new QLabel()), _focusWindow(new QLabel()),
_currentFocusLayout(new QHBoxLayout()) _currentFocusLayout(new QHBoxLayout())
{ {
_windowSelection->setEditable(true);
_windowSelection->setMaxVisibleItems(20);
_checkText->setToolTip(obs_module_text( _checkText->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.window.entry.text.note")); "AdvSceneSwitcher.condition.window.entry.text.note"));
_text->setToolTip(obs_module_text( _text->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.window.entry.text.note")); "AdvSceneSwitcher.condition.window.entry.text.note"));
_windowSelection->setToolTip(
obs_module_text("AdvSceneSwitcher.tooltip.availableVariables"));
QWidget::connect(_windowSelection, QWidget::connect(_windowSelection,
SIGNAL(currentTextChanged(const QString &)), this, SIGNAL(currentTextChanged(const QString &)), this,
SLOT(WindowChanged(const QString &))); SLOT(WindowChanged(const QString &)));
@@ -240,9 +239,7 @@ MacroConditionWindowEdit::MacroConditionWindowEdit(
QWidget::connect(&_timer, SIGNAL(timeout()), this, QWidget::connect(&_timer, SIGNAL(timeout()), this,
SLOT(UpdateFocusWindow())); SLOT(UpdateFocusWindow()));
PopulateWindowSelection(_windowSelection); const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{windows}}", _windowSelection}, {"{{windows}}", _windowSelection},
{"{{windowRegex}}", _windowRegex}, {"{{windowRegex}}", _windowRegex},
{"{{checkTitle}}", _checkTitle}, {"{{checkTitle}}", _checkTitle},
@@ -320,11 +317,7 @@ MacroConditionWindowEdit::MacroConditionWindowEdit(
void MacroConditionWindowEdit::WindowChanged(const QString &text) void MacroConditionWindowEdit::WindowChanged(const QString &text)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_window = text.toStdString(); _entryData->_window = text.toStdString();
emit HeaderInfoChanged( emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc())); QString::fromStdString(_entryData->GetShortDesc()));
@@ -332,11 +325,7 @@ void MacroConditionWindowEdit::WindowChanged(const QString &text)
void MacroConditionWindowEdit::WindowRegexChanged(const RegexConfig &conf) void MacroConditionWindowEdit::WindowRegexChanged(const RegexConfig &conf)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_windowRegex = conf; _entryData->_windowRegex = conf;
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();
@@ -344,11 +333,7 @@ void MacroConditionWindowEdit::WindowRegexChanged(const RegexConfig &conf)
void MacroConditionWindowEdit::CheckTitleChanged(int state) void MacroConditionWindowEdit::CheckTitleChanged(int state)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
const QSignalBlocker b1(_windowSelection); const QSignalBlocker b1(_windowSelection);
const QSignalBlocker b2(_windowRegex); const QSignalBlocker b2(_windowRegex);
if (!state) { if (!state) {
@@ -363,22 +348,14 @@ void MacroConditionWindowEdit::CheckTitleChanged(int state)
void MacroConditionWindowEdit::CheckTextChanged(int state) void MacroConditionWindowEdit::CheckTextChanged(int state)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_checkText = state; _entryData->_checkText = state;
SetWidgetVisibility(); SetWidgetVisibility();
} }
void MacroConditionWindowEdit::WindowTextChanged() void MacroConditionWindowEdit::WindowTextChanged()
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_text = _text->toPlainText().toStdString(); _entryData->_text = _text->toPlainText().toStdString();
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();
@@ -386,11 +363,7 @@ void MacroConditionWindowEdit::WindowTextChanged()
void MacroConditionWindowEdit::TextRegexChanged(const RegexConfig &conf) void MacroConditionWindowEdit::TextRegexChanged(const RegexConfig &conf)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_textRegex = conf; _entryData->_textRegex = conf;
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();
@@ -398,42 +371,26 @@ void MacroConditionWindowEdit::TextRegexChanged(const RegexConfig &conf)
void MacroConditionWindowEdit::FullscreenChanged(int state) void MacroConditionWindowEdit::FullscreenChanged(int state)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_fullscreen = state; _entryData->_fullscreen = state;
} }
void MacroConditionWindowEdit::MaximizedChanged(int state) void MacroConditionWindowEdit::MaximizedChanged(int state)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_maximized = state; _entryData->_maximized = state;
} }
void MacroConditionWindowEdit::FocusedChanged(int state) void MacroConditionWindowEdit::FocusedChanged(int state)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_focus = state; _entryData->_focus = state;
SetWidgetVisibility(); SetWidgetVisibility();
} }
void MacroConditionWindowEdit::WindowFocusChanged(int state) void MacroConditionWindowEdit::WindowFocusChanged(int state)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_windowFocusChanged = state; _entryData->_windowFocusChanged = state;
SetWidgetVisibility(); SetWidgetVisibility();
} }

View File

@@ -2,6 +2,7 @@
#include "macro-condition-edit.hpp" #include "macro-condition-edit.hpp"
#include "variable-text-edit.hpp" #include "variable-text-edit.hpp"
#include "regex-config.hpp" #include "regex-config.hpp"
#include "window-selection.hpp"
#include <QComboBox> #include <QComboBox>
#include <QCheckBox> #include <QCheckBox>
@@ -77,7 +78,7 @@ signals:
void HeaderInfoChanged(const QString &); void HeaderInfoChanged(const QString &);
protected: protected:
QComboBox *_windowSelection; WindowSelectionWidget *_windowSelection;
RegexConfigWidget *_windowRegex; RegexConfigWidget *_windowRegex;
QCheckBox *_checkTitle; QCheckBox *_checkTitle;
QCheckBox *_fullscreen; QCheckBox *_fullscreen;

View File

@@ -289,6 +289,13 @@ void FilterSelectionWidget::SetFilter(const SourceSelection &source,
_currentSelection = filter; _currentSelection = filter;
} }
void FilterSelectionWidget::showEvent(QShowEvent *event)
{
FilterComboBox::showEvent(event);
const QSignalBlocker b(this);
Reset();
}
void FilterSelectionWidget::SourceChanged(const SourceSelection &source) void FilterSelectionWidget::SourceChanged(const SourceSelection &source)
{ {
if (source == _source) { if (source == _source) {

View File

@@ -45,6 +45,9 @@ public:
bool addVariables = true); bool addVariables = true);
void SetFilter(const SourceSelection &, const FilterSelection &); void SetFilter(const SourceSelection &, const FilterSelection &);
protected:
void showEvent(QShowEvent *event) override;
signals: signals:
void FilterChanged(const FilterSelection &); void FilterChanged(const FilterSelection &);

View File

@@ -47,4 +47,23 @@ QStringList GetMonitorNames()
return monitorNames; return monitorNames;
} }
MonitorSelectionWidget::MonitorSelectionWidget(QWidget *parent)
: FilterComboBox(parent)
{
setEditable(true);
SetAllowUnmatchedSelection(true);
setMaxVisibleItems(20);
addItems(GetMonitorNames());
}
void MonitorSelectionWidget::showEvent(QShowEvent *event)
{
FilterComboBox::showEvent(event);
const QSignalBlocker b(this);
const auto text = currentText();
clear();
addItems(GetMonitorNames());
setCurrentText(text);
}
} // namespace advss } // namespace advss

View File

@@ -1,8 +1,18 @@
#pragma once #pragma once
#include <QStringList> #include "filter-combo-box.hpp"
namespace advss { namespace advss {
QStringList GetMonitorNames(); QStringList GetMonitorNames();
class MonitorSelectionWidget : public FilterComboBox {
Q_OBJECT
public:
MonitorSelectionWidget(QWidget *parent);
protected:
void showEvent(QShowEvent *event) override;
};
} // namespace advss } // namespace advss

View File

@@ -30,4 +30,23 @@ std::string GetPathInProfileDir(const char *filePath)
return result + "/" + filePath; return result + "/" + filePath;
} }
ProfileSelectionWidget::ProfileSelectionWidget(QWidget *parent)
: FilterComboBox(parent)
{
setEditable(true);
SetAllowUnmatchedSelection(true);
setMaxVisibleItems(20);
PopulateProfileSelection(this);
}
void ProfileSelectionWidget::showEvent(QShowEvent *event)
{
FilterComboBox::showEvent(event);
const QSignalBlocker b(this);
const auto text = currentText();
clear();
PopulateProfileSelection(this);
setCurrentText(text);
}
} // namespace advss } // namespace advss

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <QComboBox> #include "filter-combo-box.hpp"
#include <string> #include <string>
namespace advss { namespace advss {
@@ -7,4 +8,14 @@ namespace advss {
void PopulateProfileSelection(QComboBox *list); void PopulateProfileSelection(QComboBox *list);
std::string GetPathInProfileDir(const char *filePath); std::string GetPathInProfileDir(const char *filePath);
class ProfileSelectionWidget : public FilterComboBox {
Q_OBJECT
public:
ProfileSelectionWidget(QWidget *parent);
protected:
void showEvent(QShowEvent *event) override;
};
} // namespace advss } // namespace advss

View File

@@ -558,7 +558,7 @@ static bool populateSceneItemSelectionHelper(obs_scene_t *,
return true; return true;
} }
QStringList GetSceneItemsList(SceneSelection &s) static QStringList getSceneItemsList(SceneSelection &s)
{ {
QStringList names; QStringList names;
if (s.GetType() != SceneSelection::Type::SCENE) { if (s.GetType() != SceneSelection::Type::SCENE) {
@@ -589,8 +589,9 @@ QStringList GetSceneItemsList(SceneSelection &s)
void SceneItemSelectionWidget::PopulateItemSelection() void SceneItemSelectionWidget::PopulateItemSelection()
{ {
const QStringList sceneItmes = GetSceneItemsList(_scene); _sources->clear();
AddSelectionGroup(_sources, sceneItmes, false); const QStringList sceneItems = getSceneItemsList(_scene);
AddSelectionGroup(_sources, sceneItems, false);
_sources->setCurrentIndex(-1); _sources->setCurrentIndex(-1);
} }
@@ -759,8 +760,8 @@ void SceneItemSelectionWidget::SetSceneItem(const SceneItemSelection &item)
idx += 1; idx += 1;
} }
_nameConflictIndex->setCurrentIndex(idx); _nameConflictIndex->setCurrentIndex(idx);
_sources->setCurrentText( _sources->setCurrentIndex(_sources->findText(
QString::fromStdString(GetWeakSourceName(item._source))); QString::fromStdString(GetWeakSourceName(item._source))));
_sourceGroups->setCurrentText( _sourceGroups->setCurrentText(
QString::fromStdString(GetWeakSourceName(item._source))); QString::fromStdString(GetWeakSourceName(item._source)));
_variables->SetVariable(item._variable); _variables->SetVariable(item._variable);
@@ -773,7 +774,10 @@ void SceneItemSelectionWidget::SetSceneItem(const SceneItemSelection &item)
_currentSelection = item; _currentSelection = item;
SetNameConflictVisibility(); {
const QSignalBlocker b(_nameConflictIndex);
SetNameConflictVisibility();
}
switch (item._nameConflictSelectionType) { switch (item._nameConflictSelectionType) {
case SceneItemSelection::NameConflictSelection::ALL: case SceneItemSelection::NameConflictSelection::ALL:
@@ -795,10 +799,17 @@ void SceneItemSelectionWidget::SetSceneItem(const SceneItemSelection &item)
void SceneItemSelectionWidget::SetScene(const SceneSelection &s) void SceneItemSelectionWidget::SetScene(const SceneSelection &s)
{ {
_scene = s; _scene = s;
_sources->clear();
_nameConflictIndex->hide(); _nameConflictIndex->hide();
if (_currentSelection._type != SceneItemSelection::Type::SOURCE_NAME) {
PopulateItemSelection();
return;
}
auto previous = _currentSelection;
PopulateItemSelection(); PopulateItemSelection();
SetSceneItem(previous);
} }
void SceneItemSelectionWidget::ShowPlaceholder(bool value) void SceneItemSelectionWidget::ShowPlaceholder(bool value)
@@ -819,6 +830,15 @@ void SceneItemSelectionWidget::SetPlaceholderType(Placeholder t,
} }
} }
void SceneItemSelectionWidget::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
const QSignalBlocker b1(_sources);
const QSignalBlocker b2(this);
PopulateItemSelection();
SetSceneItem(_currentSelection);
}
void SceneItemSelectionWidget::SceneChanged(const SceneSelection &s) void SceneItemSelectionWidget::SceneChanged(const SceneSelection &s)
{ {
SetScene(s); SetScene(s);

View File

@@ -97,6 +97,10 @@ public:
void SetScene(const SceneSelection &); void SetScene(const SceneSelection &);
void ShowPlaceholder(bool); void ShowPlaceholder(bool);
void SetPlaceholderType(Placeholder t, bool resetSelection = true); void SetPlaceholderType(Placeholder t, bool resetSelection = true);
protected:
void showEvent(QShowEvent *event) override;
signals: signals:
void SceneItemChanged(const SceneItemSelection &); void SceneItemChanged(const SceneItemSelection &);

View File

@@ -1,5 +1,6 @@
#include "source-setting.hpp" #include "source-setting.hpp"
#include "json-helpers.hpp" #include "json-helpers.hpp"
#include "log-helper.hpp"
#include "obs-module-helper.hpp" #include "obs-module-helper.hpp"
#include "math-helpers.hpp" #include "math-helpers.hpp"
@@ -151,10 +152,14 @@ void SetSourceSetting(obs_source_t *source, const SourceSetting &setting,
{ {
auto id = setting.GetID(); auto id = setting.GetID();
OBSDataAutoRelease data = obs_source_get_settings(source); OBSDataAutoRelease data = obs_source_get_settings(source);
auto item = obs_data_item_byname(data, id.c_str()); OBSDataAutoRelease dataWithDefaults = obs_data_get_defaults(data);
obs_data_apply(dataWithDefaults, data);
auto item = obs_data_item_byname(dataWithDefaults, id.c_str());
auto type = obs_data_item_gettype(item); auto type = obs_data_item_gettype(item);
switch (type) { switch (type) {
case OBS_DATA_NULL: case OBS_DATA_NULL:
vblog(LOG_WARNING,
"could not set settings value, as the value does not (yet) exist!");
break; break;
case OBS_DATA_STRING: case OBS_DATA_STRING:
obs_data_set_string(data, id.c_str(), value.c_str()); obs_data_set_string(data, id.c_str(), value.c_str());

View File

@@ -72,7 +72,9 @@ std::string TransitionSelection::ToString() const
TransitionSelectionWidget::TransitionSelectionWidget(QWidget *parent, TransitionSelectionWidget::TransitionSelectionWidget(QWidget *parent,
bool current, bool any) bool current, bool any)
: FilterComboBox(parent, : FilterComboBox(parent,
obs_module_text("AdvSceneSwitcher.selectTransition")) obs_module_text("AdvSceneSwitcher.selectTransition")),
_addCurrent(current),
_addAny(any)
{ {
setDuplicatesEnabled(true); setDuplicatesEnabled(true);
PopulateTransitionSelection(this, current, any, false); PopulateTransitionSelection(this, current, any, false);
@@ -81,7 +83,7 @@ TransitionSelectionWidget::TransitionSelectionWidget(QWidget *parent,
this, SLOT(SelectionChanged(const QString &))); this, SLOT(SelectionChanged(const QString &)));
} }
void TransitionSelectionWidget::SetTransition(TransitionSelection &t) void TransitionSelectionWidget::SetTransition(const TransitionSelection &t)
{ {
// Order of entries // Order of entries
// 1. Any transition // 1. Any transition
@@ -106,19 +108,35 @@ void TransitionSelectionWidget::SetTransition(TransitionSelection &t)
} }
} }
void TransitionSelectionWidget::Repopulate(bool current, bool any) void TransitionSelectionWidget::EnableCurrentEntry(bool enable)
{ {
{ _addCurrent = enable;
const QSignalBlocker blocker(this); Populate();
clear();
PopulateTransitionSelection(this, current, any);
setCurrentIndex(-1);
}
TransitionSelection t;
emit TransitionChanged(t);
} }
static bool isFirstEntry(QComboBox *l, QString name, int idx) void TransitionSelectionWidget::EnableAnyEntry(bool enable)
{
_addAny = enable;
Populate();
}
void TransitionSelectionWidget::showEvent(QShowEvent *event)
{
FilterComboBox::showEvent(event);
const auto selection = GetCurrentSelection();
const QSignalBlocker b(this);
Populate();
SetTransition(selection);
}
void TransitionSelectionWidget::Populate()
{
const QSignalBlocker blocker(this);
clear();
PopulateTransitionSelection(this, _addCurrent, _addAny);
}
static bool isFirstEntry(const QComboBox *l, QString name, int idx)
{ {
for (auto i = l->count() - 1; i >= 0; i--) { for (auto i = l->count() - 1; i >= 0; i--) {
if (l->itemText(i) == name) { if (l->itemText(i) == name) {
@@ -130,7 +148,27 @@ static bool isFirstEntry(QComboBox *l, QString name, int idx)
return false; return false;
} }
bool TransitionSelectionWidget::IsCurrentTransitionSelected(const QString &name) TransitionSelection TransitionSelectionWidget::GetCurrentSelection() const
{
TransitionSelection result;
const auto text = currentText();
auto transition = GetWeakTransitionByQString(text);
if (transition) {
result._type = TransitionSelection::Type::TRANSITION;
result._transition = transition;
} else {
if (IsCurrentTransitionSelected(text)) {
result._type = TransitionSelection::Type::CURRENT;
}
if (IsAnyTransitionSelected(text)) {
result._type = TransitionSelection::Type::ANY;
}
}
return result;
}
bool TransitionSelectionWidget::IsCurrentTransitionSelected(
const QString &name) const
{ {
if (name == QString::fromStdString((obs_module_text( if (name == QString::fromStdString((obs_module_text(
"AdvSceneSwitcher.currentTransition")))) { "AdvSceneSwitcher.currentTransition")))) {
@@ -139,7 +177,8 @@ bool TransitionSelectionWidget::IsCurrentTransitionSelected(const QString &name)
return false; return false;
} }
bool TransitionSelectionWidget::IsAnyTransitionSelected(const QString &name) bool TransitionSelectionWidget::IsAnyTransitionSelected(
const QString &name) const
{ {
if (name == QString::fromStdString((obs_module_text( if (name == QString::fromStdString((obs_module_text(
"AdvSceneSwitcher.anyTransition")))) { "AdvSceneSwitcher.anyTransition")))) {
@@ -148,25 +187,9 @@ bool TransitionSelectionWidget::IsAnyTransitionSelected(const QString &name)
return false; return false;
} }
void TransitionSelectionWidget::SelectionChanged(const QString &name) void TransitionSelectionWidget::SelectionChanged(const QString &)
{ {
TransitionSelection t; emit TransitionChanged(GetCurrentSelection());
auto transition = GetWeakTransitionByQString(name);
if (transition) {
t._type = TransitionSelection::Type::TRANSITION;
t._transition = transition;
}
if (!transition) {
if (IsCurrentTransitionSelected(name)) {
t._type = TransitionSelection::Type::CURRENT;
}
if (IsAnyTransitionSelected(name)) {
t._type = TransitionSelection::Type::ANY;
}
}
emit TransitionChanged(t);
} }
} // namespace advss } // namespace advss

View File

@@ -34,8 +34,13 @@ class TransitionSelectionWidget : public FilterComboBox {
public: public:
TransitionSelectionWidget(QWidget *parent, bool current = true, TransitionSelectionWidget(QWidget *parent, bool current = true,
bool any = false); bool any = false);
void SetTransition(TransitionSelection &); void SetTransition(const TransitionSelection &);
void Repopulate(bool current, bool any); void EnableCurrentEntry(bool enable);
void EnableAnyEntry(bool enable);
protected:
void showEvent(QShowEvent *event) override;
signals: signals:
void TransitionChanged(const TransitionSelection &); void TransitionChanged(const TransitionSelection &);
@@ -43,8 +48,13 @@ private slots:
void SelectionChanged(const QString &name); void SelectionChanged(const QString &name);
private: private:
bool IsCurrentTransitionSelected(const QString &name); void Populate();
bool IsAnyTransitionSelected(const QString &name); TransitionSelection GetCurrentSelection() const;
bool IsCurrentTransitionSelected(const QString &name) const;
bool IsAnyTransitionSelected(const QString &name) const;
bool _addCurrent;
bool _addAny;
}; };
} // namespace advss } // namespace advss

View File

@@ -0,0 +1,25 @@
#include "window-selection.hpp"
#include "selection-helpers.hpp"
namespace advss {
WindowSelectionWidget::WindowSelectionWidget(QWidget *parent)
: FilterComboBox(parent)
{
setEditable(true);
SetAllowUnmatchedSelection(true);
setMaxVisibleItems(20);
PopulateWindowSelection(this);
}
void WindowSelectionWidget::showEvent(QShowEvent *event)
{
FilterComboBox::showEvent(event);
const QSignalBlocker b(this);
const auto text = currentText();
clear();
PopulateWindowSelection(this);
setCurrentText(text);
}
} // namespace advss

View File

@@ -0,0 +1,16 @@
#pragma once
#include "filter-combo-box.hpp"
namespace advss {
class WindowSelectionWidget : public FilterComboBox {
Q_OBJECT
public:
WindowSelectionWidget(QWidget *parent);
protected:
void showEvent(QShowEvent *event) override;
};
} // namespace advss

View File

@@ -1,6 +1,5 @@
#pragma once #pragma once
#include "string-list.hpp" #include "string-list.hpp"
#include "variable-line-edit.hpp"
namespace advss { namespace advss {

View File

@@ -0,0 +1,47 @@
cmake_minimum_required(VERSION 3.14)
project(advanced-scene-switcher-mqtt)
# --- Check paho.mqtt.cpp requirements ---
find_package(PahoMqttCpp)
if(NOT PahoMqttCpp_FOUND)
message(WARNING "PahoMqttCpp not found!\n"
"MQTT support will be disabled!\n\n")
return()
endif()
# --- End of section ---
add_library(${PROJECT_NAME} MODULE)
if(PAHO_MQTT_CPP_VERSION VERSION_GREATER_EQUAL "1.1.0")
target_compile_definitions(${PROJECT_NAME} PRIVATE ENABLE_MQTT5_SUPPORT=1)
endif()
# PDB was not found with paho-mqttpp3-static.lib
if(MSVC)
target_link_options(${PROJECT_NAME} PRIVATE /IGNORE:4099)
endif()
target_sources(
${PROJECT_NAME}
PRIVATE macro-action-mqtt.cpp
macro-action-mqtt.hpp
macro-condition-mqtt.hpp
macro-condition-mqtt.cpp
mqtt-helpers.cpp
mqtt-helpers.hpp
mqtt-tab.cpp
mqtt-tab.hpp
topic-selection.cpp
topic-selection.hpp)
setup_advss_plugin(${PROJECT_NAME})
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
if(OS_LINUX)
target_link_libraries(${PROJECT_NAME} PRIVATE PahoMqttCpp::paho-mqttpp3)
else()
target_link_libraries(${PROJECT_NAME}
PRIVATE PahoMqttCpp::paho-mqttpp3-static)
endif()
install_advss_plugin(${PROJECT_NAME})

View File

@@ -0,0 +1,202 @@
#include "macro-action-mqtt.hpp"
#include "layout-helpers.hpp"
#include "mqtt-helpers.hpp"
#include "ui-helpers.hpp"
namespace advss {
const std::string MacroActionMqtt::id = "mqtt";
bool MacroActionMqtt::_registered = MacroActionFactory::Register(
MacroActionMqtt::id,
{MacroActionMqtt::Create, MacroActionMqttEdit::Create,
"AdvSceneSwitcher.action.mqtt"});
bool MacroActionMqtt::PerformAction()
{
auto connection = _connection.lock();
if (!connection) {
return true;
}
int qos = _qos;
if (qos > 2 || qos < 0) {
qos = 1;
blog(LOG_WARNING,
"%s: use QoS 1 isntead of invalid QoS value %d", __func__,
(int)_qos);
}
(void)connection->SendMessage(_topic, _message, qos, _retain);
return true;
}
void MacroActionMqtt::LogAction() const
{
ablog(LOG_INFO,
"send MQTT message to \"%s\" (topic: \"%s\" - payload: \"%s\" - qos: %d - retain: %d)",
GetWeakMqttConnectionName(_connection).c_str(), _topic.c_str(),
_message.c_str(), (int)_qos, _retain);
}
bool MacroActionMqtt::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
obs_data_set_string(obj, "connection",
GetWeakMqttConnectionName(_connection).c_str());
_message.Save(obj, "message");
_topic.Save(obj, "topic");
_qos.Save(obj, "qos");
obs_data_set_bool(obj, "retain", _retain);
return true;
}
bool MacroActionMqtt::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
SetConnection(obs_data_get_string(obj, "connection"));
_message.Load(obj, "message");
_topic.Load(obj, "topic");
_qos.Load(obj, "qos");
_retain = obs_data_get_bool(obj, "retain");
return true;
}
void MacroActionMqtt::SetConnection(const std::string &name)
{
_connection = GetWeakMqttConnectionByName(name);
}
std::weak_ptr<MqttConnection> MacroActionMqtt::GetConnection() const
{
return _connection;
}
std::string MacroActionMqtt::GetShortDesc() const
{
return GetWeakMqttConnectionName(_connection);
}
void MacroActionMqtt::ResolveVariablesToFixedValues()
{
_message.ResolveVariables();
_topic.ResolveVariables();
_qos.ResolveVariables();
}
std::shared_ptr<MacroAction> MacroActionMqtt::Create(Macro *m)
{
return std::make_shared<MacroActionMqtt>(m);
}
std::shared_ptr<MacroAction> MacroActionMqtt::Copy() const
{
return std::make_shared<MacroActionMqtt>(*this);
}
MacroActionMqttEdit::MacroActionMqttEdit(
QWidget *parent, std::shared_ptr<MacroActionMqtt> entryData)
: QWidget(parent),
_connection(new MqttConnectionSelection(this)),
_message(new VariableTextEdit(this, 5, 1, 1)),
_topic(new VariableLineEdit(this)),
_qos(new VariableSpinBox(this)),
_retain(new QCheckBox(this))
{
_qos->setMinimum(0);
_qos->setMaximum(2);
QWidget::connect(_connection, SIGNAL(SelectionChanged(const QString &)),
this,
SLOT(ConnectionSelectionChanged(const QString &)));
QWidget::connect(_message, SIGNAL(textChanged()), this,
SLOT(MqttMessageChanged()));
QWidget::connect(_topic, SIGNAL(editingFinished()), this,
SLOT(MqttTopicChanged()));
QWidget::connect(
_qos,
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this, SLOT(QoSChanged(const NumberVariable<int> &)));
QWidget::connect(_retain, SIGNAL(stateChanged(int)), this,
SLOT(RetainChanged(int)));
auto sendLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.mqtt.layout"),
sendLayout, {{"{{connection}}", _connection}});
int row = 0;
auto grid = new QGridLayout;
grid->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.action.mqtt.topic")),
row, 0);
grid->addWidget(_topic, row, 1);
row++;
grid->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.action.mqtt.qos")),
row, 0);
grid->addWidget(_qos, row, 1);
row++;
grid->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.action.mqtt.retain")),
row, 0);
grid->addWidget(_retain, row, 1);
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(sendLayout);
mainLayout->addWidget(_message);
mainLayout->addLayout(grid);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionMqttEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_connection->SetConnection(_entryData->GetConnection());
_message->setPlainText(_entryData->_message);
_topic->setText(_entryData->_topic);
_qos->SetValue(_entryData->_qos);
_retain->setChecked(_entryData->_retain);
adjustSize();
updateGeometry();
}
void MacroActionMqttEdit::ConnectionSelectionChanged(const QString &connection)
{
GUARD_LOADING_AND_LOCK();
_entryData->SetConnection(connection.toStdString());
emit(HeaderInfoChanged(connection));
}
void MacroActionMqttEdit::MqttMessageChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_message = _message->toPlainText().toStdString();
}
void MacroActionMqttEdit::MqttTopicChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_topic = _topic->text().toStdString();
}
void MacroActionMqttEdit::QoSChanged(const IntVariable &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_qos = value;
}
void MacroActionMqttEdit::RetainChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_retain = value;
}
} // namespace advss

View File

@@ -0,0 +1,73 @@
#pragma once
#include "macro-action-edit.hpp"
#include "mqtt-helpers.hpp"
#include "variable-line-edit.hpp"
#include "variable-spinbox.hpp"
#include "variable-text-edit.hpp"
namespace advss {
class MacroActionMqtt : public MacroAction {
public:
MacroActionMqtt(Macro *m) : MacroAction(m, true) {}
bool PerformAction();
void LogAction() const;
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
void SetConnection(const std::string &name);
std::weak_ptr<MqttConnection> GetConnection() const;
std::string GetShortDesc() const;
void ResolveVariablesToFixedValues();
std::string GetId() const { return id; };
static std::shared_ptr<MacroAction> Create(Macro *m);
std::shared_ptr<MacroAction> Copy() const;
StringVariable _message = "payload";
StringVariable _topic = "/topic";
IntVariable _qos = 1;
bool _retain = false;
private:
std::weak_ptr<MqttConnection> _connection;
static bool _registered;
static const std::string id;
};
class MacroActionMqttEdit : public QWidget {
Q_OBJECT
public:
MacroActionMqttEdit(
QWidget *parent,
std::shared_ptr<MacroActionMqtt> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionMqttEdit(
parent,
std::dynamic_pointer_cast<MacroActionMqtt>(action));
}
private slots:
void ConnectionSelectionChanged(const QString &);
void MqttMessageChanged();
void MqttTopicChanged();
void QoSChanged(const NumberVariable<int> &value);
void RetainChanged(int value);
signals:
void HeaderInfoChanged(const QString &);
private:
MqttConnectionSelection *_connection;
VariableTextEdit *_message;
VariableLineEdit *_topic;
VariableSpinBox *_qos;
QCheckBox *_retain;
std::shared_ptr<MacroActionMqtt> _entryData;
bool _loading = true;
};
} // namespace advss

View File

@@ -0,0 +1,268 @@
#include "macro-condition-mqtt.hpp"
#include "layout-helpers.hpp"
#include "macro-helpers.hpp"
#include "ui-helpers.hpp"
namespace advss {
const std::string MacroConditionMqtt::id = "mqtt";
bool MacroConditionMqtt::_registered = MacroConditionFactory::Register(
MacroConditionMqtt::id,
{MacroConditionMqtt::Create, MacroConditionMqttEdit::Create,
"AdvSceneSwitcher.condition.mqtt"});
bool MacroConditionMqtt::CheckCondition()
{
if (!_messageBuffer) {
return false;
}
const bool macroWasPausedSinceLastCheck =
MacroWasPausedSince(GetMacro(), _lastCheck);
_lastCheck = std::chrono::high_resolution_clock::now();
if (macroWasPausedSinceLastCheck) {
_messageBuffer->Clear();
return false;
}
const auto messageMatches = [this](const std::string &message) -> bool {
if (_regex.Enabled()) {
return _regex.Matches(message, _message);
}
return message != std::string(_message);
};
while (!_messageBuffer->Empty()) {
auto message = _messageBuffer->ConsumeMessage();
if (!message) {
continue;
}
if (!messageMatches(*message)) {
continue;
}
SetTempVarValue("message", *message);
if (_clearBufferOnMatch) {
_messageBuffer->Clear();
}
return true;
}
return false;
}
bool MacroConditionMqtt::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
_message.Save(obj, "message");
_regex.Save(obj);
obs_data_set_string(obj, "connection",
GetWeakMqttConnectionName(_connection).c_str());
obs_data_set_bool(obj, "clearBufferOnMatch", _clearBufferOnMatch);
return true;
}
bool MacroConditionMqtt::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_message.Load(obj, "message");
_regex.Load(obj);
_clearBufferOnMatch = obs_data_get_bool(obj, "clearBufferOnMatch");
SetConnection(obs_data_get_string(obj, "connection"));
return true;
}
std::string MacroConditionMqtt::GetShortDesc() const
{
return GetWeakMqttConnectionName(_connection);
}
void MacroConditionMqtt::SetConnection(const std::string &name)
{
_connection = GetWeakMqttConnectionByName(name);
auto connection = _connection.lock();
if (!connection) {
return;
}
_messageBuffer = connection->RegisterForEvents();
}
std::weak_ptr<MqttConnection> MacroConditionMqtt::GetConnection() const
{
return _connection;
}
void MacroConditionMqtt::SetupTempVars()
{
MacroCondition::SetupTempVars();
AddTempvar("message",
obs_module_text("AdvSceneSwitcher.tempVar.mqtt.message"));
}
MacroConditionMqttEdit::MacroConditionMqttEdit(
QWidget *parent, std::shared_ptr<MacroConditionMqtt> entryData)
: QWidget(parent),
_connection(new MqttConnectionSelection(this)),
_message(new VariableTextEdit(this, 5, 1, 1)),
_regex(new RegexConfigWidget(parent)),
_listen(new QPushButton(obs_module_text(
"AdvSceneSwitcher.mqttConnection.startListen"))),
_clearBufferOnMatch(new QCheckBox(
obs_module_text("AdvSceneSwitcher.clearBufferOnMatch")))
{
QWidget::connect(_message, SIGNAL(textChanged()), this,
SLOT(MqttMessageChanged()));
QWidget::connect(_regex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(RegexChanged(const RegexConfig &)));
QWidget::connect(_connection, SIGNAL(SelectionChanged(const QString &)),
this,
SLOT(ConnectionSelectionChanged(const QString &)));
QWidget::connect(_listen, SIGNAL(clicked()), this,
SLOT(ToggleListen()));
QWidget::connect(_clearBufferOnMatch, SIGNAL(stateChanged(int)), this,
SLOT(ClearBufferOnMatchChanged(int)));
QWidget::connect(&_listenTimer, SIGNAL(timeout()), this,
SLOT(SetMessageSelectionToLastReceived()));
auto entryLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.mqtt.layout.match"),
entryLayout,
{{"{{connection}}", _connection}, {"{{regex}}", _regex}});
auto listenLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.mqtt.layout.listen"),
listenLayout, {{"{{listenButton}}", _listen}});
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLayout);
mainLayout->addWidget(_message);
mainLayout->addLayout(listenLayout);
mainLayout->addWidget(_clearBufferOnMatch);
setLayout(mainLayout);
_listenTimer.setInterval(100);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
MacroConditionMqttEdit::~MacroConditionMqttEdit()
{
EnableListening(false);
}
void MacroConditionMqttEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_message->setPlainText(_entryData->_message);
_connection->SetConnection(_entryData->GetConnection());
_regex->SetRegexConfig(_entryData->_regex);
_clearBufferOnMatch->setChecked(_entryData->_clearBufferOnMatch);
adjustSize();
updateGeometry();
}
void MacroConditionMqttEdit::ConnectionSelectionChanged(
const QString &connection)
{
GUARD_LOADING_AND_LOCK();
_entryData->SetConnection(connection.toStdString());
emit(HeaderInfoChanged(connection));
}
void MacroConditionMqttEdit::MqttMessageChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_message = _message->toPlainText().toStdString();
}
void MacroConditionMqttEdit::ClearBufferOnMatchChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_clearBufferOnMatch = value;
}
void MacroConditionMqttEdit::RegexChanged(const RegexConfig &conf)
{
GUARD_LOADING_AND_LOCK();
_entryData->_regex = conf;
adjustSize();
updateGeometry();
}
void MacroConditionMqttEdit::EnableListening(bool enable)
{
if (_currentlyListening == enable) {
return;
}
if (enable) {
auto connectionItem = _connection->GetCurrentItem();
if (!connectionItem) {
return;
}
auto weakConnection = GetWeakMqttConnectionByQString(
QString::fromStdString(connectionItem->Name()));
auto connection = weakConnection.lock();
if (!connection) {
return;
}
_messageBuffer = connection->RegisterForEvents();
_listenTimer.start();
} else {
_messageBuffer.reset();
_listenTimer.stop();
}
}
void MacroConditionMqttEdit::ToggleListen()
{
if (!_entryData) {
return;
}
_listen->setText(
_currentlyListening
? obs_module_text(
"AdvSceneSwitcher.mqttConnection.startListen")
: obs_module_text(
"AdvSceneSwitcher.mqttConnection.stopListen"));
EnableListening(!_currentlyListening);
_currentlyListening = !_currentlyListening;
_message->setDisabled(_currentlyListening);
}
void MacroConditionMqttEdit::SetMessageSelectionToLastReceived()
{
auto lock = LockContext();
if (!_entryData || !_messageBuffer || _messageBuffer->Empty()) {
return;
}
std::optional<std::string> message;
while (!_messageBuffer->Empty()) {
message = _messageBuffer->ConsumeMessage();
if (!message) {
continue;
}
}
if (!message) {
return;
}
const QSignalBlocker blocker(_message);
_message->setPlainText(*message);
_entryData->_message = *message;
}
} // namespace advss

View File

@@ -0,0 +1,86 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "mqtt-helpers.hpp"
#include "regex-config.hpp"
#include "variable-text-edit.hpp"
#include <QCheckBox>
#include <QPushButton>
#include <QTimer>
namespace advss {
class MacroConditionMqtt : public MacroCondition {
public:
MacroConditionMqtt(Macro *m) : MacroCondition(m, true) {}
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetShortDesc() const;
std::string GetId() const { return id; };
static std::shared_ptr<MacroCondition> Create(Macro *m)
{
return std::make_shared<MacroConditionMqtt>(m);
}
void SetConnection(const std::string &);
std::weak_ptr<MqttConnection> GetConnection() const;
StringVariable _message;
RegexConfig _regex;
bool _clearBufferOnMatch = true;
private:
void SetupTempVars();
std::weak_ptr<MqttConnection> _connection;
MqttMessageBuffer _messageBuffer;
std::chrono::high_resolution_clock::time_point _lastCheck{};
static bool _registered;
static const std::string id;
};
class MacroConditionMqttEdit : public QWidget {
Q_OBJECT
public:
MacroConditionMqttEdit(
QWidget *parent,
std::shared_ptr<MacroConditionMqtt> cond = nullptr);
virtual ~MacroConditionMqttEdit();
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionMqttEdit(
parent,
std::dynamic_pointer_cast<MacroConditionMqtt>(cond));
}
private slots:
void ConnectionSelectionChanged(const QString &);
void MqttMessageChanged();
void ClearBufferOnMatchChanged(int);
void RegexChanged(const RegexConfig &conf);
void ToggleListen();
void SetMessageSelectionToLastReceived();
signals:
void HeaderInfoChanged(const QString &);
private:
void EnableListening(bool);
MqttConnectionSelection *_connection;
VariableTextEdit *_message;
RegexConfigWidget *_regex;
QPushButton *_listen;
QCheckBox *_clearBufferOnMatch;
std::shared_ptr<MacroConditionMqtt> _entryData;
QTimer _listenTimer;
MqttMessageBuffer _messageBuffer;
bool _currentlyListening = false;
bool _loading = true;
};
} // namespace advss

View File

@@ -0,0 +1,639 @@
#include "mqtt-helpers.hpp"
#include "layout-helpers.hpp"
#include "log-helper.hpp"
#include "obs-module-helper.hpp"
#include "plugin-state-helpers.hpp"
#include "ui-helpers.hpp"
#include <obs.hpp>
#include <QTimer>
#undef DispatchMessage
namespace advss {
MqttConnection::~MqttConnection()
{
Disconnect();
}
MqttConnection::MqttConnection(const MqttConnection &other)
: Item(other._name),
_uri(other._uri),
_username(other._username),
_password(other._password),
_connectOnStart(other._connectOnStart),
_reconnect(other._reconnect),
_reconnectDelay(other._reconnectDelay)
{
}
MqttConnection::MqttConnection(const std::string &name, const std::string &uri,
const std::string &username,
const std::string &password, bool connectOnStart,
bool reconnect, int reconnectDelay)
: Item(name),
_uri(uri),
_username(username),
_password(password),
_connectOnStart(connectOnStart),
_reconnect(reconnect),
_reconnectDelay(reconnectDelay)
{
}
void MqttConnection::Connect()
{
static std::mutex mutex;
std::scoped_lock<std::mutex> lock(mutex);
if (_connecting) {
return;
}
if (_thread.joinable()) {
_thread.join();
}
_connecting = true;
_disconnect = false;
_thread = std::thread(&MqttConnection::ConnectThread, this);
}
void MqttConnection::Disconnect()
{
_disconnect = true;
{
std::unique_lock<std::mutex> waitLck(_waitMtx);
_cv.notify_all();
}
if (_thread.joinable()) {
_thread.join();
}
}
void MqttConnection::ConnectThread()
{
using namespace std::chrono_literals;
const auto waitBeforeReconnect = [this]() {
std::unique_lock<std::mutex> lck(_waitMtx);
vblog(LOG_INFO,
"trying to reconnect to MQTT server \"%s\" in %d seconds.",
_name.c_str(), _reconnectDelay);
_cv.wait_for(lck, std::chrono::seconds(_reconnectDelay));
};
const auto logConnectionLost = [this](const std::string &cause) {
blog(LOG_INFO, "MQTT connection \"%s\" lost: %s", _name.c_str(),
cause.c_str());
};
const auto dispatchMessage = [this](mqtt::const_message_ptr msg) {
if (!msg) {
return;
}
vblog(LOG_INFO, "MQTT connection \"%s\" received message: %s",
_name.c_str(), msg->to_string().c_str());
_dispatcher.DispatchMessage(msg->to_string());
};
do {
std::unique_lock<std::mutex> clientLock(_clientMtx);
_client = std::make_shared<mqtt::async_client>(
_uri, std::string("advss_") + _name);
#ifdef ENABLE_MQTT5_SUPPORT
auto connOpts = mqtt::connect_options_builder::v5()
#else
auto connOpts = mqtt::connect_options_builder()
#endif
.clean_start(false)
.clean_session(true)
.connect_timeout(5s)
.user_name(_username)
.password(_password)
.finalize();
_client->set_connection_lost_handler(logConnectionLost);
_client->set_message_callback(dispatchMessage);
_client->start_consuming();
try {
vblog(LOG_INFO, "connecting to MQTT server \"%s\" ...",
_name.c_str());
auto tok = _client->connect(connOpts);
auto rsp = tok->get_connect_response();
#ifdef ENABLE_MQTT5_SUPPORT
if (rsp.get_mqtt_version() < MQTTVERSION_5) {
blog(LOG_INFO,
"\"%s\" did not get an MQTT v5 connection",
_name.c_str());
break;
}
#endif
if (!rsp.is_session_present()) {
vblog(LOG_INFO,
"\"%s\" session not present on broker. subscribing...",
_name.c_str());
const auto topics = std::make_shared<
mqtt::string_collection>(_topics);
_client->subscribe(topics, _qos)->wait();
}
blog(LOG_INFO,
"\"%s\" connection established to MQTT server!",
_name.c_str());
_connected = true;
clientLock.unlock();
while (!_disconnect && _client->is_connected()) {
std::unique_lock<std::mutex> lock(_waitMtx);
_cv.wait_for(lock, std::chrono::seconds(
_reconnectDelay));
}
clientLock.lock();
if (_client->is_connected()) {
blog(LOG_INFO,
"Disconnecting from the MQTT server \"%s\"",
_name.c_str());
_client->stop_consuming();
_client->disconnect()->wait();
} else {
blog(LOG_INFO,
"MQTT client %s was disconnected",
_name.c_str());
}
_client.reset();
_connected = false;
if (!_reconnect || _disconnect) {
break;
}
waitBeforeReconnect();
} catch (const std::exception &e) {
_lastError = e.what();
blog(LOG_INFO, "%s %s", __func__, _lastError.c_str());
_client.reset();
_connected = false;
if (!_reconnect || _disconnect) {
break;
}
waitBeforeReconnect();
}
} while (_reconnect && !_disconnect);
_connecting = false;
}
bool MqttConnection::SendMessage(const std::string &topic,
const std::string &payload, int qos,
bool retained)
{
std::scoped_lock<std::mutex> lock(_clientMtx);
if (!_client || !_client->is_connected()) {
blog(LOG_WARNING,
"Cannot send message: MQTT client \"%s\" is not connected",
_name.c_str());
Connect();
return false;
}
try {
auto msg = mqtt::make_message(topic, payload);
msg->set_qos(qos);
msg->set_retained(retained);
_client->publish(msg);
vblog(LOG_INFO, "Sent MQTT message on \"%s\": %s",
topic.c_str(), payload.c_str());
return true;
} catch (const mqtt::exception &e) {
blog(LOG_WARNING, "MQTT send error for \"%s\": %s",
_name.c_str(), e.what());
return false;
}
}
void MqttConnection::Load(obs_data_t *data)
{
Item::Load(data);
_uri = obs_data_get_string(data, "uri");
_username = obs_data_get_string(data, "username");
_password = obs_data_get_string(data, "password");
_connectOnStart = obs_data_get_bool(data, "connectOnStart");
_reconnect = obs_data_get_bool(data, "reconnect");
_reconnectDelay = obs_data_get_int(data, "reconnectDelay");
_topics.clear();
OBSDataArrayAutoRelease array = obs_data_get_array(data, "topics");
size_t count = obs_data_array_count(array);
for (size_t i = 0; i < count; i++) {
OBSDataAutoRelease obj = obs_data_array_item(array, i);
_topics.push_back(obs_data_get_string(obj, "topic"));
}
_qos.clear();
array = obs_data_get_array(data, "qos");
count = obs_data_array_count(array);
for (size_t i = 0; i < count; i++) {
OBSDataAutoRelease obj = obs_data_array_item(array, i);
_qos.push_back(obs_data_get_int(obj, "qos"));
}
if (ConnectOnStartup()) {
Connect();
}
}
void MqttConnection::Save(obs_data_t *data) const
{
Item::Save(data);
obs_data_set_string(data, "uri", _uri.c_str());
obs_data_set_string(data, "username", _username.c_str());
obs_data_set_string(data, "password", _password.c_str());
obs_data_set_bool(data, "connectOnStart", _connectOnStart);
obs_data_set_bool(data, "reconnect", _reconnect);
obs_data_set_int(data, "reconnectDelay", _reconnectDelay);
OBSDataArrayAutoRelease array = obs_data_array_create();
for (const auto &topic : _topics) {
OBSDataAutoRelease obj = obs_data_create();
obs_data_set_string(obj, "topic", topic.c_str());
obs_data_array_push_back(array, obj);
}
obs_data_set_array(data, "topics", array);
array = obs_data_array_create();
for (const int qos : _qos) {
OBSDataAutoRelease obj = obs_data_create();
obs_data_set_int(obj, "qos", qos);
obs_data_array_push_back(array, obj);
}
obs_data_set_array(data, "qos", array);
}
MqttMessageBuffer MqttConnection::RegisterForEvents()
{
return _dispatcher.RegisterClient();
}
QString MqttConnection::GetStatus() const
{
if (_connected) {
return obs_module_text(
"AdvSceneSwitcher.mqttConnection.status.connected");
}
if (_connecting) {
return obs_module_text(
"AdvSceneSwitcher.mqttConnection.status.connecting");
}
if (_lastError.empty()) {
return obs_module_text(
"AdvSceneSwitcher.mqttConnection.status.disconnected");
}
QString status(obs_module_text(
"AdvSceneSwitcher.mqttConnection.status.disconnected"));
status += " (" + QString::fromStdString(_lastError) + ")";
return status;
}
MqttConnectionSettingsDialog::MqttConnectionSettingsDialog(
QWidget *parent, const MqttConnection &connection)
: ItemSettingsDialog(connection, GetMqttConnections(),
"AdvSceneSwitcher.mqttConnection.select",
"AdvSceneSwitcher.mqttConnection.add",
"AdvSceneSwitcher.item.nameNotAvailable", true,
parent),
_uri(new QLineEdit()),
_username(new QLineEdit()),
_password(new QLineEdit()),
_showPassword(new QPushButton()),
_topics(new MqttTopicListWidget(this)),
_connectOnStart(new QCheckBox()),
_reconnect(new QCheckBox()),
_reconnectDelay(new QSpinBox()),
_status(new QLabel()),
_test(new QPushButton(
obs_module_text("AdvSceneSwitcher.mqttConnection.test"))),
_layout(new QGridLayout())
{
_showPassword->setMaximumWidth(22);
_showPassword->setFlat(true);
_showPassword->setStyleSheet(
"QPushButton { background-color: transparent; border: 0px }");
_uri->setText(QString::fromStdString(connection._uri));
_username->setText(QString::fromStdString(connection._username));
_password->setText(QString::fromStdString(connection._password));
_topics->SetValues(connection._topics, connection._qos);
_reconnectDelay->setMaximum(9999);
_reconnectDelay->setSuffix("s");
_connectOnStart->setChecked(connection._connectOnStart);
_reconnect->setChecked(connection._reconnect);
_reconnectDelay->setValue(connection._reconnectDelay);
QWidget::connect(_showPassword, SIGNAL(pressed()), this,
SLOT(ShowPassword()));
QWidget::connect(_showPassword, SIGNAL(released()), this,
SLOT(HidePassword()));
QWidget::connect(_reconnect, SIGNAL(stateChanged(int)), this,
SLOT(ReconnectChanged(int)));
QWidget::connect(_test, SIGNAL(clicked()), this,
SLOT(TestConnection()));
int row = 0;
_layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.mqttConnection.name")),
row, 0);
auto nameLayout = new QHBoxLayout();
nameLayout->addWidget(_name);
nameLayout->addWidget(_nameHint);
_layout->addLayout(nameLayout, row, 1);
++row;
_layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.mqttConnection.address")),
row, 0);
_layout->addWidget(_uri, row, 1);
++row;
_layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.mqttConnection.username")),
row, 0);
_layout->addWidget(_username, row, 1);
++row;
auto passLayout = new QHBoxLayout();
passLayout->addWidget(_password);
passLayout->addWidget(_showPassword);
_layout->addLayout(passLayout, row, 1);
++row;
_layout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.mqttConnection.topics")));
++row;
_layout->addWidget(_topics, row, 0, 1, -1);
++row;
_layout->addWidget(
new QLabel(obs_module_text(
"AdvSceneSwitcher.mqttConnection.connectOnStart")),
row, 0);
_layout->addWidget(_connectOnStart, row, 1);
++row;
_layout->addWidget(
new QLabel(obs_module_text(
"AdvSceneSwitcher.mqttConnection.reconnect")),
row, 0);
_layout->addWidget(_reconnect, row, 1);
++row;
_layout->addWidget(
new QLabel(obs_module_text(
"AdvSceneSwitcher.mqttConnection.reconnectDelay")),
row, 0);
_layout->addWidget(_reconnectDelay, row, 1);
++row;
_layout->addWidget(_test, row, 0);
_layout->addWidget(_status, row, 1);
++row;
_layout->addWidget(_buttonbox, row, 0, 1, -1);
setLayout(_layout);
MinimizeSizeOfColumn(_layout, 0);
ReconnectChanged(_reconnect->isChecked());
HidePassword();
}
bool MqttConnectionSettingsDialog::AskForSettings(QWidget *parent,
MqttConnection &connection)
{
MqttConnectionSettingsDialog dialog(parent, connection);
dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
if (dialog.exec() != DialogCode::Accepted) {
return false;
}
connection._name = dialog._name->text().toStdString();
connection._uri = dialog._uri->text().toStdString();
connection._username = dialog._username->text().toStdString();
connection._password = dialog._password->text().toStdString();
connection._topics = dialog._topics->GetTopics();
connection._qos = dialog._topics->GetQoS();
connection._connectOnStart = dialog._connectOnStart->isChecked();
connection._reconnect = dialog._reconnect->isChecked();
connection._reconnectDelay = dialog._reconnectDelay->value();
if (connection._connecting) {
connection.Disconnect();
}
connection.Connect();
return true;
}
void MqttConnectionSettingsDialog::ShowPassword()
{
SetButtonIcon(_showPassword, GetThemeTypeName() == "Light"
? ":res/images/visible.svg"
: "theme:Dark/visible.svg");
_password->setEchoMode(QLineEdit::Normal);
}
void MqttConnectionSettingsDialog::HidePassword()
{
SetButtonIcon(_showPassword, ":res/images/invisible.svg");
_password->setEchoMode(QLineEdit::PasswordEchoOnEdit);
}
void MqttConnectionSettingsDialog::ReconnectChanged(int state)
{
_reconnectDelay->setEnabled(state);
}
void MqttConnectionSettingsDialog::TestConnection()
{
if (_updateStatusTimer) {
_updateStatusTimer->stop();
_updateStatusTimer->deleteLater();
}
auto connection = std::make_shared<MqttConnection>();
connection->_name = _name->text().toStdString();
connection->_uri = _uri->text().toStdString();
connection->_username = _username->text().toStdString();
connection->_password = _password->text().toStdString();
connection->_topics = _topics->GetTopics();
connection->_qos = _topics->GetQoS();
connection->_connectOnStart = false;
connection->_reconnect = false;
connection->_reconnectDelay = _reconnectDelay->value();
connection->Connect();
_updateStatusTimer = new QTimer(this);
_updateStatusTimer->setInterval(300);
QObject::connect(_updateStatusTimer, &QTimer::timeout,
[this, connection]() {
_status->setText(connection->GetStatus());
});
_updateStatusTimer->start();
}
static bool AskForSettingsWrapper(QWidget *parent, Item &settings)
{
MqttConnection &connection = dynamic_cast<MqttConnection &>(settings);
if (MqttConnectionSettingsDialog::AskForSettings(parent, connection)) {
return true;
}
return false;
}
MqttConnectionSelection::MqttConnectionSelection(QWidget *parent)
: ItemSelection(GetMqttConnections(), MqttConnection::Create,
AskForSettingsWrapper,
"AdvSceneSwitcher.mqttConnection.select",
"AdvSceneSwitcher.mqttConnection.add",
"AdvSceneSwitcher.item.nameNotAvailable",
"AdvSceneSwitcher.mqttConnection.configure", parent)
{
// Connect to slots
QWidget::connect(MqttConnectionSignalManager::Instance(),
SIGNAL(Rename(const QString &, const QString &)), this,
SLOT(RenameItem(const QString &, const QString &)));
QWidget::connect(MqttConnectionSignalManager::Instance(),
SIGNAL(Add(const QString &)), this,
SLOT(AddItem(const QString &)));
QWidget::connect(MqttConnectionSignalManager::Instance(),
SIGNAL(Remove(const QString &)), this,
SLOT(RemoveItem(const QString &)));
// Forward signals
QWidget::connect(this,
SIGNAL(ItemRenamed(const QString &, const QString &)),
MqttConnectionSignalManager::Instance(),
SIGNAL(Rename(const QString &, const QString &)));
QWidget::connect(this, SIGNAL(ItemAdded(const QString &)),
MqttConnectionSignalManager::Instance(),
SIGNAL(Add(const QString &)));
QWidget::connect(this, SIGNAL(ItemRemoved(const QString &)),
MqttConnectionSignalManager::Instance(),
SIGNAL(Remove(const QString &)));
}
void MqttConnectionSelection::SetConnection(
const std::weak_ptr<MqttConnection> &connection_)
{
auto connection = connection_.lock();
if (connection) {
SetItem(connection->Name());
} else {
SetItem("");
}
}
MqttConnectionSignalManager::MqttConnectionSignalManager(QObject *parent)
: QObject(parent)
{
QWidget::connect(this, SIGNAL(Add(const QString &)), this,
SLOT(OpenNewConnection(const QString &)));
}
MqttConnectionSignalManager *MqttConnectionSignalManager::Instance()
{
static MqttConnectionSignalManager manager;
return &manager;
}
void MqttConnectionSignalManager::OpenNewConnection(const QString &name)
{
auto weakConnection = GetWeakMqttConnectionByName(name.toStdString());
auto connection = weakConnection.lock();
if (!connection) {
return;
}
if (connection->ConnectOnStartup()) {
connection->Connect();
}
}
std::deque<std::shared_ptr<Item>> &GetMqttConnections()
{
static std::deque<std::shared_ptr<Item>> connections;
return connections;
}
MqttConnection *GetMqttConnectionByName(const QString &name)
{
return GetMqttConnectionByName(name.toStdString());
}
MqttConnection *GetMqttConnectionByName(const std::string &name)
{
for (const auto &connection : GetMqttConnections()) {
if (connection->Name() == name) {
return dynamic_cast<MqttConnection *>(connection.get());
}
}
return nullptr;
}
std::weak_ptr<MqttConnection>
GetWeakMqttConnectionByName(const std::string &name)
{
for (const auto &connection : GetMqttConnections()) {
if (connection->Name() == name) {
std::weak_ptr<MqttConnection> weak =
std::dynamic_pointer_cast<MqttConnection>(
connection);
return weak;
}
}
return std::weak_ptr<MqttConnection>();
}
std::weak_ptr<MqttConnection>
GetWeakMqttConnectionByQString(const QString &name)
{
return GetWeakMqttConnectionByName(name.toStdString());
}
std::string
GetWeakMqttConnectionName(const std::weak_ptr<MqttConnection> &connection_)
{
auto connection = connection_.lock();
if (!connection) {
return obs_module_text(
"AdvSceneSwitcher.mqttConnection.invalid");
}
return connection->Name();
}
static void SaveMqttConnections(obs_data_t *obj)
{
OBSDataArrayAutoRelease array = obs_data_array_create();
for (const auto &connection : GetMqttConnections()) {
OBSDataAutoRelease obj = obs_data_create();
connection->Save(obj);
obs_data_array_push_back(array, obj);
}
obs_data_set_array(obj, "mqttConnections", array);
}
static void LoadMqttConnections(obs_data_t *obj)
{
GetMqttConnections().clear();
OBSDataArrayAutoRelease array =
obs_data_get_array(obj, "mqttConnections");
size_t count = obs_data_array_count(array);
for (size_t i = 0; i < count; i++) {
OBSDataAutoRelease obj = obs_data_array_item(array, i);
auto connection = MqttConnection::Create();
GetMqttConnections().emplace_back(connection);
GetMqttConnections().back()->Load(obj);
}
}
static bool setup()
{
AddSaveStep(SaveMqttConnections);
AddLoadStep(LoadMqttConnections);
return true;
}
static bool _ = setup();
} // namespace advss

View File

@@ -0,0 +1,136 @@
#pragma once
#include "message-dispatcher.hpp"
#include "item-selection-helpers.hpp"
#include "topic-selection.hpp"
#include <condition_variable>
#include <obs-data.h>
#include <QCheckBox>
#include <QComboBox>
#include <QLayout>
#include <QPushButton>
#include <QSpinBox>
#include <string>
#include <thread>
#include <mqtt/async_client.h>
namespace advss {
using MqttMessageBuffer = std::shared_ptr<MessageBuffer<std::string>>;
using MqttMessageDispatcher = MessageDispatcher<std::string>;
class MqttConnection : public Item {
public:
MqttConnection() = default;
~MqttConnection();
MqttConnection(const MqttConnection &other);
MqttConnection(const std::string &name, const std::string &uri,
const std::string &username, const std::string &password,
bool connectOnStart, bool reconnect, int reconnectDelay);
static std::shared_ptr<Item> Create()
{
return std::make_shared<MqttConnection>();
}
void Connect();
bool SendMessage(const std::string &topic, const std::string &payload,
int qos, bool retained);
void Load(obs_data_t *data);
void Save(obs_data_t *data) const;
MqttMessageBuffer RegisterForEvents();
bool ConnectOnStartup() const { return _connectOnStart; }
QString GetURI() const { return QString::fromStdString(_uri); }
int GetTopicSubscriptionCount() const { return _topics.size(); }
QString GetStatus() const;
private:
void ConnectThread();
void Disconnect();
std::string _uri = "mqtt://localhost:1883";
std::string _username = "user";
std::string _password = "password";
std::vector<std::string> _topics = {"/#"};
std::vector<int> _qos = {1};
std::thread _thread;
bool _connectOnStart = true;
bool _reconnect = true;
int _reconnectDelay = 3;
std::shared_ptr<mqtt::async_client> _client;
std::atomic_bool _disconnect{false};
std::atomic_bool _connected{false};
std::atomic_bool _connecting{false};
std::mutex _clientMtx;
std::mutex _waitMtx;
std::condition_variable _cv;
std::string _lastError = "";
MqttMessageDispatcher _dispatcher;
friend class MqttConnectionSettingsDialog;
};
class MqttConnectionSettingsDialog : public ItemSettingsDialog {
Q_OBJECT
public:
MqttConnectionSettingsDialog(QWidget *parent, const MqttConnection &);
static bool AskForSettings(QWidget *parent, MqttConnection &settings);
private slots:
void ShowPassword();
void HidePassword();
void ReconnectChanged(int state);
void TestConnection();
private:
QLineEdit *_uri;
QLineEdit *_username;
QLineEdit *_password;
QPushButton *_showPassword;
MqttTopicListWidget *_topics;
QCheckBox *_connectOnStart;
QCheckBox *_reconnect;
QSpinBox *_reconnectDelay;
QLabel *_status;
QPushButton *_test;
QGridLayout *_layout;
QTimer *_updateStatusTimer = nullptr;
};
class MqttConnectionSelection : public ItemSelection {
Q_OBJECT
public:
MqttConnectionSelection(QWidget *parent = 0);
void SetConnection(const std::weak_ptr<MqttConnection> &);
};
class MqttConnectionSignalManager : public QObject {
Q_OBJECT
public:
MqttConnectionSignalManager(QObject *parent = nullptr);
static MqttConnectionSignalManager *Instance();
private slots:
void OpenNewConnection(const QString &);
signals:
void Rename(const QString &, const QString &);
void Add(const QString &);
void Remove(const QString &);
};
std::deque<std::shared_ptr<Item>> &GetMqttConnections();
MqttConnection *GetMqttConnectionByName(const QString &);
MqttConnection *GetMqttConnectionByName(const std::string &);
std::weak_ptr<MqttConnection>
GetWeakMqttConnectionByName(const std::string &name);
std::weak_ptr<MqttConnection>
GetWeakMqttConnectionByQString(const QString &name);
std::string GetWeakMqttConnectionName(const std::weak_ptr<MqttConnection> &);
} // namespace advss

232
plugins/mqtt/mqtt-tab.cpp Normal file
View File

@@ -0,0 +1,232 @@
#include "mqtt-tab.hpp"
#include "mqtt-helpers.hpp"
#include "obs-module-helper.hpp"
#include "sync-helpers.hpp"
#include "tab-helpers.hpp"
#include "ui-helpers.hpp"
#include <QTabWidget>
#include <QTimer>
namespace advss {
static bool registerTab();
static void setupTab(QTabWidget *);
static bool registerTabDone = registerTab();
static MqttConnectionsTable *tabWidget = nullptr;
static bool registerTab()
{
AddSetupTabCallback("mqttConnectionTab", MqttConnectionsTable::Create,
setupTab);
return true;
}
static void setTabVisible(QTabWidget *tabWidget, bool visible)
{
SetTabVisibleByName(
tabWidget, visible,
obs_module_text("AdvSceneSwitcher.mqttConnectionTab.title"));
}
MqttConnectionsTable *MqttConnectionsTable::Create()
{
tabWidget = new MqttConnectionsTable();
return tabWidget;
}
void MqttConnectionsTable::Add()
{
auto newConnection = std::make_shared<MqttConnection>();
auto accepted = MqttConnectionSettingsDialog::AskForSettings(
GetSettingsWindow(), *newConnection);
if (!accepted) {
return;
}
{
auto lock = LockContext();
auto &connections = GetMqttConnections();
connections.emplace_back(newConnection);
}
MqttConnectionSignalManager::Instance()->Add(
QString::fromStdString(newConnection->Name()));
}
void MqttConnectionsTable::Remove()
{
auto selectedRows =
tabWidget->Table()->selectionModel()->selectedRows();
if (selectedRows.empty()) {
return;
}
QStringList connectionNames;
for (auto row : selectedRows) {
auto cell = tabWidget->Table()->item(row.row(), 0);
if (!cell) {
continue;
}
connectionNames << cell->text();
}
int connectionNameCount = connectionNames.size();
if (connectionNameCount == 1) {
QString deleteWarning = obs_module_text(
"AdvSceneSwitcher.mqttConnectionTab.removeSingleConnectionPopup.text");
if (!DisplayMessage(deleteWarning.arg(connectionNames.at(0)),
true)) {
return;
}
} else {
QString deleteWarning = obs_module_text(
"AdvSceneSwitcher.mqttConnectionTab.removeMultipleConnectionsPopup.text");
if (!DisplayMessage(deleteWarning.arg(connectionNameCount),
true)) {
return;
}
}
{
auto lock = LockContext();
RemoveItemsByName(GetMqttConnections(), connectionNames);
}
for (const auto &name : connectionNames) {
MqttConnectionSignalManager::Instance()->Remove(name);
}
}
static QStringList getCellLabels(MqttConnection *connection,
bool addName = true)
{
assert(connection);
auto result = QStringList();
if (addName) {
result << QString::fromStdString(connection->Name());
}
result << connection->GetURI()
<< QString::number(connection->GetTopicSubscriptionCount())
<< connection->GetStatus();
return result;
}
static void openSettingsDialog()
{
auto selectedRows =
tabWidget->Table()->selectionModel()->selectedRows();
if (selectedRows.empty()) {
return;
}
auto cell = tabWidget->Table()->item(selectedRows.last().row(), 0);
if (!cell) {
return;
}
auto weakConnection = GetWeakMqttConnectionByQString(cell->text());
auto connection = weakConnection.lock();
if (!connection) {
return;
}
auto oldName = connection->Name();
bool accepted = MqttConnectionSettingsDialog::AskForSettings(
GetSettingsWindow(), *connection.get());
if (accepted && oldName != connection->Name()) {
MqttConnectionSignalManager::Instance()->Rename(
QString::fromStdString(oldName),
QString::fromStdString(connection->Name()));
}
}
static const QStringList headers =
QStringList()
<< obs_module_text("AdvSceneSwitcher.mqttConnectionTab.name.header")
<< obs_module_text("AdvSceneSwitcher.mqttConnectionTab.address.header")
<< obs_module_text("AdvSceneSwitcher.mqttConnectionTab.topics.header")
<< obs_module_text("AdvSceneSwitcher.mqttConnectionTab.status.header");
MqttConnectionsTable::MqttConnectionsTable(QTabWidget *parent)
: ResourceTable(
parent,
obs_module_text("AdvSceneSwitcher.mqttConnectionTab.help"),
obs_module_text(
"AdvSceneSwitcher.mqttConnectionTab.websocketConnectionAddButton.tooltip"),
obs_module_text(
"AdvSceneSwitcher.mqttConnectionTab.websocketConnectionRemoveButton.tooltip"),
headers, openSettingsDialog)
{
for (const auto &connection : GetMqttConnections()) {
auto c = std::static_pointer_cast<MqttConnection>(connection);
AddItemTableRow(Table(), getCellLabels(c.get()));
}
SetHelpVisible(GetMqttConnections().empty());
}
static void updateConnectionStatus(QTableWidget *table)
{
for (int row = 0; row < table->rowCount(); row++) {
auto item = table->item(row, 0);
if (!item) {
continue;
}
auto weakConnection =
GetWeakMqttConnectionByQString(item->text());
auto connection = weakConnection.lock();
if (!connection) {
continue;
}
UpdateItemTableRow(table, row,
getCellLabels(connection.get(), false));
}
}
static void setupTab(QTabWidget *tab)
{
if (GetMqttConnections().empty()) {
setTabVisible(tab, false);
}
QWidget::connect(MqttConnectionSignalManager::Instance(),
&MqttConnectionSignalManager::Rename, tab,
[](const QString &oldName, const QString &newName) {
RenameItemTableRow(tabWidget->Table(), oldName,
newName);
});
QWidget::connect(
MqttConnectionSignalManager::Instance(),
&MqttConnectionSignalManager::Add, tab,
[tab](const QString &name) {
AddItemTableRow(
tabWidget->Table(),
getCellLabels(GetMqttConnectionByName(name)));
tabWidget->SetHelpVisible(false);
tabWidget->HighlightAddButton(false);
setTabVisible(tab, true);
});
QWidget::connect(MqttConnectionSignalManager::Instance(),
&MqttConnectionSignalManager::Remove, tab,
[](const QString &name) {
RemoveItemTableRow(tabWidget->Table(), name);
if (tabWidget->Table()->rowCount() == 0) {
tabWidget->SetHelpVisible(true);
tabWidget->HighlightAddButton(true);
}
});
auto timer = new QTimer(tabWidget);
timer->setInterval(1000);
QWidget::connect(timer, &QTimer::timeout,
[]() { updateConnectionStatus(tabWidget->Table()); });
timer->start();
}
} // namespace advss

20
plugins/mqtt/mqtt-tab.hpp Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include "resource-table.hpp"
namespace advss {
class MqttConnectionsTable final : public ResourceTable {
Q_OBJECT
public:
static MqttConnectionsTable *Create();
private slots:
void Add();
void Remove();
private:
MqttConnectionsTable(QTabWidget *parent = nullptr);
};
} // namespace advss

View File

@@ -0,0 +1,284 @@
#include "topic-selection.hpp"
#include "layout-helpers.hpp"
#include "log-helper.hpp"
#include "obs-module-helper.hpp"
#include "ui-helpers.hpp"
#include <QHeaderView>
#include <QMessageBox>
namespace advss {
MqttTopicListWidget::MqttTopicListWidget(QWidget *parent)
: QWidget(parent),
_table(new QTableWidget(0, 2, this)),
_controls(new ListControls(this, false))
{
_table->setHorizontalHeaderLabels(
QStringList()
<< obs_module_text("AdvSceneSwitcher.mqttConnection.topic")
<< obs_module_text("AdvSceneSwitcher.mqttConnection.qos"));
_table->horizontalHeader()->setStretchLastSection(true);
_table->setSelectionBehavior(QAbstractItemView::SelectRows);
_table->setSelectionMode(QAbstractItemView::SingleSelection);
_table->setEditTriggers(QAbstractItemView::DoubleClicked |
QAbstractItemView::SelectedClicked);
_table->verticalHeader()->hide();
_table->horizontalHeader()->setSectionResizeMode(0,
QHeaderView::Stretch);
_table->horizontalHeader()->setSectionResizeMode(
1, QHeaderView::ResizeToContents);
connect(_table, &QTableWidget::itemChanged, this,
&MqttTopicListWidget::ModifyRow);
connect(_controls, &ListControls::Add, this,
&MqttTopicListWidget::AddTopic);
connect(_controls, &ListControls::Remove, this,
&MqttTopicListWidget::RemoveSelectedRow);
auto mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(_table);
mainLayout->addWidget(_controls);
setLayout(mainLayout);
}
void MqttTopicListWidget::SetValues(const std::vector<std::string> &topics,
const std::vector<int> &qos)
{
assert(topics.size() == qos.size());
if (topics.size() != qos.size()) {
blog(LOG_WARNING, "%s topics and qos size mismatch", __func__);
return;
}
_table->setRowCount(0);
_topicSet.clear();
std::vector<std::pair<QString, int>> entries;
for (size_t i = 0; i < topics.size(); ++i) {
auto topic = QString::fromStdString(topics[i]).trimmed();
int qos_ = qos[i];
if (topic.isEmpty()) {
blog(LOG_INFO, "%s ignoring empty topic entry",
__func__);
continue;
}
if (_topicSet.contains(topic)) {
blog(LOG_INFO, "%s discarding duplicate topic \"%s\"",
__func__, topics[i].c_str());
continue;
}
if (qos_ < 0 || qos_ > 2) {
blog(LOG_INFO,
"%s discard invalid QoS level \"%d\" and set to \"1\"",
__func__, qos[i]);
qos_ = 1;
}
entries.emplace_back(topic, qos_);
_topicSet.insert(topic);
}
for (const auto &entry : entries) {
int row = _table->rowCount();
_table->insertRow(row);
_table->setItem(row, 0, new QTableWidgetItem(entry.first));
_table->setItem(
row, 1,
new QTableWidgetItem(QString::number(entry.second)));
}
SortTable();
}
std::vector<std::string> MqttTopicListWidget::GetTopics()
{
std::vector<std::string> result;
for (int i = 0; i < _table->rowCount(); ++i) {
auto topic = _table->item(i, 0)->text().trimmed();
result.push_back(topic.toStdString());
}
return result;
}
std::vector<int> MqttTopicListWidget::GetQoS()
{
std::vector<int> result;
for (int i = 0; i < _table->rowCount(); ++i) {
bool isValidInt = false;
int qos = _table->item(i, 1)->text().toInt(&isValidInt);
result.push_back(isValidInt ? qos : 1);
}
return result;
}
void MqttTopicListWidget::AddTopic()
{
AddMqttTopicDialog dialog(this);
if (dialog.exec() != QDialog::Accepted) {
return;
}
auto topic = dialog.Topic();
int qos = dialog.QoS();
if (topic.isEmpty()) {
ShowTopicEmptyWarning();
return;
}
if (_topicSet.contains(topic)) {
ShowTopicDuplicateWarning();
return;
}
InsertSorted(topic, qos);
_topicSet.insert(topic);
}
void MqttTopicListWidget::InsertSorted(const QString &topic, int qos)
{
int row = 0;
while (row < _table->rowCount()) {
QString current = _table->item(row, 0)->text();
if (topic.compare(current, Qt::CaseInsensitive) < 0)
break;
++row;
}
_table->insertRow(row);
_table->setItem(row, 0, new QTableWidgetItem(topic));
_table->setItem(row, 1, new QTableWidgetItem(QString::number(qos)));
}
void MqttTopicListWidget::SortTable()
{
_table->sortItems(0, Qt::AscendingOrder);
}
void MqttTopicListWidget::ShowTopicDuplicateWarning()
{
QMessageBox::warning(
this,
obs_module_text(
"AdvSceneSwitcher.mqttConnection.inputWarning.duplicateTopic.title"),
obs_module_text(
"AdvSceneSwitcher.mqttConnection.inputWarning.duplicateTopic"));
}
void MqttTopicListWidget::ShowTopicEmptyWarning()
{
QMessageBox::warning(
this,
obs_module_text(
"AdvSceneSwitcher.mqttConnection.inputWarning.emptyTopic.title"),
obs_module_text(
"AdvSceneSwitcher.mqttConnection.inputWarning.emptyTopic"));
}
void MqttTopicListWidget::ShowQoSRangeWarning()
{
QMessageBox::warning(
this,
obs_module_text(
"AdvSceneSwitcher.mqttConnection.inputWarning.qosRange.title"),
obs_module_text(
"AdvSceneSwitcher.mqttConnection.inputWarning.qosRange"));
}
void MqttTopicListWidget::RemoveSelectedRow()
{
auto selected = _table->currentRow();
if (selected >= 0) {
QString topic = _table->item(selected, 0)->text();
_topicSet.remove(topic);
_table->removeRow(selected);
}
}
void MqttTopicListWidget::ModifyRow(QTableWidgetItem *item)
{
if (!item) {
return;
}
int row = item->row();
auto tableItemTopic = _table->item(row, 0);
auto tableItemQos = _table->item(row, 1);
if (!tableItemTopic || !tableItemQos) {
return;
}
QString topic = tableItemTopic->text().trimmed();
QString qosStr = tableItemQos->text().trimmed();
// Validate topic column
if (item->column() == 0) {
QString oldTopic = _topicSet.values().value(row);
if (topic.isEmpty()) {
ShowTopicEmptyWarning();
item->setText(oldTopic);
return;
}
if (_topicSet.contains(topic) && topic != oldTopic) {
ShowTopicDuplicateWarning();
item->setText(oldTopic);
return;
}
_topicSet.remove(oldTopic);
_topicSet.insert(topic);
SortTable();
}
// Validate QoS column
if (item->column() == 1) {
bool isValidInt;
int qos = qosStr.toInt(&isValidInt);
if (!isValidInt || qos < 0 || qos > 2) {
ShowQoSRangeWarning();
item->setText("1");
}
}
SortTable();
}
AddMqttTopicDialog::AddMqttTopicDialog(QWidget *parent)
: QDialog(parent),
_topicEdit(new QLineEdit(this)),
_qosSpin(new QSpinBox(this))
{
setWindowTitle(
obs_module_text("AdvSceneSwitcher.mqttConnection.add.title"));
_topicEdit->setText("/#");
_qosSpin->setRange(0, 2);
_qosSpin->setValue(1);
auto buttonbox = new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel);
buttonbox->setCenterButtons(true);
connect(buttonbox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonbox, &QDialogButtonBox::rejected, this, &QDialog::reject);
auto inputLayout = new QHBoxLayout();
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.mqttConnection.add.layout"),
inputLayout, {{"{{topic}}", _topicEdit}, {"{{QoS}}", _qosSpin}},
false);
auto layout = new QVBoxLayout();
layout->addLayout(inputLayout);
layout->addWidget(buttonbox);
setLayout(layout);
}
QString AddMqttTopicDialog::Topic() const
{
return _topicEdit->text().trimmed();
}
int AddMqttTopicDialog::QoS() const
{
return _qosSpin->value();
}
} // namespace advss

View File

@@ -0,0 +1,56 @@
#pragma once
#include "string-list.hpp"
#include "list-controls.hpp"
#include "variable-line-edit.hpp"
#include <QDialog>
#include <QLineEdit>
#include <QSet>
#include <QSpinBox>
#include <QTableWidget>
namespace advss {
class MqttTopicListWidget : public QWidget {
Q_OBJECT
public:
MqttTopicListWidget(QWidget *parent = nullptr);
void SetValues(const std::vector<std::string> &topics,
const std::vector<int> &qos);
std::vector<std::string> GetTopics();
std::vector<int> GetQoS();
private slots:
void AddTopic();
void RemoveSelectedRow();
void ModifyRow(QTableWidgetItem *);
private:
void InsertSorted(const QString &topic, int qos);
void SortTable();
void ShowTopicDuplicateWarning();
void ShowTopicEmptyWarning();
void ShowQoSRangeWarning();
QTableWidget *_table;
ListControls *_controls;
QSet<QString> _topicSet;
};
class AddMqttTopicDialog : public QDialog {
Q_OBJECT
public:
AddMqttTopicDialog(QWidget *parent = nullptr);
QString Topic() const;
int QoS() const;
private:
QLineEdit *_topicEdit;
QSpinBox *_qosSpin;
};
} // namespace advss

View File

@@ -16,8 +16,19 @@ endif()
# --- Check optional OCR dependencies --- # --- Check optional OCR dependencies ---
find_package(Leptonica) find_package(Leptonica QUIET)
find_package(Tesseract) find_package(Tesseract QUIET)
if(OS_LINUX AND NOT Leptonica_FOUND)
find_package(PkgConfig REQUIRED)
pkg_check_modules(Tesseract IMPORTED_TARGET tesseract lept)
if(Tesseract_FOUND)
add_library(Tesseract::libtesseract ALIAS PkgConfig::Tesseract)
set(Leptonica_FOUND TRUE)
set(Tesseract_FOUND TRUE)
endif()
endif()
# --- End of section --- # --- End of section ---

View File

@@ -6,6 +6,7 @@
#include <plugin-state-helpers.hpp> #include <plugin-state-helpers.hpp>
#include <QBuffer> #include <QBuffer>
#include <QColorDialog> #include <QColorDialog>
#include <QDesktopServices>
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
#include <QtGlobal> #include <QtGlobal>
@@ -85,18 +86,6 @@ const static std::map<tesseract::PageSegMode, std::string> pageSegModes = {
"AdvSceneSwitcher.condition.video.ocrMode.sparseTextOSD"}, "AdvSceneSwitcher.condition.video.ocrMode.sparseTextOSD"},
}; };
cv::CascadeClassifier initObjectCascade(std::string &path)
{
cv::CascadeClassifier cascade;
try {
cascade.load(path);
} catch (...) {
blog(LOG_WARNING, "failed to load model data \"%s\"",
path.c_str());
}
return cascade;
}
static bool requiresFileInput(VideoCondition t) static bool requiresFileInput(VideoCondition t)
{ {
return t == VideoCondition::MATCH || t == VideoCondition::DIFFER || return t == VideoCondition::MATCH || t == VideoCondition::DIFFER ||
@@ -211,10 +200,6 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
(void)LoadImageFromFile(); (void)LoadImageFromFile();
} }
if (_condition == VideoCondition::OBJECT) {
LoadModelData(_objMatchParameters.modelPath);
}
return true; return true;
} }
@@ -234,8 +219,9 @@ void MacroConditionVideo::GetScreenshot(bool blocking)
_areaParameters.area.width, _areaParameters.area.width,
_areaParameters.area.height); _areaParameters.area.height);
} }
new (&_screenshotData) Screenshot(source, screenshotArea, blocking, const int timeout = GetIntervalValue() < 300 ? 300 : GetIntervalValue();
GetIntervalValue()); new (&_screenshotData)
Screenshot(source, screenshotArea, blocking, timeout);
obs_source_release(source); obs_source_release(source);
_getNextScreenshot = false; _getNextScreenshot = false;
} }
@@ -263,28 +249,21 @@ bool MacroConditionVideo::LoadImageFromFile()
return true; return true;
} }
bool MacroConditionVideo::LoadModelData(std::string &path)
{
_objMatchParameters.modelPath = path;
_objMatchParameters.cascade = initObjectCascade(path);
return !_objMatchParameters.cascade.empty();
}
std::string MacroConditionVideo::GetModelDataPath() const
{
return _objMatchParameters.modelPath;
}
void MacroConditionVideo::SetPageSegMode(tesseract::PageSegMode mode) void MacroConditionVideo::SetPageSegMode(tesseract::PageSegMode mode)
{ {
_ocrParameters.SetPageMode(mode); _ocrParameters.SetPageMode(mode);
} }
bool MacroConditionVideo::SetLanguage(const std::string &language) bool MacroConditionVideo::SetLanguageCode(const std::string &language)
{ {
return _ocrParameters.SetLanguageCode(language); return _ocrParameters.SetLanguageCode(language);
} }
bool MacroConditionVideo::SetTesseractBaseDir(const std::string &dir)
{
return _ocrParameters.SetTesseractBasePath(dir);
}
void MacroConditionVideo::SetCondition(VideoCondition condition) void MacroConditionVideo::SetCondition(VideoCondition condition)
{ {
_condition = condition; _condition = condition;
@@ -337,8 +316,11 @@ bool MacroConditionVideo::OutputChanged()
bool MacroConditionVideo::ScreenshotContainsObject() bool MacroConditionVideo::ScreenshotContainsObject()
{ {
auto objects = MatchObject(_screenshotData.GetImage(), auto model = _objMatchParameters.GetModel();
_objMatchParameters.cascade, if (!model) {
return false;
}
auto objects = MatchObject(_screenshotData.GetImage(), *model,
_objMatchParameters.scaleFactor, _objMatchParameters.scaleFactor,
_objMatchParameters.minNeighbors, _objMatchParameters.minNeighbors,
_objMatchParameters.minSize.CV(), _objMatchParameters.minSize.CV(),
@@ -364,12 +346,16 @@ bool MacroConditionVideo::CheckOCR()
auto text = RunOCR(_ocrParameters.GetOCR(), _screenshotData.GetImage(), auto text = RunOCR(_ocrParameters.GetOCR(), _screenshotData.GetImage(),
_ocrParameters.color, _ocrParameters.colorThreshold); _ocrParameters.color, _ocrParameters.colorThreshold);
SetVariableValue(text); if (!text) {
SetTempVarValue("text", text); return false;
}
SetVariableValue(*text);
SetTempVarValue("text", *text);
if (!_ocrParameters.regex.Enabled()) { if (!_ocrParameters.regex.Enabled()) {
return text == std::string(_ocrParameters.text); return text == std::string(_ocrParameters.text);
} }
return _ocrParameters.regex.Matches(text, _ocrParameters.text); return _ocrParameters.regex.Matches(*text, _ocrParameters.text);
} }
bool MacroConditionVideo::CheckColor() bool MacroConditionVideo::CheckColor()
@@ -514,7 +500,7 @@ BrightnessEdit::BrightnessEdit(QWidget *parent,
obs_module_text( obs_module_text(
"AdvSceneSwitcher.condition.video.brightnessThresholdDescription"))), "AdvSceneSwitcher.condition.video.brightnessThresholdDescription"))),
_current(new QLabel), _current(new QLabel),
_data(data) _entryData(data)
{ {
auto layout = new QVBoxLayout; auto layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
@@ -532,7 +518,7 @@ BrightnessEdit::BrightnessEdit(QWidget *parent,
&BrightnessEdit::UpdateCurrentBrightness); &BrightnessEdit::UpdateCurrentBrightness);
_timer.start(1000); _timer.start(1000);
_threshold->SetDoubleValue(_data->_brightnessThreshold); _threshold->SetDoubleValue(_entryData->_brightnessThreshold);
_loading = false; _loading = false;
} }
@@ -540,17 +526,34 @@ void BrightnessEdit::UpdateCurrentBrightness()
{ {
QString text = obs_module_text( QString text = obs_module_text(
"AdvSceneSwitcher.condition.video.currentBrightness"); "AdvSceneSwitcher.condition.video.currentBrightness");
_current->setText(text.arg(_data->GetCurrentBrightness())); _current->setText(text.arg(_entryData->GetCurrentBrightness()));
} }
void BrightnessEdit::BrightnessThresholdChanged(const DoubleVariable &value) void BrightnessEdit::BrightnessThresholdChanged(const DoubleVariable &value)
{ {
if (_loading || !_data) { GUARD_LOADING_AND_LOCK();
return; _entryData->_brightnessThreshold = value;
}
static void openFileInEditor(const std::string &filepath)
{
const auto path = QString::fromStdString(filepath);
const QFileInfo fileInfo(path);
if (!fileInfo.exists()) {
QFile file(path);
if (!file.open(QIODevice::WriteOnly)) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrOpenConfig.createFailed"));
return;
}
file.close();
} }
auto lock = LockContext(); QUrl fileUrl = QUrl::fromLocalFile(path);
_data->_brightnessThreshold = value; if (!QDesktopServices::openUrl(fileUrl)) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrOpenConfig.openFailed"));
}
} }
OCREdit::OCREdit(QWidget *parent, PreviewDialog *previewDialog, OCREdit::OCREdit(QWidget *parent, PreviewDialog *previewDialog,
@@ -569,12 +572,27 @@ OCREdit::OCREdit(QWidget *parent, PreviewDialog *previewDialog,
"AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription"), "AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription"),
true)), true)),
_pageSegMode(new QComboBox()), _pageSegMode(new QComboBox()),
_tesseractBaseDir(new FileSelection(FileSelection::Type::FOLDER)),
_languageCode(new VariableLineEdit(this)), _languageCode(new VariableLineEdit(this)),
_useConfig(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrUseConfigFile"))),
_configFile(new FileSelection(FileSelection::Type::WRITE, this)),
_openConfigFile(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrOpenConfigFile"))),
_reloadConfig(new QPushButton()),
_configLayout(new QHBoxLayout()),
_previewDialog(previewDialog), _previewDialog(previewDialog),
_data(data) _entryData(data)
{ {
populatePageSegModeSelection(_pageSegMode); populatePageSegModeSelection(_pageSegMode);
_reloadConfig->setMaximumWidth(22);
SetButtonIcon(_reloadConfig, GetThemeTypeName() == "Light"
? ":res/images/refresh.svg"
: "theme:Dark/refresh.svg");
_reloadConfig->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrConfigReload"));
QWidget::connect(_selectColor, SIGNAL(clicked()), this, QWidget::connect(_selectColor, SIGNAL(clicked()), this,
SLOT(SelectColorClicked())); SLOT(SelectColorClicked()));
QWidget::connect( QWidget::connect(
@@ -589,14 +607,45 @@ OCREdit::OCREdit(QWidget *parent, PreviewDialog *previewDialog,
SLOT(RegexChanged(const RegexConfig &))); SLOT(RegexChanged(const RegexConfig &)));
QWidget::connect(_pageSegMode, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_pageSegMode, SIGNAL(currentIndexChanged(int)), this,
SLOT(PageSegModeChanged(int))); SLOT(PageSegModeChanged(int)));
QWidget::connect(_tesseractBaseDir,
SIGNAL(PathChanged(const QString &)), this,
SLOT(TesseractBaseDirChanged(const QString &)));
QWidget::connect(_languageCode, SIGNAL(editingFinished()), this, QWidget::connect(_languageCode, SIGNAL(editingFinished()), this,
SLOT(LanguageChanged())); SLOT(LanguageChanged()));
QWidget::connect(_useConfig, SIGNAL(stateChanged(int)), this,
SLOT(UseConfigChanged(int)));
QWidget::connect(_configFile, SIGNAL(PathChanged(const QString &)),
this, SLOT(ConfigFileChanged(const QString &)));
QWidget::connect(_openConfigFile, &QPushButton::clicked, [this](bool) {
openFileInEditor(
_entryData->_ocrParameters.GetCustomConfigFile());
});
QWidget::connect(_reloadConfig, &QPushButton::clicked, [this](bool) {
_entryData->_ocrParameters.EnableCustomConfig(true);
_previewDialog->OCRParametersChanged(
_entryData->_ocrParameters);
});
std::unordered_map<std::string, QWidget *> widgetPlaceholders = { auto configFileHint = new QLabel();
const QString path = GetThemeTypeName() == "Light"
? ":/res/images/help.svg"
: ":/res/images/help_light.svg";
const QIcon icon(path);
const QPixmap pixmap = icon.pixmap(QSize(16, 16));
configFileHint->setPixmap(pixmap);
configFileHint->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrConfigHint"));
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{textColor}}", _textColor}, {"{{textColor}}", _textColor},
{"{{selectColor}}", _selectColor}, {"{{selectColor}}", _selectColor},
{"{{textType}}", _pageSegMode}, {"{{textType}}", _pageSegMode},
{"{{tesseractBaseDir}}", _tesseractBaseDir},
{"{{languageCode}}", _languageCode}, {"{{languageCode}}", _languageCode},
{"{{configFile}}", _configFile},
{"{{openConfigFile}}", _openConfigFile},
{"{{reloadConfig}}", _reloadConfig},
{"{{configFileHint}}", configFileHint},
}; };
auto layout = new QVBoxLayout(); auto layout = new QVBoxLayout();
@@ -609,31 +658,51 @@ OCREdit::OCREdit(QWidget *parent, PreviewDialog *previewDialog,
auto pageModeSegLayout = new QHBoxLayout(); auto pageModeSegLayout = new QHBoxLayout();
PlaceWidgets( PlaceWidgets(
obs_module_text( obs_module_text(
"AdvSceneSwitcher.condition.video.entry.orcTextType"), "AdvSceneSwitcher.condition.video.layout.ocrTextType"),
pageModeSegLayout, widgetPlaceholders); pageModeSegLayout, widgetPlaceholders);
layout->addLayout(pageModeSegLayout); layout->addLayout(pageModeSegLayout);
auto baseDirLayout = new QHBoxLayout();
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.layout.ocrBaseDir"),
baseDirLayout, widgetPlaceholders, false);
layout->addLayout(baseDirLayout);
auto languageLayout = new QHBoxLayout(); auto languageLayout = new QHBoxLayout();
PlaceWidgets( PlaceWidgets(
obs_module_text( obs_module_text(
"AdvSceneSwitcher.condition.video.entry.orcLanguage"), "AdvSceneSwitcher.condition.video.layout.ocrLanguage"),
languageLayout, widgetPlaceholders); languageLayout, widgetPlaceholders);
layout->addLayout(languageLayout); layout->addLayout(languageLayout);
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.layout.ocrConfig"),
_configLayout, widgetPlaceholders, false);
layout->addWidget(_useConfig);
layout->addLayout(_configLayout);
auto colorPickLayout = new QHBoxLayout(); auto colorPickLayout = new QHBoxLayout();
PlaceWidgets( PlaceWidgets(
obs_module_text( obs_module_text(
"AdvSceneSwitcher.condition.video.entry.orcColorPick"), "AdvSceneSwitcher.condition.video.layout.ocrColorPick"),
colorPickLayout, widgetPlaceholders); colorPickLayout, widgetPlaceholders);
layout->addLayout(colorPickLayout); layout->addLayout(colorPickLayout);
layout->addWidget(_colorThreshold); layout->addWidget(_colorThreshold);
setLayout(layout); setLayout(layout);
_matchText->setPlainText(_data->_ocrParameters.text); _matchText->setPlainText(_entryData->_ocrParameters.text);
_regex->SetRegexConfig(_data->_ocrParameters.regex); _regex->SetRegexConfig(_entryData->_ocrParameters.regex);
SetupColorLabel(_data->_ocrParameters.color); SetupColorLabel(_entryData->_ocrParameters.color);
_colorThreshold->SetDoubleValue(_data->_ocrParameters.colorThreshold); _colorThreshold->SetDoubleValue(
_entryData->_ocrParameters.colorThreshold);
_pageSegMode->setCurrentIndex(_pageSegMode->findData( _pageSegMode->setCurrentIndex(_pageSegMode->findData(
static_cast<int>(_data->_ocrParameters.GetPageMode()))); static_cast<int>(_entryData->_ocrParameters.GetPageMode())));
_languageCode->setText(_data->_ocrParameters.GetLanguageCode()); _tesseractBaseDir->SetPath(
_entryData->_ocrParameters.GetTesseractBasePath());
_languageCode->setText(_entryData->_ocrParameters.GetLanguageCode());
_useConfig->setChecked(
_entryData->_ocrParameters.CustomConfigIsEnabled());
_configFile->SetPath(_entryData->_ocrParameters.GetCustomConfigFile());
SetLayoutVisible(_configLayout,
_entryData->_ocrParameters.CustomConfigIsEnabled());
_loading = false; _loading = false;
} }
@@ -646,12 +715,12 @@ void OCREdit::SetupColorLabel(const QColor &color)
void OCREdit::SelectColorClicked() void OCREdit::SelectColorClicked()
{ {
if (_loading || !_data) { if (_loading || !_entryData) {
return; return;
} }
const QColor color = QColorDialog::getColor( const QColor color = QColorDialog::getColor(
_data->_ocrParameters.color, this, _entryData->_ocrParameters.color, this,
obs_module_text("AdvSceneSwitcher.condition.video.selectColor"), obs_module_text("AdvSceneSwitcher.condition.video.selectColor"),
QColorDialog::ColorDialogOption()); QColorDialog::ColorDialogOption());
@@ -661,87 +730,104 @@ void OCREdit::SelectColorClicked()
SetupColorLabel(color); SetupColorLabel(color);
auto lock = LockContext(); auto lock = LockContext();
_data->_ocrParameters.color = color; _entryData->_ocrParameters.color = color;
_previewDialog->OCRParametersChanged(_data->_ocrParameters); _previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
} }
void OCREdit::ColorThresholdChanged(const DoubleVariable &value) void OCREdit::ColorThresholdChanged(const DoubleVariable &value)
{ {
if (_loading || !_data) { GUARD_LOADING_AND_LOCK();
return; _entryData->_ocrParameters.colorThreshold = value;
}
auto lock = LockContext(); _previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
_data->_ocrParameters.colorThreshold = value;
_previewDialog->OCRParametersChanged(_data->_ocrParameters);
} }
void OCREdit::MatchTextChanged() void OCREdit::MatchTextChanged()
{ {
if (_loading || !_data) { GUARD_LOADING_AND_LOCK();
return; _entryData->_ocrParameters.text =
}
auto lock = LockContext();
_data->_ocrParameters.text =
_matchText->toPlainText().toUtf8().constData(); _matchText->toPlainText().toUtf8().constData();
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();
_previewDialog->OCRParametersChanged(_data->_ocrParameters); _previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
} }
void OCREdit::RegexChanged(const RegexConfig &conf) void OCREdit::RegexChanged(const RegexConfig &conf)
{ {
if (_loading || !_data) { GUARD_LOADING_AND_LOCK();
return; _entryData->_ocrParameters.regex = conf;
}
auto lock = LockContext();
_data->_ocrParameters.regex = conf;
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();
_previewDialog->OCRParametersChanged(_data->_ocrParameters); _previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
} }
void OCREdit::PageSegModeChanged(int idx) void OCREdit::PageSegModeChanged(int idx)
{ {
if (_loading || !_data) { GUARD_LOADING_AND_LOCK();
return; _entryData->SetPageSegMode(static_cast<tesseract::PageSegMode>(
}
auto lock = LockContext();
_data->SetPageSegMode(static_cast<tesseract::PageSegMode>(
_pageSegMode->itemData(idx).toInt())); _pageSegMode->itemData(idx).toInt()));
_previewDialog->OCRParametersChanged(_data->_ocrParameters); _previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
} }
void OCREdit::LanguageChanged() void OCREdit::TesseractBaseDirChanged(const QString &path)
{ {
if (_loading || !_data) { GUARD_LOADING_AND_LOCK();
return; if (!_entryData->SetTesseractBaseDir(path.toStdString())) {
}
auto lock = LockContext();
if (!_data->SetLanguage(_languageCode->text().toStdString())) {
const QString message(obs_module_text( const QString message(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrLanguageNotFound")); "AdvSceneSwitcher.condition.video.ocrLanguageNotFound"));
const QDir dataDir( const QDir dataDir(path);
obs_get_module_data_path(obs_current_module()));
const QString fileName(_languageCode->text() + ".traineddata"); const QString fileName(_languageCode->text() + ".traineddata");
DisplayMessage(message.arg(fileName, dataDir.absolutePath())); DisplayMessage(message.arg(fileName, dataDir.absolutePath()));
// Reset to previous value // Reset to previous value
const QSignalBlocker b(this); const QSignalBlocker b(this);
_languageCode->setText(_data->_ocrParameters.GetLanguageCode()); _tesseractBaseDir->SetPath(
_entryData->_ocrParameters.GetTesseractBasePath());
return; return;
} }
_previewDialog->OCRParametersChanged(_data->_ocrParameters); _previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
}
void OCREdit::LanguageChanged()
{
GUARD_LOADING_AND_LOCK();
if (!_entryData->SetLanguageCode(_languageCode->text().toStdString())) {
const QString message(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrLanguageNotFound"));
const QDir dataDir(QString::fromStdString(
_entryData->_ocrParameters.GetTesseractBasePath()));
const QString fileName(_languageCode->text() + ".traineddata");
DisplayMessage(message.arg(fileName, dataDir.absolutePath()));
// Reset to previous value
const QSignalBlocker b(this);
_languageCode->setText(
_entryData->_ocrParameters.GetLanguageCode());
return;
}
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
}
void OCREdit::UseConfigChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_ocrParameters.EnableCustomConfig(value);
SetLayoutVisible(_configLayout, value);
adjustSize();
updateGeometry();
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
}
void OCREdit::ConfigFileChanged(const QString &path)
{
GUARD_LOADING_AND_LOCK();
_entryData->_ocrParameters.SetCustomConfigFile(path.toStdString());
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
} }
ObjectDetectEdit::ObjectDetectEdit( ObjectDetectEdit::ObjectDetectEdit(
@@ -761,7 +847,7 @@ ObjectDetectEdit::ObjectDetectEdit(
_minSize(new SizeSelection(0, 1024)), _minSize(new SizeSelection(0, 1024)),
_maxSize(new SizeSelection(0, 4096)), _maxSize(new SizeSelection(0, 4096)),
_previewDialog(previewDialog), _previewDialog(previewDialog),
_data(data) _entryData(data)
{ {
_minNeighbors->setMinimum(minMinNeighbors); _minNeighbors->setMinimum(minMinNeighbors);
_minNeighbors->setMaximum(maxMinNeighbors); _minNeighbors->setMaximum(maxMinNeighbors);
@@ -792,14 +878,14 @@ ObjectDetectEdit::ObjectDetectEdit(
pathLayout->setContentsMargins(0, 0, 0, 0); pathLayout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets( PlaceWidgets(
obs_module_text( obs_module_text(
"AdvSceneSwitcher.condition.video.entry.modelPath"), "AdvSceneSwitcher.condition.video.layout.modelPath"),
pathLayout, widgetPlaceholders); pathLayout, widgetPlaceholders);
auto neighborsLayout = new QHBoxLayout; auto neighborsLayout = new QHBoxLayout;
neighborsLayout->setContentsMargins(0, 0, 0, 0); neighborsLayout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets( PlaceWidgets(
obs_module_text( obs_module_text(
"AdvSceneSwitcher.condition.video.entry.minNeighbor"), "AdvSceneSwitcher.condition.video.layout.minNeighbor"),
neighborsLayout, widgetPlaceholders); neighborsLayout, widgetPlaceholders);
auto sizeGrid = new QGridLayout; auto sizeGrid = new QGridLayout;
@@ -825,62 +911,50 @@ ObjectDetectEdit::ObjectDetectEdit(
layout->addLayout(sizeLayout); layout->addLayout(sizeLayout);
setLayout(layout); setLayout(layout);
_modelDataPath->SetPath(_data->GetModelDataPath()); _modelDataPath->SetPath(_entryData->_objMatchParameters.GetModelPath());
_objectScaleThreshold->SetDoubleValue( _objectScaleThreshold->SetDoubleValue(
_data->_objMatchParameters.scaleFactor); _entryData->_objMatchParameters.scaleFactor);
_minNeighbors->setValue(_data->_objMatchParameters.minNeighbors); _minNeighbors->setValue(_entryData->_objMatchParameters.minNeighbors);
_minSize->SetSize(_data->_objMatchParameters.minSize); _minSize->SetSize(_entryData->_objMatchParameters.minSize);
_maxSize->SetSize(_data->_objMatchParameters.maxSize); _maxSize->SetSize(_entryData->_objMatchParameters.maxSize);
_loading = false; _loading = false;
} }
void ObjectDetectEdit::ObjectScaleThresholdChanged(const DoubleVariable &value) void ObjectDetectEdit::ObjectScaleThresholdChanged(const DoubleVariable &value)
{ {
if (_loading || !_data) { GUARD_LOADING_AND_LOCK();
return; _entryData->_objMatchParameters.scaleFactor = value;
} _previewDialog->ObjDetectParametersChanged(
_entryData->_objMatchParameters);
auto lock = LockContext();
_data->_objMatchParameters.scaleFactor = value;
_previewDialog->ObjDetectParametersChanged(_data->_objMatchParameters);
} }
void ObjectDetectEdit::MinNeighborsChanged(int value) void ObjectDetectEdit::MinNeighborsChanged(int value)
{ {
if (_loading || !_data) { GUARD_LOADING_AND_LOCK();
return; _entryData->_objMatchParameters.minNeighbors = value;
} _previewDialog->ObjDetectParametersChanged(
_entryData->_objMatchParameters);
auto lock = LockContext();
_data->_objMatchParameters.minNeighbors = value;
_previewDialog->ObjDetectParametersChanged(_data->_objMatchParameters);
} }
void ObjectDetectEdit::MinSizeChanged(advss::Size value) void ObjectDetectEdit::MinSizeChanged(advss::Size value)
{ {
if (_loading || !_data) { GUARD_LOADING_AND_LOCK();
return; _entryData->_objMatchParameters.minSize = value;
} _previewDialog->ObjDetectParametersChanged(
_entryData->_objMatchParameters);
auto lock = LockContext();
_data->_objMatchParameters.minSize = value;
_previewDialog->ObjDetectParametersChanged(_data->_objMatchParameters);
} }
void ObjectDetectEdit::MaxSizeChanged(advss::Size value) void ObjectDetectEdit::MaxSizeChanged(advss::Size value)
{ {
if (_loading || !_data) { GUARD_LOADING_AND_LOCK();
return; _entryData->_objMatchParameters.maxSize = value;
} _previewDialog->ObjDetectParametersChanged(
_entryData->_objMatchParameters);
auto lock = LockContext();
_data->_objMatchParameters.maxSize = value;
_previewDialog->ObjDetectParametersChanged(_data->_objMatchParameters);
} }
void ObjectDetectEdit::ModelPathChanged(const QString &text) void ObjectDetectEdit::ModelPathChanged(const QString &text)
{ {
if (_loading || !_data) { if (_loading || !_entryData) {
return; return;
} }
@@ -888,13 +962,14 @@ void ObjectDetectEdit::ModelPathChanged(const QString &text)
{ {
auto lock = LockContext(); auto lock = LockContext();
std::string path = text.toStdString(); std::string path = text.toStdString();
dataLoaded = _data->LoadModelData(path); dataLoaded = _entryData->_objMatchParameters.SetModelPath(path);
} }
if (!dataLoaded) { if (!dataLoaded) {
DisplayMessage(obs_module_text( DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.modelLoadFail")); "AdvSceneSwitcher.condition.video.modelLoadFail"));
} }
_previewDialog->ObjDetectParametersChanged(_data->_objMatchParameters); _previewDialog->ObjDetectParametersChanged(
_entryData->_objMatchParameters);
} }
ColorEdit::ColorEdit(QWidget *parent, ColorEdit::ColorEdit(QWidget *parent,
@@ -917,7 +992,7 @@ ColorEdit::ColorEdit(QWidget *parent,
_color(new QLabel), _color(new QLabel),
_selectColor(new QPushButton(obs_module_text( _selectColor(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.video.selectColor"))), "AdvSceneSwitcher.condition.video.selectColor"))),
_data(data) _entryData(data)
{ {
QWidget::connect(_selectColor, SIGNAL(clicked()), this, QWidget::connect(_selectColor, SIGNAL(clicked()), this,
SLOT(SelectColorClicked())); SLOT(SelectColorClicked()));
@@ -938,9 +1013,9 @@ ColorEdit::ColorEdit(QWidget *parent,
}; };
auto colorLayout = new QHBoxLayout; auto colorLayout = new QHBoxLayout;
PlaceWidgets( PlaceWidgets(obs_module_text(
obs_module_text("AdvSceneSwitcher.condition.video.entry.color"), "AdvSceneSwitcher.condition.video.layout.color"),
colorLayout, widgetPlaceholders); colorLayout, widgetPlaceholders);
auto layout = new QVBoxLayout; auto layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
@@ -949,9 +1024,11 @@ ColorEdit::ColorEdit(QWidget *parent,
layout->addWidget(_matchThreshold); layout->addWidget(_matchThreshold);
setLayout(layout); setLayout(layout);
_matchThreshold->SetDoubleValue(_data->_colorParameters.matchThreshold); _matchThreshold->SetDoubleValue(
_colorThreshold->SetDoubleValue(_data->_colorParameters.colorThreshold); _entryData->_colorParameters.matchThreshold);
SetupColorLabel(_data->_colorParameters.color); _colorThreshold->SetDoubleValue(
_entryData->_colorParameters.colorThreshold);
SetupColorLabel(_entryData->_colorParameters.color);
_loading = false; _loading = false;
} }
@@ -964,32 +1041,24 @@ void ColorEdit::SetupColorLabel(const QColor &color)
void ColorEdit::MatchThresholdChanged(const DoubleVariable &value) void ColorEdit::MatchThresholdChanged(const DoubleVariable &value)
{ {
if (_loading || !_data) { GUARD_LOADING_AND_LOCK();
return; _entryData->_colorParameters.matchThreshold = value;
}
auto lock = LockContext();
_data->_colorParameters.matchThreshold = value;
} }
void ColorEdit::ColorThresholdChanged(const DoubleVariable &value) void ColorEdit::ColorThresholdChanged(const DoubleVariable &value)
{ {
if (_loading || !_data) { GUARD_LOADING_AND_LOCK();
return; _entryData->_colorParameters.colorThreshold = value;
}
auto lock = LockContext();
_data->_colorParameters.colorThreshold = value;
} }
void ColorEdit::SelectColorClicked() void ColorEdit::SelectColorClicked()
{ {
if (_loading || !_data) { if (_loading || !_entryData) {
return; return;
} }
const QColor color = QColorDialog::getColor( const QColor color = QColorDialog::getColor(
_data->_colorParameters.color, this, _entryData->_colorParameters.color, this,
obs_module_text("AdvSceneSwitcher.condition.video.selectColor"), obs_module_text("AdvSceneSwitcher.condition.video.selectColor"),
QColorDialog::ColorDialogOption()); QColorDialog::ColorDialogOption());
@@ -999,19 +1068,19 @@ void ColorEdit::SelectColorClicked()
SetupColorLabel(color); SetupColorLabel(color);
auto lock = LockContext(); auto lock = LockContext();
_data->_colorParameters.color = color; _entryData->_colorParameters.color = color;
} }
AreaEdit::AreaEdit(QWidget *parent, PreviewDialog *previewDialog, AreaEdit::AreaEdit(QWidget *parent, PreviewDialog *previewDialog,
const std::shared_ptr<MacroConditionVideo> &data) const std::shared_ptr<MacroConditionVideo> &data)
: QWidget(parent), : QWidget(parent),
_checkAreaEnable(new QCheckBox(obs_module_text( _checkAreaEnable(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.entry.checkAreaEnable"))), "AdvSceneSwitcher.condition.video.layout.checkAreaEnable"))),
_checkArea(new AreaSelection(0, 99999)), _checkArea(new AreaSelection(0, 99999)),
_selectArea(new QPushButton(obs_module_text( _selectArea(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.video.selectArea"))), "AdvSceneSwitcher.condition.video.selectArea"))),
_previewDialog(previewDialog), _previewDialog(previewDialog),
_data(data) _entryData(data)
{ {
QWidget::connect(_checkAreaEnable, SIGNAL(stateChanged(int)), this, QWidget::connect(_checkAreaEnable, SIGNAL(stateChanged(int)), this,
SLOT(CheckAreaEnableChanged(int))); SLOT(CheckAreaEnableChanged(int)));
@@ -1032,20 +1101,20 @@ AreaEdit::AreaEdit(QWidget *parent, PreviewDialog *previewDialog,
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets( PlaceWidgets(
obs_module_text( obs_module_text(
"AdvSceneSwitcher.condition.video.entry.checkArea"), "AdvSceneSwitcher.condition.video.layout.checkArea"),
layout, widgetPlaceholders); layout, widgetPlaceholders);
setLayout(layout); setLayout(layout);
_checkAreaEnable->setChecked(_data->_areaParameters.enable); _checkAreaEnable->setChecked(_entryData->_areaParameters.enable);
_checkArea->SetArea(_data->_areaParameters.area); _checkArea->SetArea(_entryData->_areaParameters.area);
SetWidgetVisibility(); SetWidgetVisibility();
_loading = false; _loading = false;
} }
void AreaEdit::SetWidgetVisibility() void AreaEdit::SetWidgetVisibility()
{ {
_checkArea->setVisible(_data->_areaParameters.enable); _checkArea->setVisible(_entryData->_areaParameters.enable);
_selectArea->setVisible(_data->_areaParameters.enable); _selectArea->setVisible(_entryData->_areaParameters.enable);
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();
} }
@@ -1060,26 +1129,18 @@ void AreaEdit::SelectAreaClicked()
void AreaEdit::CheckAreaEnableChanged(int value) void AreaEdit::CheckAreaEnableChanged(int value)
{ {
if (_loading || !_data) { GUARD_LOADING_AND_LOCK();
return; _entryData->_areaParameters.enable = value;
}
auto lock = LockContext();
_data->_areaParameters.enable = value;
SetWidgetVisibility(); SetWidgetVisibility();
_previewDialog->AreaParametersChanged(_data->_areaParameters); _previewDialog->AreaParametersChanged(_entryData->_areaParameters);
emit Resized(); emit Resized();
} }
void AreaEdit::CheckAreaChanged(Area value) void AreaEdit::CheckAreaChanged(Area value)
{ {
if (_loading || !_data) { GUARD_LOADING_AND_LOCK();
return; _entryData->_areaParameters.area = value;
} _previewDialog->AreaParametersChanged(_entryData->_areaParameters);
auto lock = LockContext();
_data->_areaParameters.area = value;
_previewDialog->AreaParametersChanged(_data->_areaParameters);
} }
void AreaEdit::CheckAreaChanged(QRect rect) void AreaEdit::CheckAreaChanged(QRect rect)
@@ -1090,13 +1151,20 @@ void AreaEdit::CheckAreaChanged(QRect rect)
CheckAreaChanged(area); CheckAreaChanged(area);
} }
static QStringList getVideoSourcesList()
{
auto sources = GetVideoSourceNames();
sources.sort();
return sources;
}
MacroConditionVideoEdit::MacroConditionVideoEdit( MacroConditionVideoEdit::MacroConditionVideoEdit(
QWidget *parent, std::shared_ptr<MacroConditionVideo> entryData) QWidget *parent, std::shared_ptr<MacroConditionVideo> entryData)
: QWidget(parent), : QWidget(parent),
_videoInputTypes(new QComboBox()), _videoInputTypes(new QComboBox()),
_scenes(new SceneSelectionWidget(this, true, false, true, true, _scenes(new SceneSelectionWidget(this, true, false, true, true,
true)), true)),
_sources(new SourceSelectionWidget(this, QStringList(), true)), _sources(new SourceSelectionWidget(this, getVideoSourcesList, true)),
_condition(new QComboBox()), _condition(new QComboBox()),
_reduceLatency(new QCheckBox(obs_module_text( _reduceLatency(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.reduceLatency"))), "AdvSceneSwitcher.condition.video.reduceLatency"))),
@@ -1149,10 +1217,6 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
_area->setSizePolicy(QSizePolicy::MinimumExpanding, _area->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred); QSizePolicy::Preferred);
auto sources = GetVideoSourceNames();
sources.sort();
_sources->SetSourceNameList(sources);
QWidget::connect(_videoInputTypes, SIGNAL(currentIndexChanged(int)), QWidget::connect(_videoInputTypes, SIGNAL(currentIndexChanged(int)),
this, SLOT(VideoInputTypeChanged(int))); this, SLOT(VideoInputTypeChanged(int)));
QWidget::connect(_sources, QWidget::connect(_sources,
@@ -1217,15 +1281,16 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
{"{{throttleCount}}", _throttleCount}, {"{{throttleCount}}", _throttleCount},
{"{{patternMatchingModes}}", _patternMatchMode}, {"{{patternMatchingModes}}", _patternMatchMode},
}; };
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.video.entry"), PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.video.layout"),
entryLine1Layout, widgetPlaceholders); entryLine1Layout, widgetPlaceholders);
PlaceWidgets( PlaceWidgets(
obs_module_text( obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchMode"), "AdvSceneSwitcher.condition.video.patternMatchMode"),
_patternMatchModeLayout, widgetPlaceholders); _patternMatchModeLayout, widgetPlaceholders);
PlaceWidgets(obs_module_text( PlaceWidgets(
"AdvSceneSwitcher.condition.video.entry.throttle"), obs_module_text(
_throttleControlLayout, widgetPlaceholders); "AdvSceneSwitcher.condition.video.layout.throttle"),
_throttleControlLayout, widgetPlaceholders);
QHBoxLayout *showMatchLayout = new QHBoxLayout; QHBoxLayout *showMatchLayout = new QHBoxLayout;
showMatchLayout->addWidget(_showMatch); showMatchLayout->addWidget(_showMatch);
@@ -1279,33 +1344,21 @@ void MacroConditionVideoEdit::UpdatePreviewTooltip()
void MacroConditionVideoEdit::SourceChanged(const SourceSelection &source) void MacroConditionVideoEdit::SourceChanged(const SourceSelection &source)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_video.source = source; _entryData->_video.source = source;
HandleVideoInputUpdate(); HandleVideoInputUpdate();
} }
void MacroConditionVideoEdit::SceneChanged(const SceneSelection &scene) void MacroConditionVideoEdit::SceneChanged(const SceneSelection &scene)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_video.scene = scene; _entryData->_video.scene = scene;
HandleVideoInputUpdate(); HandleVideoInputUpdate();
} }
void MacroConditionVideoEdit::VideoInputTypeChanged(int type) void MacroConditionVideoEdit::VideoInputTypeChanged(int type)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_video.type = static_cast<VideoInput::Type>(type); _entryData->_video.type = static_cast<VideoInput::Type>(type);
HandleVideoInputUpdate(); HandleVideoInputUpdate();
SetWidgetVisibility(); SetWidgetVisibility();
@@ -1321,11 +1374,7 @@ void MacroConditionVideoEdit::HandleVideoInputUpdate()
void MacroConditionVideoEdit::ConditionChanged(int idx) void MacroConditionVideoEdit::ConditionChanged(int idx)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->SetCondition( _entryData->SetCondition(
static_cast<VideoCondition>(_condition->itemData(idx).toInt())); static_cast<VideoCondition>(_condition->itemData(idx).toInt()));
_entryData->ResetLastMatch(); _entryData->ResetLastMatch();
@@ -1342,22 +1391,12 @@ void MacroConditionVideoEdit::ConditionChanged(int idx)
_previewDialog.PatternMatchParametersChanged( _previewDialog.PatternMatchParametersChanged(
_entryData->_patternMatchParameters); _entryData->_patternMatchParameters);
if (_entryData->GetCondition() == VideoCondition::OBJECT) {
auto path = _entryData->GetModelDataPath();
_entryData->_objMatchParameters.cascade =
initObjectCascade(path);
}
SetupPreviewDialogParams(); SetupPreviewDialogParams();
} }
void MacroConditionVideoEdit::ImagePathChanged(const QString &text) void MacroConditionVideoEdit::ImagePathChanged(const QString &text)
{ {
if (_loading || !_entryData) { GUARD_LOADING_AND_LOCK();
return;
}
auto lock = LockContext();
_entryData->_file = text.toUtf8().constData(); _entryData->_file = text.toUtf8().constData();
_entryData->ResetLastMatch(); _entryData->ResetLastMatch();
if (_entryData->LoadImageFromFile()) { if (_entryData->LoadImageFromFile()) {
@@ -1560,6 +1599,17 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
_patternMatchModeLayout, _patternMatchModeLayout,
_entryData->_patternMatchParameters.useForChangedCheck); _entryData->_patternMatchParameters.useForChangedCheck);
} }
// TODO:
// Remove "reduce matching latency" and "reduce cpu load" options as they
// have become superfluous with short circuit evaluation
if (!_entryData->_throttleEnabled) {
SetLayoutVisible(_throttleControlLayout, false);
}
if (_entryData->_blockUntilScreenshotDone) {
_reduceLatency->hide();
}
Resize(); Resize();
} }

View File

@@ -42,12 +42,11 @@ public:
QImage GetMatchImage() const { return _matchImage; }; QImage GetMatchImage() const { return _matchImage; };
void GetScreenshot(bool blocking = false); void GetScreenshot(bool blocking = false);
bool LoadImageFromFile(); bool LoadImageFromFile();
bool LoadModelData(std::string &path);
std::string GetModelDataPath() const;
void ResetLastMatch() { _lastMatchResult = false; } void ResetLastMatch() { _lastMatchResult = false; }
double GetCurrentBrightness() const { return _currentBrightness; } double GetCurrentBrightness() const { return _currentBrightness; }
void SetPageSegMode(tesseract::PageSegMode); void SetPageSegMode(tesseract::PageSegMode);
bool SetLanguage(const std::string &); bool SetLanguageCode(const std::string &);
bool SetTesseractBaseDir(const std::string &);
void SetCondition(VideoCondition); void SetCondition(VideoCondition);
VideoCondition GetCondition() const { return _condition; } VideoCondition GetCondition() const { return _condition; }
@@ -60,13 +59,18 @@ public:
// If not set the screenshot will be gathered in one interval and // If not set the screenshot will be gathered in one interval and
// checked in the next one. // checked in the next one.
// If set both operations will happen in the same interval. // If set both operations will happen in the same interval.
bool _blockUntilScreenshotDone = false; //
// TODO: Remove this option in a future release as it has become
// superfluous with "short circuit" evaluation.
bool _blockUntilScreenshotDone = true;
NumberVariable<double> _brightnessThreshold = 0.5; NumberVariable<double> _brightnessThreshold = 0.5;
PatternMatchParameters _patternMatchParameters; PatternMatchParameters _patternMatchParameters;
ObjDetectParameters _objMatchParameters; ObjDetectParameters _objMatchParameters;
OCRParameters _ocrParameters; OCRParameters _ocrParameters;
ColorParameters _colorParameters; ColorParameters _colorParameters;
AreaParameters _areaParameters; AreaParameters _areaParameters;
// TODO: Remove this option in a future release as it has become
// superfluous with "short circuit" evaluation.
bool _throttleEnabled = false; bool _throttleEnabled = false;
int _throttleCount = 3; int _throttleCount = 3;
@@ -122,7 +126,7 @@ private:
QLabel *_current; QLabel *_current;
QTimer _timer; QTimer _timer;
std::shared_ptr<MacroConditionVideo> _data; std::shared_ptr<MacroConditionVideo> _entryData;
bool _loading = true; bool _loading = true;
}; };
@@ -139,7 +143,10 @@ private slots:
void MatchTextChanged(); void MatchTextChanged();
void RegexChanged(const RegexConfig &conf); void RegexChanged(const RegexConfig &conf);
void PageSegModeChanged(int); void PageSegModeChanged(int);
void TesseractBaseDirChanged(const QString &);
void LanguageChanged(); void LanguageChanged();
void UseConfigChanged(int);
void ConfigFileChanged(const QString &);
private: private:
void SetupColorLabel(const QColor &); void SetupColorLabel(const QColor &);
@@ -150,11 +157,17 @@ private:
QPushButton *_selectColor; QPushButton *_selectColor;
SliderSpinBox *_colorThreshold; SliderSpinBox *_colorThreshold;
QComboBox *_pageSegMode; QComboBox *_pageSegMode;
FileSelection *_tesseractBaseDir;
VariableLineEdit *_languageCode; VariableLineEdit *_languageCode;
QCheckBox *_useConfig;
FileSelection *_configFile;
QPushButton *_openConfigFile;
QPushButton *_reloadConfig;
QHBoxLayout *_configLayout;
PreviewDialog *_previewDialog; PreviewDialog *_previewDialog;
std::shared_ptr<MacroConditionVideo> _data; std::shared_ptr<MacroConditionVideo> _entryData;
bool _loading = true; bool _loading = true;
}; };
@@ -182,7 +195,7 @@ private:
PreviewDialog *_previewDialog; PreviewDialog *_previewDialog;
std::shared_ptr<MacroConditionVideo> _data; std::shared_ptr<MacroConditionVideo> _entryData;
bool _loading = true; bool _loading = true;
}; };
@@ -206,7 +219,7 @@ private:
QLabel *_color; QLabel *_color;
QPushButton *_selectColor; QPushButton *_selectColor;
std::shared_ptr<MacroConditionVideo> _data; std::shared_ptr<MacroConditionVideo> _entryData;
bool _loading = true; bool _loading = true;
}; };
@@ -235,7 +248,7 @@ private:
PreviewDialog *_previewDialog; PreviewDialog *_previewDialog;
std::shared_ptr<MacroConditionVideo> _data; std::shared_ptr<MacroConditionVideo> _entryData;
bool _loading = true; bool _loading = true;
}; };

View File

@@ -202,14 +202,15 @@ cv::Mat PreprocessForOCR(const QImage &image, const QColor &textColor,
return result; return result;
} }
std::string RunOCR(tesseract::TessBaseAPI *ocr, const QImage &image, std::optional<std::string> RunOCR(tesseract::TessBaseAPI *ocr,
const QColor &color, double colorDiff) const QImage &image, const QColor &color,
double colorDiff)
{ {
(void)ocr; (void)ocr;
(void)color; (void)color;
(void)colorDiff; (void)colorDiff;
if (image.isNull()) { if (image.isNull()) {
return ""; return {};
} }
#ifdef OCR_SUPPORT #ifdef OCR_SUPPORT
@@ -221,12 +222,12 @@ std::string RunOCR(tesseract::TessBaseAPI *ocr, const QImage &image,
std::unique_ptr<char[]> detectedText(ocr->GetUTF8Text()); std::unique_ptr<char[]> detectedText(ocr->GetUTF8Text());
if (!detectedText) { if (!detectedText) {
return ""; return {};
} }
return detectedText.get(); return detectedText.get();
#else #else
return ""; return {};
#endif #endif
} }

View File

@@ -27,10 +27,23 @@ enum PageSegMode {
PSM_COUNT PSM_COUNT
}; };
enum OcrEngineMode {
OEM_TESSERACT_ONLY,
OEM_LSTM_ONLY,
OEM_TESSERACT_LSTM_COMBINED,
OEM_DEFAULT,
OEM_COUNT // Number of OEMs
};
class TessBaseAPI { class TessBaseAPI {
public: public:
void SetPageSegMode(PageSegMode) {} void SetPageSegMode(PageSegMode) {}
int Init(const char *, const char *) { return 0; } int Init(const char *, const char *, OcrEngineMode, char **, int,
const std::vector<std::string> *,
const std::vector<std::string> *, bool)
{
return 0;
}
void End() {} void End() {}
}; };
} // namespace tesseract } // namespace tesseract
@@ -62,8 +75,8 @@ std::vector<cv::Rect> MatchObject(QImage &img, cv::CascadeClassifier &cascade,
uchar GetAvgBrightness(QImage &img); uchar GetAvgBrightness(QImage &img);
cv::Mat PreprocessForOCR(const QImage &image, const QColor &color, cv::Mat PreprocessForOCR(const QImage &image, const QColor &color,
double colorDiff); double colorDiff);
std::string RunOCR(tesseract::TessBaseAPI *, const QImage &, const QColor &, std::optional<std::string> RunOCR(tesseract::TessBaseAPI *, const QImage &,
double colorDiff); const QColor &, double colorDiff);
bool ContainsPixelsInColorRange(const QImage &image, const QColor &color, bool ContainsPixelsInColorRange(const QImage &image, const QColor &color,
double colorDeviationThreshold, double colorDeviationThreshold,
double totalPixelMatchThreshold); double totalPixelMatchThreshold);

View File

@@ -1,4 +1,5 @@
#include "parameter-wrappers.hpp" #include "parameter-wrappers.hpp"
#include "log-helper.hpp"
#include <QFileInfo> #include <QFileInfo>
#include <source-helpers.hpp> #include <source-helpers.hpp>
@@ -47,6 +48,31 @@ bool PatternMatchParameters::Load(obs_data_t *obj)
return true; return true;
} }
static std::shared_ptr<cv::CascadeClassifier>
initObjectCascade(std::string &path)
{
auto cascade = std::make_shared<cv::CascadeClassifier>();
try {
cascade->load(path);
} catch (...) {
blog(LOG_WARNING, "failed to load model data \"%s\"",
path.c_str());
}
return cascade;
}
bool ObjDetectParameters::LoadModelData()
{
const auto path = QString::fromStdString(modelPath);
if (!QFileInfo(path).exists(path)) {
cascade.reset();
return false;
}
cascade = initObjectCascade(modelPath);
return !cascade->empty();
}
bool ObjDetectParameters::Save(obs_data_t *obj) const bool ObjDetectParameters::Save(obs_data_t *obj) const
{ {
auto data = obs_data_create(); auto data = obs_data_create();
@@ -106,9 +132,29 @@ bool ObjDetectParameters::Load(obs_data_t *obj)
minSize.Load(data, "minSize"); minSize.Load(data, "minSize");
maxSize.Load(data, "maxSize"); maxSize.Load(data, "maxSize");
obs_data_release(data); obs_data_release(data);
return true; return true;
} }
bool ObjDetectParameters::SetModelPath(const std::string &path)
{
modelPath = path;
return LoadModelData();
}
std::shared_ptr<cv::CascadeClassifier> ObjDetectParameters::GetModel()
{
if (cascade && !cascade->empty()) {
return cascade;
}
if (!LoadModelData()) {
return {};
}
return cascade;
}
bool AreaParameters::Save(obs_data_t *obj) const bool AreaParameters::Save(obs_data_t *obj) const
{ {
auto data = obs_data_create(); auto data = obs_data_create();
@@ -252,14 +298,14 @@ OCRParameters::OCRParameters(const OCRParameters &other)
color(other.color), color(other.color),
colorThreshold(other.colorThreshold), colorThreshold(other.colorThreshold),
pageSegMode(other.pageSegMode), pageSegMode(other.pageSegMode),
languageCode(other.languageCode) tesseractBasePath(other.tesseractBasePath),
languageCode(other.languageCode),
useConfig(other.useConfig),
configFile(other.configFile)
{ {
if (!initDone) { if (!initDone) {
Setup(); Setup();
} }
if (initDone) {
ocr->SetPageSegMode(pageSegMode);
}
} }
OCRParameters &OCRParameters::operator=(const OCRParameters &other) OCRParameters &OCRParameters::operator=(const OCRParameters &other)
@@ -269,13 +315,13 @@ OCRParameters &OCRParameters::operator=(const OCRParameters &other)
color = other.color; color = other.color;
colorThreshold = other.colorThreshold; colorThreshold = other.colorThreshold;
pageSegMode = other.pageSegMode; pageSegMode = other.pageSegMode;
tesseractBasePath = other.tesseractBasePath;
languageCode = other.languageCode; languageCode = other.languageCode;
useConfig = other.useConfig;
configFile = other.configFile;
if (!initDone) { if (!initDone) {
Setup(); Setup();
} }
if (initDone) {
ocr->SetPageSegMode(pageSegMode);
}
return *this; return *this;
} }
@@ -284,11 +330,14 @@ bool OCRParameters::Save(obs_data_t *obj) const
auto data = obs_data_create(); auto data = obs_data_create();
text.Save(data, "pattern"); text.Save(data, "pattern");
regex.Save(data); regex.Save(data);
tesseractBasePath.Save(data, "tesseractBasePath");
languageCode.Save(data, "language"); languageCode.Save(data, "language");
obs_data_set_bool(data, "useConfig", useConfig);
obs_data_set_string(data, "configFile", configFile.c_str());
SaveColor(data, "textColor", color); SaveColor(data, "textColor", color);
colorThreshold.Save(data, "colorThreshold"); colorThreshold.Save(data, "colorThreshold");
obs_data_set_int(data, "pageSegMode", static_cast<int>(pageSegMode)); obs_data_set_int(data, "pageSegMode", static_cast<int>(pageSegMode));
obs_data_set_int(data, "version", 1); obs_data_set_int(data, "version", 2);
obs_data_set_obj(obj, "ocrData", data); obs_data_set_obj(obj, "ocrData", data);
obs_data_release(data); obs_data_release(data);
return true; return true;
@@ -301,6 +350,16 @@ bool OCRParameters::Load(obs_data_t *obj)
regex.Load(data); regex.Load(data);
obs_data_set_default_string(data, "language", "eng"); obs_data_set_default_string(data, "language", "eng");
languageCode.Load(data, "language"); languageCode.Load(data, "language");
tesseractBasePath.Load(data, "tesseractBasePath");
if (!obs_data_has_user_value(data, "version") ||
obs_data_get_int(data, "version") < 2) {
tesseractBasePath = std::string(obs_get_module_data_path(
obs_current_module())) +
"/res/ocr/";
}
useConfig = obs_data_get_bool(data, "useConfig");
configFile = obs_data_get_string(data, "configFile");
color = LoadColor(data, "textColor"); color = LoadColor(data, "textColor");
if (obs_data_has_user_value(data, "version")) { if (obs_data_has_user_value(data, "version")) {
colorThreshold.Load(data, "colorThreshold"); colorThreshold.Load(data, "colorThreshold");
@@ -309,9 +368,6 @@ bool OCRParameters::Load(obs_data_t *obj)
obs_data_get_int(data, "pageSegMode")); obs_data_get_int(data, "pageSegMode"));
obs_data_release(data); obs_data_release(data);
if (initDone) {
ocr->SetPageSegMode(pageSegMode);
}
Setup(); Setup();
return true; return true;
} }
@@ -319,22 +375,21 @@ bool OCRParameters::Load(obs_data_t *obj)
void OCRParameters::SetPageMode(tesseract::PageSegMode mode) void OCRParameters::SetPageMode(tesseract::PageSegMode mode)
{ {
pageSegMode = mode; pageSegMode = mode;
ocr->SetPageSegMode(mode); if (ocr) {
ocr->SetPageSegMode(mode);
}
} }
bool OCRParameters::SetLanguageCode(const std::string &value) bool OCRParameters::SetLanguageCode(const std::string &value)
{ {
const auto dataPath = const auto dataPath = QString::fromStdString(tesseractBasePath) + "/" +
QString(obs_get_module_data_path(obs_current_module())) + QString::fromStdString(value) + ".traineddata";
QString("/res/ocr") + "/" + QString::fromStdString(value) +
".traineddata";
QFileInfo fileInfo(dataPath); QFileInfo fileInfo(dataPath);
if (!fileInfo.exists(dataPath)) { if (!fileInfo.exists(dataPath)) {
return false; return false;
} }
languageCode = value; languageCode = value;
Setup(); Setup();
ocr->SetPageSegMode(pageSegMode);
return true; return true;
} }
@@ -343,6 +398,37 @@ std::string OCRParameters::GetLanguageCode() const
return languageCode; return languageCode;
} }
bool OCRParameters::SetTesseractBasePath(const std::string &value)
{
const auto dataPath = QString::fromStdString(value) + "/" +
QString::fromStdString(languageCode) +
".traineddata";
QFileInfo fileInfo(dataPath);
if (!fileInfo.exists(dataPath)) {
return false;
}
tesseractBasePath = value;
Setup();
return true;
}
std::string OCRParameters::GetTesseractBasePath() const
{
return tesseractBasePath;
}
void OCRParameters::EnableCustomConfig(bool enable)
{
useConfig = enable;
Setup();
}
void OCRParameters::SetCustomConfigFile(const std::string &filename)
{
configFile = filename;
Setup();
}
void OCRParameters::Setup() void OCRParameters::Setup()
{ {
ocr = std::make_unique<tesseract::TessBaseAPI>(); ocr = std::make_unique<tesseract::TessBaseAPI>();
@@ -350,12 +436,45 @@ void OCRParameters::Setup()
initDone = false; initDone = false;
return; return;
} }
std::string dataPath = obs_get_module_data_path(obs_current_module()) +
std::string("/res/ocr"); const std::string dataPath = std::string(tesseractBasePath) + "/";
if (ocr->Init(dataPath.c_str(), languageCode.c_str()) != 0) { const std::string modelFile =
std::string(languageCode) + ".traineddata";
const auto modelFullPath = QString::fromStdString(dataPath) +
QString::fromStdString(modelFile);
QFileInfo modelFileInfo(modelFullPath);
if (!modelFileInfo.exists(modelFullPath)) {
initDone = false;
blog(LOG_WARNING,
"cannot init tesseract! Model path does not exists: %s",
modelFileInfo.absoluteFilePath().toStdString().c_str());
return;
}
auto configPath = QString::fromStdString(configFile);
QFileInfo configFileInfo(configPath);
bool configFileExists = configFileInfo.exists(configPath);
bool setupWithConfig = useConfig;
if (useConfig && !configFileExists) {
blog(LOG_WARNING,
"tesseract config file will be ignored! File does not exists: %s",
configFileInfo.absoluteFilePath().toStdString().c_str());
setupWithConfig = false;
}
char *configs[] = {configFile.data()};
if (ocr->Init(dataPath.c_str(), languageCode.c_str(),
tesseract::OEM_DEFAULT,
setupWithConfig ? configs : nullptr,
setupWithConfig ? 1 : 0, nullptr, nullptr, false) != 0) {
blog(LOG_WARNING, "tesseract init failed!");
initDone = false; initDone = false;
return; return;
} }
ocr->SetPageSegMode(pageSegMode);
initDone = true; initDone = true;
} }

View File

@@ -68,15 +68,23 @@ public:
bool Save(obs_data_t *obj) const; bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj); bool Load(obs_data_t *obj);
std::string modelPath = bool SetModelPath(const std::string &path);
obs_get_module_data_path(obs_current_module()) + const std::string &GetModelPath() const { return modelPath; }
std::string( std::shared_ptr<cv::CascadeClassifier> GetModel();
"/res/cascadeClassifiers/haarcascade_frontalface_alt.xml");
cv::CascadeClassifier cascade;
NumberVariable<double> scaleFactor = defaultScaleFactor; NumberVariable<double> scaleFactor = defaultScaleFactor;
int minNeighbors = minMinNeighbors; int minNeighbors = minMinNeighbors;
Size minSize{0, 0}; Size minSize{0, 0};
Size maxSize{0, 0}; Size maxSize{0, 0};
private:
bool LoadModelData();
std::shared_ptr<cv::CascadeClassifier> cascade;
std::string modelPath =
obs_get_module_data_path(obs_current_module()) +
std::string(
"/res/cascadeClassifiers/haarcascade_frontalface_alt.xml");
}; };
class OCRParameters { class OCRParameters {
@@ -93,6 +101,12 @@ public:
void SetPageMode(tesseract::PageSegMode); void SetPageMode(tesseract::PageSegMode);
bool SetLanguageCode(const std::string &); bool SetLanguageCode(const std::string &);
std::string GetLanguageCode() const; std::string GetLanguageCode() const;
bool SetTesseractBasePath(const std::string &);
std::string GetTesseractBasePath() const;
void EnableCustomConfig(bool enable);
bool CustomConfigIsEnabled() const { return useConfig; }
void SetCustomConfigFile(const std::string &);
std::string GetCustomConfigFile() const { return configFile; }
tesseract::PageSegMode GetPageMode() const { return pageSegMode; } tesseract::PageSegMode GetPageMode() const { return pageSegMode; }
tesseract::TessBaseAPI *GetOCR() const { return ocr.get(); } tesseract::TessBaseAPI *GetOCR() const { return ocr.get(); }
@@ -100,12 +114,17 @@ public:
RegexConfig regex = RegexConfig::PartialMatchRegexConfig(); RegexConfig regex = RegexConfig::PartialMatchRegexConfig();
QColor color = Qt::black; QColor color = Qt::black;
DoubleVariable colorThreshold = 0.3; DoubleVariable colorThreshold = 0.3;
StringVariable languageCode = "eng";
private: private:
void Setup(); void Setup();
tesseract::PageSegMode pageSegMode = tesseract::PSM_SINGLE_BLOCK; tesseract::PageSegMode pageSegMode = tesseract::PSM_SINGLE_BLOCK;
StringVariable tesseractBasePath =
obs_get_module_data_path(obs_current_module()) +
std::string("/res/ocr");
StringVariable languageCode = "eng";
bool useConfig = false;
std::string configFile = "config.txt";
std::unique_ptr<tesseract::TessBaseAPI> ocr; std::unique_ptr<tesseract::TessBaseAPI> ocr;
bool initDone = false; bool initDone = false;
}; };

View File

@@ -134,13 +134,13 @@ void PreviewDialog::PatternMatchParametersChanged(
void PreviewDialog::ObjDetectParametersChanged(const ObjDetectParameters &params) void PreviewDialog::ObjDetectParametersChanged(const ObjDetectParameters &params)
{ {
std::unique_lock<std::mutex> lock(_mtx); std::unique_lock<std::mutex> lock(_mtx);
_objDetectParams = params; _objDetectParams = std::make_shared<ObjDetectParameters>(params);
} }
void PreviewDialog::OCRParametersChanged(const OCRParameters &params) void PreviewDialog::OCRParametersChanged(const OCRParameters &params)
{ {
std::unique_lock<std::mutex> lock(_mtx); std::unique_lock<std::mutex> lock(_mtx);
_ocrParams = params; _ocrParams = std::make_shared<OCRParameters>(params);
} }
void PreviewDialog::VideoSelectionChanged(const VideoInput &video) void PreviewDialog::VideoSelectionChanged(const VideoInput &video)
@@ -262,13 +262,13 @@ static void markObjects(QImage &image, std::vector<cv::Rect> &objects)
PreviewImage::PreviewImage(std::mutex &mtx) : _mtx(mtx) {} PreviewImage::PreviewImage(std::mutex &mtx) : _mtx(mtx) {}
void PreviewImage::CreateImage(const VideoInput &video, PreviewType type, void PreviewImage::CreateImage(
const PatternMatchParameters &patternMatchParams, const VideoInput &video, PreviewType type,
const PatternImageData &patternImageData, const PatternMatchParameters &patternMatchParams,
ObjDetectParameters objDetectParams, const PatternImageData &patternImageData,
OCRParameters ocrParams, std::shared_ptr<ObjDetectParameters> objDetectParams,
const AreaParameters &areaParams, std::shared_ptr<OCRParameters> ocrParams,
VideoCondition condition) const AreaParameters &areaParams, VideoCondition condition)
{ {
auto source = obs_weak_source_get_source(video.GetVideo()); auto source = obs_weak_source_get_source(video.GetVideo());
QRect screenshotArea; QRect screenshotArea;
@@ -308,55 +308,94 @@ void PreviewImage::CreateImage(const VideoInput &video, PreviewType type,
emit ImageReady(QPixmap::fromImage(screenshot.GetImage())); emit ImageReady(QPixmap::fromImage(screenshot.GetImage()));
} }
void PreviewImage::MarkMatch(QImage &screenshot, void PreviewImage::MarkMatch(
const PatternMatchParameters &patternMatchParams, QImage &screenshot, const PatternMatchParameters &patternMatchParams,
const PatternImageData &patternImageData, const PatternImageData &patternImageData,
ObjDetectParameters &objDetectParams, std::shared_ptr<ObjDetectParameters> objDetectParams,
const OCRParameters &ocrParams, std::shared_ptr<OCRParameters> ocrParams, VideoCondition condition)
VideoCondition condition)
{ {
if (condition == VideoCondition::PATTERN) { if (condition == VideoCondition::PATTERN) {
cv::Mat result; MarkPatternMatch(screenshot, patternMatchParams,
double matchValue = patternImageData);
std::numeric_limits<double>::signaling_NaN();
MatchPattern(screenshot, patternImageData,
patternMatchParams.threshold, result, &matchValue,
patternMatchParams.useAlphaAsMask,
patternMatchParams.matchMode);
emit ValueUpdate(matchValue);
if (result.total() == 0 || countNonZero(result) == 0) {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchFail"));
} else if (result.cols == 1 && result.rows == 1) {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchSuccessFullImage"));
} else {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchSuccess"));
markPatterns(result, screenshot,
patternImageData.rgbaPattern);
}
} else if (condition == VideoCondition::OBJECT) { } else if (condition == VideoCondition::OBJECT) {
auto objects = MatchObject(screenshot, objDetectParams.cascade, MarkObjectMatch(screenshot, objDetectParams);
objDetectParams.scaleFactor,
objDetectParams.minNeighbors,
objDetectParams.minSize.CV(),
objDetectParams.maxSize.CV());
if (objects.empty()) {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.objectMatchFail"));
} else {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.objectMatchSuccess"));
markObjects(screenshot, objects);
}
} else if (condition == VideoCondition::OCR) { } else if (condition == VideoCondition::OCR) {
auto text = RunOCR(ocrParams.GetOCR(), screenshot, MarkOCRMatch(screenshot, ocrParams);
ocrParams.color, ocrParams.colorThreshold);
QString status(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrMatchSuccess"));
emit StatusUpdate(status.arg(QString::fromStdString(text)));
} }
} }
void PreviewImage::MarkPatternMatch(
QImage &screenshot, const PatternMatchParameters &patternMatchParams,
const PatternImageData &patternImageData)
{
cv::Mat result;
double matchValue = std::numeric_limits<double>::signaling_NaN();
MatchPattern(screenshot, patternImageData, patternMatchParams.threshold,
result, &matchValue, patternMatchParams.useAlphaAsMask,
patternMatchParams.matchMode);
emit ValueUpdate(matchValue);
if (result.total() == 0 || countNonZero(result) == 0) {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchFail"));
} else if (result.cols == 1 && result.rows == 1) {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchSuccessFullImage"));
} else {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchSuccess"));
markPatterns(result, screenshot, patternImageData.rgbaPattern);
}
}
void PreviewImage::MarkObjectMatch(
QImage &screenshot,
const std::shared_ptr<ObjDetectParameters> &objDetectParams)
{
if (!objDetectParams) {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.objectMatchFail"));
return;
}
auto model = objDetectParams->GetModel();
if (!model) {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.objectMatchFail"));
return;
}
auto objects = MatchObject(screenshot, *model,
objDetectParams->scaleFactor,
objDetectParams->minNeighbors,
objDetectParams->minSize.CV(),
objDetectParams->maxSize.CV());
if (objects.empty()) {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.objectMatchFail"));
} else {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.objectMatchSuccess"));
markObjects(screenshot, objects);
}
}
void PreviewImage::MarkOCRMatch(QImage &screenshot,
const std::shared_ptr<OCRParameters> &ocrParams)
{
if (!ocrParams) {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrMatchFail"));
return;
}
auto text = RunOCR(ocrParams->GetOCR(), screenshot, ocrParams->color,
ocrParams->colorThreshold);
if (!text) {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrMatchFail"));
}
QString status(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrMatchSuccess"));
emit StatusUpdate(status.arg(QString::fromStdString(*text)));
}
} // namespace advss } // namespace advss

View File

@@ -26,8 +26,10 @@ public:
public slots: public slots:
void CreateImage(const VideoInput &, PreviewType, void CreateImage(const VideoInput &, PreviewType,
const PatternMatchParameters &, const PatternMatchParameters &,
const PatternImageData &, ObjDetectParameters, const PatternImageData &,
OCRParameters, const AreaParameters &, VideoCondition); std::shared_ptr<ObjDetectParameters>,
std::shared_ptr<OCRParameters>, const AreaParameters &,
VideoCondition);
signals: signals:
void ImageReady(const QPixmap &); void ImageReady(const QPixmap &);
void ValueUpdate(double); void ValueUpdate(double);
@@ -35,8 +37,14 @@ signals:
private: private:
void MarkMatch(QImage &screenshot, const PatternMatchParameters &, void MarkMatch(QImage &screenshot, const PatternMatchParameters &,
const PatternImageData &, ObjDetectParameters &, const PatternImageData &,
const OCRParameters &, VideoCondition); std::shared_ptr<ObjDetectParameters>,
std::shared_ptr<OCRParameters>, VideoCondition);
void MarkPatternMatch(QImage &, const PatternMatchParameters &,
const PatternImageData &);
void MarkObjectMatch(QImage &,
const std::shared_ptr<ObjDetectParameters> &);
void MarkOCRMatch(QImage &, const std::shared_ptr<OCRParameters> &);
std::mutex &_mtx; std::mutex &_mtx;
}; };
@@ -67,8 +75,9 @@ signals:
void SelectionAreaChanged(QRect area); void SelectionAreaChanged(QRect area);
void NeedImage(const VideoInput &, PreviewType, void NeedImage(const VideoInput &, PreviewType,
const PatternMatchParameters &, const PatternImageData &, const PatternMatchParameters &, const PatternImageData &,
ObjDetectParameters, OCRParameters, std::shared_ptr<ObjDetectParameters>,
const AreaParameters &, VideoCondition); std::shared_ptr<OCRParameters>, const AreaParameters &,
VideoCondition);
private: private:
void Start(); void Start();
@@ -81,8 +90,8 @@ private:
VideoInput _video; VideoInput _video;
PatternMatchParameters _patternMatchParams; PatternMatchParameters _patternMatchParams;
PatternImageData _patternImageData; PatternImageData _patternImageData;
ObjDetectParameters _objDetectParams; std::shared_ptr<ObjDetectParameters> _objDetectParams;
OCRParameters _ocrParams; std::shared_ptr<OCRParameters> _ocrParams;
AreaParameters _areaParams; AreaParameters _areaParams;
VideoCondition _condition = VideoCondition::PATTERN; VideoCondition _condition = VideoCondition::PATTERN;