Compare commits

..

17 Commits

Author SHA1 Message Date
WarmUpTill
aa87911b71 Use cpp-httplib based HTTP action type
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
The goal is to remove the older, more limited version of the HTTP action
at some point in the future.
2025-03-30 14:06:19 +02:00
WarmUpTill
1b05019acc Add option to hide entries from action selection
Intended to be used to hide legacy version of actions types (e.g. HTTP)
2025-03-30 14:01:41 +02:00
WarmUpTill
78a5a2629d Hide the "remote" file check option
This option will be removed at some point in the future.
The http action should be used instead.
2025-03-30 14:01:41 +02:00
WarmUpTill
634270a978 Cleanup includes 2025-03-30 14:01:41 +02:00
WarmUpTill
78ba22e1e4 Hide "get settings" button when setting macro property value 2025-03-30 14:01:41 +02:00
WarmUpTill
aba5737a60 Update and clean up locale (qwe1154323937) 2025-03-30 14:01:41 +02:00
WarmUpTill
4315f7f621 Exclude unwanted files from sources archive 2025-03-30 14:01:41 +02:00
WarmUpTill
53c535962f Use tab key to switch to dialog controls and set default focus to input 2025-03-30 14:01:41 +02:00
WarmUpTill
1e718b78c7 Fall back to project version if git tag cannot be queried 2025-03-30 14:01:41 +02:00
WarmUpTill
b1d2156228 Update libremidi to v4.3.0 2025-03-30 14:01:41 +02:00
WarmUpTill
9c3c953c6b Ignore deprecation warnings for Qt 6.7 and above
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-03-22 18:09:51 +01:00
WarmUpTill
ae74f68db7 Add option to close projector windows 2025-03-22 18:09:51 +01:00
WarmUpTill
213f1bba36 Add "Any" entry of the reward selection only for Twitch condition 2025-03-22 18:09:51 +01:00
WarmUpTill
5a2cb0bd68 Add more scripting signals / procedures 2025-03-22 18:09:51 +01:00
WarmUpTill
23b461828b Add start start / stop callbacks 2025-03-22 18:09:51 +01:00
WarmUpTill
9944a1b03b Move interval reset handling 2025-03-22 18:09:51 +01:00
WarmUpTill
eaad4d1bbd Fix Twitch helper caches misbehaving
API calls with different sets of arguments could map to the same key
value pair, which resulted in unexpected behavior for functions using
those caches
2025-03-22 18:09:51 +01:00
50 changed files with 3235 additions and 595 deletions

View File

@@ -33,6 +33,11 @@ add_library(${LIB_NAME} SHARED)
include(cmake/common/get_git_revision_description.cmake) include(cmake/common/get_git_revision_description.cmake)
get_git_head_revision(GIT_REFSPEC GIT_SHA1) get_git_head_revision(GIT_REFSPEC GIT_SHA1)
git_describe(GIT_TAG) git_describe(GIT_TAG)
if(${GIT_TAG} STREQUAL "GIT-NOTFOUND")
set(GIT_TAG ${PROJECT_VERSION})
endif()
message(STATUS "${PROJECT_NAME} version: ${GIT_TAG}")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/version.cpp.in" configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/version.cpp.in"
"${CMAKE_CURRENT_BINARY_DIR}/lib/version.cpp" @ONLY) "${CMAKE_CURRENT_BINARY_DIR}/lib/version.cpp" @ONLY)
@@ -298,6 +303,13 @@ setup_obs_lib_dependency(${PROJECT_NAME})
find_package(Qt6 REQUIRED COMPONENTS Widgets Core) find_package(Qt6 REQUIRED COMPONENTS Widgets Core)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Widgets) target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Widgets)
target_link_libraries(${LIB_NAME} PRIVATE Qt6::Core Qt6::Widgets) target_link_libraries(${LIB_NAME} PRIVATE Qt6::Core Qt6::Widgets)
# Ignore QCheckBox::stateChanged deprecation warning until minimum supported Qt
# version is at least Qt 6.7, which introduces QCheckBox::checkStateChanged
if(Qt6_VERSION VERSION_GREATER "6.0.0")
target_compile_definitions(${LIB_NAME} PRIVATE QT_NO_DEPRECATED_WARNINGS)
endif()
target_compile_options( target_compile_options(
${PROJECT_NAME} ${PROJECT_NAME}
PRIVATE PRIVATE
@@ -431,6 +443,9 @@ else()
endif() endif()
target_include_directories(${LIB_NAME} PRIVATE "${PROC_INCLUDE_DIR}") target_include_directories(${LIB_NAME} PRIVATE "${PROC_INCLUDE_DIR}")
target_sources(${LIB_NAME} PRIVATE lib/linux/advanced-scene-switcher-nix.cpp) target_sources(${LIB_NAME} PRIVATE lib/linux/advanced-scene-switcher-nix.cpp)
# Don't include irrelevant folders into sources archive
list(APPEND CPACK_SOURCE_IGNORE_FILES "\\.deps/.*")
endif() endif()
if(NOT OS_WINDOWS) if(NOT OS_WINDOWS)

View File

@@ -286,6 +286,12 @@ function(setup_advss_plugin target)
find_package(Qt6 REQUIRED COMPONENTS Widgets Core) find_package(Qt6 REQUIRED COMPONENTS Widgets Core)
target_link_libraries(${target} PRIVATE Qt6::Core Qt6::Widgets) target_link_libraries(${target} PRIVATE Qt6::Core Qt6::Widgets)
# Ignore QCheckBox::stateChanged deprecation warning until minimum supported
# Qt version is at least Qt 6.7, which introduces QCheckBox::checkStateChanged
if(Qt6_VERSION VERSION_GREATER "6.7.0")
target_compile_definitions(${target} PRIVATE QT_NO_DEPRECATED_WARNINGS)
endif()
set_target_properties( set_target_properties(
${target} ${target}
PROPERTIES AUTOMOC ON PROPERTIES AUTOMOC ON

View File

@@ -526,7 +526,6 @@ AdvSceneSwitcher.action.screenshot.save.default="Standard"
AdvSceneSwitcher.action.screenshot.save.custom="Benutzerdefiniert" AdvSceneSwitcher.action.screenshot.save.custom="Benutzerdefiniert"
AdvSceneSwitcher.action.screenshot.type.source="Quelle" AdvSceneSwitcher.action.screenshot.type.source="Quelle"
AdvSceneSwitcher.action.screenshot.type.scene="Szene" AdvSceneSwitcher.action.screenshot.type.scene="Szene"
AdvSceneSwitcher.action.screenshot.mainOutput="OBS's Haupt-Ausgabe"
AdvSceneSwitcher.action.screenshot.blackscreenNote="Quellen oder Szenen, die nicht immer gerendert werden, können dazu führen, dass einige Teile der Screenshots leer bleiben." AdvSceneSwitcher.action.screenshot.blackscreenNote="Quellen oder Szenen, die nicht immer gerendert werden, können dazu führen, dass einige Teile der Screenshots leer bleiben."
AdvSceneSwitcher.action.screenshot.entry="Screenshot{{targetType}}{{sources}}{{scenes}}und speichere in{{saveType}}{{variables}}Pfad" AdvSceneSwitcher.action.screenshot.entry="Screenshot{{targetType}}{{sources}}{{scenes}}und speichere in{{saveType}}{{variables}}Pfad"
AdvSceneSwitcher.action.profile="Profil" AdvSceneSwitcher.action.profile="Profil"

View File

@@ -1022,7 +1022,6 @@ AdvSceneSwitcher.action.screenshot.save.custom="Custom file path"
AdvSceneSwitcher.action.screenshot.save.variable="Variable (base64 encoded PNG)" AdvSceneSwitcher.action.screenshot.save.variable="Variable (base64 encoded PNG)"
AdvSceneSwitcher.action.screenshot.type.source="Source" AdvSceneSwitcher.action.screenshot.type.source="Source"
AdvSceneSwitcher.action.screenshot.type.scene="Scene" AdvSceneSwitcher.action.screenshot.type.scene="Scene"
AdvSceneSwitcher.action.screenshot.mainOutput="OBS's main output"
AdvSceneSwitcher.action.screenshot.blackscreenNote="Sources or scenes, which are not always rendered, may result in some parts of screenshots to remain blank." AdvSceneSwitcher.action.screenshot.blackscreenNote="Sources or scenes, which are not always rendered, may result in some parts of screenshots to remain blank."
AdvSceneSwitcher.action.screenshot.entry="Screenshot{{targetType}}{{sources}}{{scenes}}and save to{{saveType}}{{variables}}" AdvSceneSwitcher.action.screenshot.entry="Screenshot{{targetType}}{{sources}}{{scenes}}and save to{{saveType}}{{variables}}"
AdvSceneSwitcher.action.profile="Profile" AdvSceneSwitcher.action.profile="Profile"
@@ -1051,11 +1050,25 @@ AdvSceneSwitcher.action.websocket.entry.sceneSwitcher.request="Send{{api}}of typ
AdvSceneSwitcher.action.websocket.entry.sceneSwitcher.event="Send{{api}}of type{{type}}to connected clients" AdvSceneSwitcher.action.websocket.entry.sceneSwitcher.event="Send{{api}}of type{{type}}to connected clients"
AdvSceneSwitcher.action.websocket.entry.generic="Send{{api}}via{{connection}}" AdvSceneSwitcher.action.websocket.entry.generic="Send{{api}}via{{connection}}"
AdvSceneSwitcher.action.http="HTTP" AdvSceneSwitcher.action.http="HTTP"
AdvSceneSwitcher.action.http.legacy="HTTP (legacy)"
AdvSceneSwitcher.action.http.setHeaders="Set headers" AdvSceneSwitcher.action.http.setHeaders="Set headers"
AdvSceneSwitcher.action.http.headers="Headers:" AdvSceneSwitcher.action.http.headers="Headers:"
AdvSceneSwitcher.action.http.addHeader="Add header" AdvSceneSwitcher.action.http.addHeader="Add header"
AdvSceneSwitcher.action.http.addHeader.name="Header name"
AdvSceneSwitcher.action.http.addHeader.value="Header value"
AdvSceneSwitcher.action.http.setParams="Set parameters"
AdvSceneSwitcher.action.http.params="Parameters:"
AdvSceneSwitcher.action.http.addParam.name="Parameter name"
AdvSceneSwitcher.action.http.addParam.value="Parameter value"
AdvSceneSwitcher.action.http.body="Message body:"
AdvSceneSwitcher.action.http.type.get="GET" AdvSceneSwitcher.action.http.type.get="GET"
AdvSceneSwitcher.action.http.type.post="POST" AdvSceneSwitcher.action.http.type.post="POST"
AdvSceneSwitcher.action.http.type.put="PUT"
AdvSceneSwitcher.action.http.type.patch="PATCH"
AdvSceneSwitcher.action.http.type.delete="DELETE"
AdvSceneSwitcher.action.http.layout.method="Send{{method}}to URL{{url}}at path{{path}}"
AdvSceneSwitcher.action.http.layout.contentType="Content type:{{contentType}}"
AdvSceneSwitcher.action.http.layout.timeout="Timeout:{{timeout}}seconds"
AdvSceneSwitcher.action.http.entry.line1="Send{{method}}to{{url}}" AdvSceneSwitcher.action.http.entry.line1="Send{{method}}to{{url}}"
AdvSceneSwitcher.action.http.entry.line2="Timeout:{{timeout}}seconds" AdvSceneSwitcher.action.http.entry.line2="Timeout:{{timeout}}seconds"
AdvSceneSwitcher.action.variable="Variable" AdvSceneSwitcher.action.variable="Variable"
@@ -1113,6 +1126,8 @@ AdvSceneSwitcher.action.variable.entry.userInput.customPrompt="{{useCustomPrompt
AdvSceneSwitcher.action.variable.entry.userInput.placeholder="{{useInputPlaceholder}}Fill with placeholder{{inputPlaceholder}}" AdvSceneSwitcher.action.variable.entry.userInput.placeholder="{{useInputPlaceholder}}Fill with placeholder{{inputPlaceholder}}"
AdvSceneSwitcher.action.variable.entry.randomNumber="Generate random number in range from{{randomNumberStart}}to{{randomNumberEnd}}" AdvSceneSwitcher.action.variable.entry.randomNumber="Generate random number in range from{{randomNumberStart}}to{{randomNumberEnd}}"
AdvSceneSwitcher.action.projector="Projector" AdvSceneSwitcher.action.projector="Projector"
AdvSceneSwitcher.action.projector.action.open="Open"
AdvSceneSwitcher.action.projector.action.close="Close"
AdvSceneSwitcher.action.projector.type.source="Source" AdvSceneSwitcher.action.projector.type.source="Source"
AdvSceneSwitcher.action.projector.type.scene="Scene" AdvSceneSwitcher.action.projector.type.scene="Scene"
AdvSceneSwitcher.action.projector.type.preview="Preview" AdvSceneSwitcher.action.projector.type.preview="Preview"
@@ -1121,8 +1136,9 @@ AdvSceneSwitcher.action.projector.type.multiview="Multiview"
AdvSceneSwitcher.action.projector.display="Display" AdvSceneSwitcher.action.projector.display="Display"
AdvSceneSwitcher.action.projector.windowed="Windowed" AdvSceneSwitcher.action.projector.windowed="Windowed"
AdvSceneSwitcher.action.projector.fullscreen="Fullscreen" AdvSceneSwitcher.action.projector.fullscreen="Fullscreen"
AdvSceneSwitcher.action.projector.entry="Open{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}" AdvSceneSwitcher.action.projector.entry.close="{{actions}}projector with name{{projectorWindowName}}{{regex}}"
AdvSceneSwitcher.action.projector.entry.monitor="on{{monitors}}" AdvSceneSwitcher.action.projector.entry.open.windowed="{{actions}}{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.projector.entry.open.fullscreen="{{actions}}{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}on{{monitors}}"
AdvSceneSwitcher.action.midi="MIDI" AdvSceneSwitcher.action.midi="MIDI"
AdvSceneSwitcher.action.midi.entry="Send message to{{device}}:" AdvSceneSwitcher.action.midi.entry="Send message to{{device}}:"
AdvSceneSwitcher.action.midi.entry.listen="Set MIDI message selection to messages incoming on{{listenDevices}}:{{listenButton}}" AdvSceneSwitcher.action.midi.entry.listen="Set MIDI message selection to messages incoming on{{listenDevices}}:{{listenButton}}"
@@ -2038,6 +2054,11 @@ AdvSceneSwitcher.tempVar.gameCapture.class.description="Window class of the appl
AdvSceneSwitcher.tempVar.gameCapture.executable="Executable" AdvSceneSwitcher.tempVar.gameCapture.executable="Executable"
AdvSceneSwitcher.tempVar.gameCapture.executable.description="Executable name of the application captured by the source." AdvSceneSwitcher.tempVar.gameCapture.executable.description="Executable name of the application captured by the source."
AdvSceneSwitcher.tempVar.http.status="Status code"
AdvSceneSwitcher.tempVar.http.body="Message body"
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.selectScene="--select scene--" AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene" AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene" AdvSceneSwitcher.selectCurrentScene="Current Scene"
@@ -2076,7 +2097,6 @@ AdvSceneSwitcher.settings.suffix.type.list=" (List)"
AdvSceneSwitcher.settings.suffix.type.color=" (Color)" AdvSceneSwitcher.settings.suffix.type.color=" (Color)"
AdvSceneSwitcher.settings.suffix.type.button=" (Button)" AdvSceneSwitcher.settings.suffix.type.button=" (Button)"
AdvSceneSwitcher.settings.suffix.type.font=" (Font)" AdvSceneSwitcher.settings.suffix.type.font=" (Font)"
AdvSceneSwitcher.settings.suffix.type.font=" (Font)"
AdvSceneSwitcher.settings.suffix.type.editableList=" (Editable list)" AdvSceneSwitcher.settings.suffix.type.editableList=" (Editable list)"
AdvSceneSwitcher.settings.suffix.type.frameRate=" (Frame rate)" AdvSceneSwitcher.settings.suffix.type.frameRate=" (Frame rate)"
AdvSceneSwitcher.settings.suffix.type.group=" (Group)" AdvSceneSwitcher.settings.suffix.type.group=" (Group)"

View File

@@ -438,7 +438,6 @@ AdvSceneSwitcher.action.random="Aleatorio"
AdvSceneSwitcher.action.random.entry="Ejecute aleatoriamente cualquiera de las siguientes macros (las macros en pausa se ignoran)" AdvSceneSwitcher.action.random.entry="Ejecute aleatoriamente cualquiera de las siguientes macros (las macros en pausa se ignoran)"
AdvSceneSwitcher.action.systray="Notificación de la bandeja del sistema" AdvSceneSwitcher.action.systray="Notificación de la bandeja del sistema"
AdvSceneSwitcher.action.screenshot="Captura de pantalla" AdvSceneSwitcher.action.screenshot="Captura de pantalla"
AdvSceneSwitcher.action.screenshot.mainOutput="Salida principal de OBS"
AdvSceneSwitcher.action.profile="Perfil" AdvSceneSwitcher.action.profile="Perfil"
AdvSceneSwitcher.action.profile.entry="Cambiar perfil activo a {{profiles}}" AdvSceneSwitcher.action.profile.entry="Cambiar perfil activo a {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="Colección de escenas" AdvSceneSwitcher.action.sceneCollection="Colección de escenas"

View File

@@ -647,7 +647,6 @@ AdvSceneSwitcher.action.screenshot.save.default="Par défaut"
AdvSceneSwitcher.action.screenshot.save.custom="Personnalisé" AdvSceneSwitcher.action.screenshot.save.custom="Personnalisé"
AdvSceneSwitcher.action.screenshot.type.source="Source" AdvSceneSwitcher.action.screenshot.type.source="Source"
AdvSceneSwitcher.action.screenshot.type.scene="Scène" AdvSceneSwitcher.action.screenshot.type.scene="Scène"
AdvSceneSwitcher.action.screenshot.mainOutput="Sortie principale d'OBS"
AdvSceneSwitcher.action.screenshot.blackscreenNote="Les sources ou les scènes qui ne sont pas toujours rendues peuvent entraîner des parties de captures d'écran vides." AdvSceneSwitcher.action.screenshot.blackscreenNote="Les sources ou les scènes qui ne sont pas toujours rendues peuvent entraîner des parties de captures d'écran vides."
AdvSceneSwitcher.action.screenshot.entry="Capturer{{targetType}}{{sources}}{{scenes}}et enregistrer à l'emplacement{{saveType}}{{variables}}" AdvSceneSwitcher.action.screenshot.entry="Capturer{{targetType}}{{sources}}{{scenes}}et enregistrer à l'emplacement{{saveType}}{{variables}}"
AdvSceneSwitcher.action.profile="Profil" AdvSceneSwitcher.action.profile="Profil"
@@ -718,8 +717,6 @@ AdvSceneSwitcher.action.projector.type.multiview="Multivue"
AdvSceneSwitcher.action.projector.display="Affichage" AdvSceneSwitcher.action.projector.display="Affichage"
AdvSceneSwitcher.action.projector.windowed="Fenêtré" AdvSceneSwitcher.action.projector.windowed="Fenêtré"
AdvSceneSwitcher.action.projector.fullscreen="Plein écran" AdvSceneSwitcher.action.projector.fullscreen="Plein écran"
AdvSceneSwitcher.action.projector.entry="Ouvrir le projecteur{{windowTypes}}de{{types}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.projector.entry.monitor="sur{{monitors}}"
AdvSceneSwitcher.action.midi="MIDI" AdvSceneSwitcher.action.midi="MIDI"
AdvSceneSwitcher.action.midi.entry="Envoyer un message à{{device}}:" AdvSceneSwitcher.action.midi.entry="Envoyer un message à{{device}}:"
AdvSceneSwitcher.action.midi.entry.listen="Définir la sélection de messages MIDI sur les messages entrants de{{listenDevices}}:{{listenButton}}" AdvSceneSwitcher.action.midi.entry.listen="Définir la sélection de messages MIDI sur les messages entrants de{{listenDevices}}:{{listenButton}}"

View File

@@ -979,7 +979,6 @@ AdvSceneSwitcher.action.screenshot.save.default="デフォルト"
AdvSceneSwitcher.action.screenshot.save.custom="カスタム" AdvSceneSwitcher.action.screenshot.save.custom="カスタム"
; AdvSceneSwitcher.action.screenshot.type.source="Source" ; AdvSceneSwitcher.action.screenshot.type.source="Source"
AdvSceneSwitcher.action.screenshot.type.scene="シーン" AdvSceneSwitcher.action.screenshot.type.scene="シーン"
AdvSceneSwitcher.action.screenshot.mainOutput="OBSの主な出力"
AdvSceneSwitcher.action.screenshot.blackscreenNote="常にレンダリングされるわけではないソースやシーンにより、スクリーンショットの一部が空白のままになる場合があります。" AdvSceneSwitcher.action.screenshot.blackscreenNote="常にレンダリングされるわけではないソースやシーンにより、スクリーンショットの一部が空白のままになる場合があります。"
AdvSceneSwitcher.action.screenshot.entry="スクリーンショット{{targetType}}{{sources}}{{scenes}}を作成し、{{saveType}}{{variables}}の場所に保存します" AdvSceneSwitcher.action.screenshot.entry="スクリーンショット{{targetType}}{{sources}}{{scenes}}を作成し、{{saveType}}{{variables}}の場所に保存します"
AdvSceneSwitcher.action.profile="プロファイル" AdvSceneSwitcher.action.profile="プロファイル"
@@ -1068,8 +1067,6 @@ AdvSceneSwitcher.action.projector.type.multiview="マルチビュー"
AdvSceneSwitcher.action.projector.display="ディスプレイ" AdvSceneSwitcher.action.projector.display="ディスプレイ"
AdvSceneSwitcher.action.projector.windowed="ウィンドウ" AdvSceneSwitcher.action.projector.windowed="ウィンドウ"
AdvSceneSwitcher.action.projector.fullscreen="フルスクリーン" AdvSceneSwitcher.action.projector.fullscreen="フルスクリーン"
AdvSceneSwitcher.action.projector.entry="{{types}}{{scenes}}{{sources}}の{{windowTypes}}プロジェクターを開きます"
AdvSceneSwitcher.action.projector.entry.monitor="{{monitors}}上で"
; AdvSceneSwitcher.action.midi="MIDI" ; AdvSceneSwitcher.action.midi="MIDI"
AdvSceneSwitcher.action.midi.entry="メッセージを{{device}}に送信します:" AdvSceneSwitcher.action.midi.entry="メッセージを{{device}}に送信します:"
AdvSceneSwitcher.action.midi.entry.listen="MIDI メッセージの選択を {{listenDevices}} で受信するメッセージに設定します:{{listenButton}}" AdvSceneSwitcher.action.midi.entry.listen="MIDI メッセージの選択を {{listenDevices}} で受信するメッセージに設定します:{{listenButton}}"

View File

@@ -960,7 +960,6 @@ AdvSceneSwitcher.action.screenshot.save.default="Padrão"
AdvSceneSwitcher.action.screenshot.save.custom="Personalizado" AdvSceneSwitcher.action.screenshot.save.custom="Personalizado"
AdvSceneSwitcher.action.screenshot.type.source="Fonte" AdvSceneSwitcher.action.screenshot.type.source="Fonte"
AdvSceneSwitcher.action.screenshot.type.scene="Cena" AdvSceneSwitcher.action.screenshot.type.scene="Cena"
AdvSceneSwitcher.action.screenshot.mainOutput="Saída principal do OBS"
AdvSceneSwitcher.action.screenshot.blackscreenNote="Fontes ou cenas, que nem sempre são renderizadas, podem resultar em algumas partes das capturas de tela permanecendo em branco." AdvSceneSwitcher.action.screenshot.blackscreenNote="Fontes ou cenas, que nem sempre são renderizadas, podem resultar em algumas partes das capturas de tela permanecendo em branco."
AdvSceneSwitcher.action.screenshot.entry="Captura de tela{{targetType}}{{sources}}{{scenes}}e salvar em{{saveType}}{{variables}}localização" AdvSceneSwitcher.action.screenshot.entry="Captura de tela{{targetType}}{{sources}}{{scenes}}e salvar em{{saveType}}{{variables}}localização"
AdvSceneSwitcher.action.profile="Perfil" AdvSceneSwitcher.action.profile="Perfil"
@@ -1049,8 +1048,6 @@ AdvSceneSwitcher.action.projector.type.multiview="Multivisão"
AdvSceneSwitcher.action.projector.display="Exibição" AdvSceneSwitcher.action.projector.display="Exibição"
AdvSceneSwitcher.action.projector.windowed="Janela" AdvSceneSwitcher.action.projector.windowed="Janela"
AdvSceneSwitcher.action.projector.fullscreen="Tela cheia" AdvSceneSwitcher.action.projector.fullscreen="Tela cheia"
AdvSceneSwitcher.action.projector.entry="Abrir{{windowTypes}}projetor de{{types}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.projector.entry.monitor="em{{monitors}}"
AdvSceneSwitcher.action.midi="MIDI" AdvSceneSwitcher.action.midi="MIDI"
AdvSceneSwitcher.action.midi.entry="Enviar mensagem para{{device}}:" AdvSceneSwitcher.action.midi.entry="Enviar mensagem para{{device}}:"
AdvSceneSwitcher.action.midi.entry.listen="Definir seleção de mensagem MIDI para mensagens recebidas em{{listenDevices}}:{{listenButton}}" AdvSceneSwitcher.action.midi.entry.listen="Definir seleção de mensagem MIDI para mensagens recebidas em{{listenDevices}}:{{listenButton}}"

View File

@@ -356,7 +356,6 @@ AdvSceneSwitcher.action.random="Rastgele"
AdvSceneSwitcher.action.random.entry="Aşağıdaki makrolardan herhangi birini rastgele çalıştırın (duraklatılmış makrolar yoksayılır)" AdvSceneSwitcher.action.random.entry="Aşağıdaki makrolardan herhangi birini rastgele çalıştırın (duraklatılmış makrolar yoksayılır)"
AdvSceneSwitcher.action.systray="Sistem tepsisi bildirimi" AdvSceneSwitcher.action.systray="Sistem tepsisi bildirimi"
AdvSceneSwitcher.action.screenshot="Ekran görüntüsü" AdvSceneSwitcher.action.screenshot="Ekran görüntüsü"
AdvSceneSwitcher.action.screenshot.mainOutput="OBS'nin ana çıkışı"
AdvSceneSwitcher.action.profile="Profil" AdvSceneSwitcher.action.profile="Profil"
AdvSceneSwitcher.action.profile.entry="Aktif profili şununla değiştir: {{profiles}}" AdvSceneSwitcher.action.profile.entry="Aktif profili şununla değiştir: {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="Sahne koleksiyonu" AdvSceneSwitcher.action.sceneCollection="Sahne koleksiyonu"

File diff suppressed because it is too large Load Diff

2
deps/libremidi vendored

View File

@@ -1,6 +1,5 @@
#include "advanced-scene-switcher.hpp" #include "advanced-scene-switcher.hpp"
#include "backup.hpp" #include "backup.hpp"
#include "curl-helper.hpp"
#include "log-helper.hpp" #include "log-helper.hpp"
#include "macro-helpers.hpp" #include "macro-helpers.hpp"
#include "obs-module-helper.hpp" #include "obs-module-helper.hpp"
@@ -19,10 +18,15 @@
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <QAction> #include <QAction>
#include <QDirIterator> #include <QDirIterator>
#include <QLibrary>
#include <QMainWindow> #include <QMainWindow>
#include <QTextStream> #include <QTextStream>
#include <regex> #include <regex>
#ifdef _WIN32
#include <Windows.h>
#endif
namespace advss { namespace advss {
AdvSceneSwitcher *AdvSceneSwitcher::window = nullptr; AdvSceneSwitcher *AdvSceneSwitcher::window = nullptr;
@@ -307,7 +311,7 @@ void SwitcherData::Thread()
} }
} }
ResetForNextInterval(); RunIntervalResetSteps();
if (match) { if (match) {
if (macroMatch) { if (macroMatch) {
@@ -357,14 +361,6 @@ void SwitcherData::SetPreconditions()
InvalidateMacroTempVarValues(); InvalidateMacroTempVarValues();
} }
void SwitcherData::ResetForNextInterval()
{
// Plugin reset functions
for (const auto &func : resetIntervalSteps) {
func();
}
}
bool SwitcherData::CheckForMatch(OBSWeakSource &scene, bool SwitcherData::CheckForMatch(OBSWeakSource &scene,
OBSWeakSource &transition, int &linger, OBSWeakSource &transition, int &linger,
bool &setPrevSceneAfterLinger, bool &setPrevSceneAfterLinger,
@@ -445,7 +441,7 @@ void AutoStartActionQueues();
void SwitcherData::Start() void SwitcherData::Start()
{ {
if (!(th && th->isRunning())) { if (!(th && th->isRunning())) {
ResetForNextInterval(); RunIntervalResetSteps();
ResetMacros(); ResetMacros();
AutoStartActionQueues(); AutoStartActionQueues();
@@ -453,10 +449,7 @@ void SwitcherData::Start()
th = new SwitcherThread(); th = new SwitcherThread();
th->start((QThread::Priority)threadPriority); th->start((QThread::Priority)threadPriority);
// Will be overwritten quickly but might be useful RunStartSteps();
writeToStatusFile("Advanced Scene Switcher running");
SendWebsocketVendorEvent("AdvancedSceneSwitcherStarted",
nullptr);
} }
if (showSystemTrayNotifications) { if (showSystemTrayNotifications) {
@@ -483,11 +476,7 @@ void SwitcherData::Stop()
th->wait(); th->wait();
delete th; delete th;
th = nullptr; th = nullptr;
writeToStatusFile("Advanced Scene Switcher stopped"); RunStopSteps();
if (!obsIsShuttingDown) {
SendWebsocketVendorEvent("AdvancedSceneSwitcherStopped",
nullptr);
}
} }
if (showSystemTrayNotifications) { if (showSystemTrayNotifications) {

View File

@@ -21,6 +21,29 @@ bool FileSwitch::pause = false;
static QObject *addPulse = nullptr; static QObject *addPulse = nullptr;
static std::hash<std::string> strHash; static std::hash<std::string> strHash;
static void writeToStatusFile(const QString &msg)
{
if (!GetSwitcher() || !GetSwitcher()->fileIO.writeEnabled ||
GetSwitcher()->fileIO.writePath.empty()) {
return;
}
QFile file(QString::fromStdString(GetSwitcher()->fileIO.writePath));
if (file.open(QIODevice::ReadWrite)) {
QTextStream stream(&file);
stream << msg << Qt::endl;
}
file.close();
}
static bool _ = []() {
AddStartStep(
[]() { writeToStatusFile("Advanced Scene Switcher running"); });
AddStopStep(
[]() { writeToStatusFile("Advanced Scene Switcher stopped"); });
return true;
}();
void AdvSceneSwitcher::on_browseButton_clicked() void AdvSceneSwitcher::on_browseButton_clicked()
{ {
QString path = QFileDialog::getOpenFileName( QString path = QFileDialog::getOpenFileName(
@@ -113,20 +136,6 @@ void SwitcherData::writeSceneInfoToFile()
} }
} }
void SwitcherData::writeToStatusFile(const QString &msg)
{
if (!fileIO.writeEnabled || fileIO.writePath.empty()) {
return;
}
QFile file(QString::fromStdString(fileIO.writePath));
if (file.open(QIODevice::ReadWrite)) {
QTextStream stream(&file);
stream << msg << Qt::endl;
}
file.close();
}
bool SwitcherData::checkSwitchInfoFromFile(OBSWeakSource &scene, bool SwitcherData::checkSwitchInfoFromFile(OBSWeakSource &scene,
OBSWeakSource &transition) OBSWeakSource &transition)
{ {

View File

@@ -17,6 +17,9 @@ static inline void populateActionSelection(QComboBox *list)
QString entry(obs_module_text(action._name.c_str())); QString entry(obs_module_text(action._name.c_str()));
if (list->findText(entry) == -1) { if (list->findText(entry) == -1) {
list->addItem(entry); list->addItem(entry);
qobject_cast<QListView *>(list->view())
->setRowHidden(list->count() - 1,
action._hidden);
} else { } else {
blog(LOG_WARNING, blog(LOG_WARNING,
"did not insert duplicate action entry with name \"%s\"", "did not insert duplicate action entry with name \"%s\"",

View File

@@ -12,6 +12,7 @@ struct MacroActionInfo {
std::function<std::shared_ptr<MacroAction>(Macro *m)> _create = nullptr; std::function<std::shared_ptr<MacroAction>(Macro *m)> _create = nullptr;
CreateActionWidget _createWidget = nullptr; CreateActionWidget _createWidget = nullptr;
std::string _name; std::string _name;
bool _hidden = false;
}; };
class MacroActionFactory { class MacroActionFactory {

View File

@@ -9,7 +9,6 @@
#include <QPushButton> #include <QPushButton>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QTimer> #include <QTimer>
#include <QListWidget>
namespace advss { namespace advss {

View File

@@ -67,9 +67,7 @@ void SetMacroSwitchedScene(bool value)
{ {
static bool setupDone = false; static bool setupDone = false;
if (!setupDone) { if (!setupDone) {
// Will always be called with switcher lock already held AddIntervalResetStep([]() { macroSceneSwitched = false; });
AddIntervalResetStep([]() { macroSceneSwitched = false; },
false);
setupDone = true; setupDone = true;
} }
macroSceneSwitched = value; macroSceneSwitched = value;

View File

@@ -108,6 +108,24 @@ static const std::string setTempVarValueDeclString =
tempVarIdParam.data() + ", in string " + valueParam.data() + tempVarIdParam.data() + ", in string " + valueParam.data() +
", in int " + GetInstanceIdParamName().data() + ")"; ", in int " + GetInstanceIdParamName().data() + ")";
/* Plugin status */
static constexpr std::string_view stopSignalName = "advss_plugin_stopped";
static constexpr std::string_view startSignalName = "advss_plugin_started";
static constexpr std::string_view getRunningStatusFuncName =
"advss_plugin_running";
static constexpr std::string_view resetIntervalSignalName =
"advss_interval_reset";
static const std::string stopSignalDeclString =
std::string("void ") + stopSignalName.data() + "()";
static const std::string startSignalDeclString =
std::string("void ") + startSignalName.data() + "()";
static const std::string resetIntervalDeclString =
std::string("void ") + resetIntervalSignalName.data() + "()";
static const std::string getRunningStatusDeclString =
std::string("bool ") + getRunningStatusFuncName.data() + "()";
static bool setup(); static bool setup();
static bool setupDone = setup(); static bool setupDone = setup();
@@ -134,6 +152,25 @@ static bool setup()
&ScriptHandler::DeregisterAllTempVars, nullptr); &ScriptHandler::DeregisterAllTempVars, nullptr);
proc_handler_add(ph, setTempVarValueDeclString.c_str(), proc_handler_add(ph, setTempVarValueDeclString.c_str(),
&ScriptHandler::SetTempVarValue, nullptr); &ScriptHandler::SetTempVarValue, nullptr);
proc_handler_add(ph, getRunningStatusDeclString.c_str(),
&ScriptHandler::GetRunningStatus, nullptr);
auto sh = obs_get_signal_handler();
signal_handler_add(sh, stopSignalDeclString.c_str());
signal_handler_add(sh, startSignalDeclString.c_str());
signal_handler_add(sh, resetIntervalDeclString.c_str());
static constexpr auto triggerSignal = [](const std::string_view &name) {
auto sh = obs_get_signal_handler();
struct calldata data;
calldata_init(&data);
signal_handler_signal(sh, name.data(), &data);
calldata_free(&data);
};
AddIntervalResetStep([]() { triggerSignal(resetIntervalSignalName); });
AddStartStep([]() { triggerSignal(startSignalName); });
AddStopStep([]() { triggerSignal(stopSignalName); });
return true; return true;
} }
@@ -476,6 +513,11 @@ void ScriptHandler::SetVariableValue(void *, calldata_t *data)
RETURN_SUCCESS(); RETURN_SUCCESS();
} }
void ScriptHandler::GetRunningStatus(void *ctx, calldata_t *data)
{
calldata_set_bool(data, "is_running", PluginIsRunning());
}
void ScriptHandler::RegisterTempVar(void *, calldata_t *data) void ScriptHandler::RegisterTempVar(void *, calldata_t *data)
{ {
const char *variableId; const char *variableId;

View File

@@ -32,6 +32,7 @@ public:
static void DeregisterAllTempVars(void *ctx, calldata_t *data); static void DeregisterAllTempVars(void *ctx, calldata_t *data);
static void SetTempVarValue(void *ctx, calldata_t *data); static void SetTempVarValue(void *ctx, calldata_t *data);
static void SetVariableValue(void *ctx, calldata_t *data); static void SetVariableValue(void *ctx, calldata_t *data);
static void GetRunningStatus(void *ctx, calldata_t *data);
static bool ActionIdIsValid(const std::string &id); static bool ActionIdIsValid(const std::string &id);
static bool ConditionIdIsValid(const std::string &id); static bool ConditionIdIsValid(const std::string &id);

View File

@@ -168,17 +168,6 @@ void SwitcherData::SaveVersion(obs_data_t *obj,
obs_data_set_string(obj, "version", currentVersion.c_str()); obs_data_set_string(obj, "version", currentVersion.c_str());
} }
void SwitcherData::AddIntervalResetStep(std::function<void()> function,
bool tryLock)
{
if (!tryLock) {
resetIntervalSteps.emplace_back(function);
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
resetIntervalSteps.emplace_back(function);
}
void SwitcherData::RunPostLoadSteps() void SwitcherData::RunPostLoadSteps()
{ {
for (const auto &func : postLoadSteps) { for (const auto &func : postLoadSteps) {

View File

@@ -57,11 +57,9 @@ public:
bool AnySceneTransitionStarted(); bool AnySceneTransitionStarted();
void SetPreconditions(); void SetPreconditions();
void ResetForNextInterval();
void AddSaveStep(std::function<void(obs_data_t *)>); void AddSaveStep(std::function<void(obs_data_t *)>);
void AddLoadStep(std::function<void(obs_data_t *)>); void AddLoadStep(std::function<void(obs_data_t *)>);
void AddPostLoadStep(std::function<void()>); void AddPostLoadStep(std::function<void()>);
void AddIntervalResetStep(std::function<void()>, bool lock = true);
void RunPostLoadSteps(); void RunPostLoadSteps();
bool CheckForMatch(OBSWeakSource &scene, OBSWeakSource &transition, bool CheckForMatch(OBSWeakSource &scene, OBSWeakSource &transition,
int &linger, bool &setPreviousSceneAsMatch, int &linger, bool &setPreviousSceneAsMatch,
@@ -100,7 +98,6 @@ public:
std::vector<std::function<void(obs_data_t *)>> saveSteps; std::vector<std::function<void(obs_data_t *)>> saveSteps;
std::vector<std::function<void(obs_data_t *)>> loadSteps; std::vector<std::function<void(obs_data_t *)>> loadSteps;
std::vector<std::function<void()>> postLoadSteps; std::vector<std::function<void()>> postLoadSteps;
std::vector<std::function<void()>> resetIntervalSteps;
bool firstBoot = true; bool firstBoot = true;
bool transitionActive = false; bool transitionActive = false;
@@ -233,7 +230,6 @@ public:
bool checkPause(); bool checkPause();
void checkDefaultSceneTransitions(); void checkDefaultSceneTransitions();
void writeSceneInfoToFile(); void writeSceneInfoToFile();
void writeToStatusFile(const QString &msg);
void checkSwitchCooldown(bool &match); void checkSwitchCooldown(bool &match);
std::deque<WindowSwitch> windowSwitches; std::deque<WindowSwitch> windowSwitches;

View File

@@ -32,7 +32,9 @@ void PreventMouseWheelAdjustWithoutFocus(QWidget *w)
QString(w->metaObject()->className()) == QString(w->metaObject()->className()) ==
"advss::OSCMessageElementEdit" || "advss::OSCMessageElementEdit" ||
QString(w->metaObject()->className()) == QString(w->metaObject()->className()) ==
"advss::ChatMessagePropertyEdit") { "advss::ChatMessagePropertyEdit" ||
QString(w->metaObject()->className()) ==
"advss::KeyValueListContainerWidget") {
return; return;
} }
w->setFocusPolicy(Qt::StrongFocus); w->setFocusPolicy(Qt::StrongFocus);

View File

@@ -67,6 +67,8 @@ NonModalMessageDialog::NonModalMessageDialog(const QString &message, Type type,
} }
case Type::INPUT: { case Type::INPUT: {
_inputEdit = new ResizingPlainTextEdit(this); _inputEdit = new ResizingPlainTextEdit(this);
_inputEdit->setTabChangesFocus(true);
_inputEdit->setFocus();
connect(_inputEdit, &ResizingPlainTextEdit::textChanged, this, connect(_inputEdit, &ResizingPlainTextEdit::textChanged, this,
&NonModalMessageDialog::InputChanged); &NonModalMessageDialog::InputChanged);
layout->addWidget(_inputEdit); layout->addWidget(_inputEdit);

View File

@@ -21,6 +21,24 @@ static std::vector<std::function<void()>> &getPluginCleanupSteps()
return steps; return steps;
} }
static std::vector<std::function<void()>> &getResetIntervalSteps()
{
static std::vector<std::function<void()>> steps;
return steps;
}
static std::vector<std::function<void()>> &getStartSteps()
{
static std::vector<std::function<void()>> steps;
return steps;
}
static std::vector<std::function<void()>> &getStopSteps()
{
static std::vector<std::function<void()>> steps;
return steps;
}
static std::mutex mutex; static std::mutex mutex;
void SavePluginSettings(obs_data_t *obj) void SavePluginSettings(obs_data_t *obj)
@@ -48,9 +66,10 @@ void AddPostLoadStep(std::function<void()> step)
GetSwitcher()->AddPostLoadStep(step); GetSwitcher()->AddPostLoadStep(step);
} }
void AddIntervalResetStep(std::function<void()> step, bool lock) void AddIntervalResetStep(std::function<void()> step)
{ {
GetSwitcher()->AddIntervalResetStep(step, lock); std::lock_guard<std::mutex> lock(mutex);
getResetIntervalSteps().emplace_back(step);
} }
void RunPostLoadSteps() void RunPostLoadSteps()
@@ -100,6 +119,42 @@ void RunPluginCleanupSteps()
} }
} }
void RunIntervalResetSteps()
{
std::lock_guard<std::mutex> lock(mutex);
for (const auto &step : getResetIntervalSteps()) {
step();
}
}
void AddStartStep(std::function<void()> step)
{
std::lock_guard<std::mutex> lock(mutex);
getStartSteps().emplace_back(step);
}
void AddStopStep(std::function<void()> step)
{
std::lock_guard<std::mutex> lock(mutex);
getStopSteps().emplace_back(step);
}
void RunStartSteps()
{
std::lock_guard<std::mutex> lock(mutex);
for (const auto &step : getStartSteps()) {
step();
}
}
void RunStopSteps()
{
std::lock_guard<std::mutex> lock(mutex);
for (const auto &step : getStopSteps()) {
step();
}
}
void StopPlugin() void StopPlugin()
{ {
GetSwitcher()->Stop(); GetSwitcher()->Stop();

View File

@@ -11,7 +11,7 @@ EXPORT void LoadPluginSettings(obs_data_t *);
EXPORT void AddSaveStep(std::function<void(obs_data_t *)>); EXPORT void AddSaveStep(std::function<void(obs_data_t *)>);
EXPORT void AddLoadStep(std::function<void(obs_data_t *)>); EXPORT void AddLoadStep(std::function<void(obs_data_t *)>);
EXPORT void AddPostLoadStep(std::function<void()>); EXPORT void AddPostLoadStep(std::function<void()>);
EXPORT void AddIntervalResetStep(std::function<void()>, bool lock = true); EXPORT void AddIntervalResetStep(std::function<void()>);
EXPORT void RunPostLoadSteps(); EXPORT void RunPostLoadSteps();
EXPORT void AddPluginInitStep(std::function<void()>); EXPORT void AddPluginInitStep(std::function<void()>);
@@ -25,6 +25,11 @@ EXPORT void StopPlugin();
EXPORT void StartPlugin(); EXPORT void StartPlugin();
EXPORT bool PluginIsRunning(); EXPORT bool PluginIsRunning();
EXPORT int GetIntervalValue(); EXPORT int GetIntervalValue();
void AddStartStep(std::function<void()>);
void AddStopStep(std::function<void()>);
void RunStartSteps();
void RunStopSteps();
void RunIntervalResetSteps();
enum class NoMatchBehavior { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 }; enum class NoMatchBehavior { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 };
EXPORT void SetPluginNoMatchBehavior(NoMatchBehavior); EXPORT void SetPluginNoMatchBehavior(NoMatchBehavior);

View File

@@ -23,6 +23,14 @@ static bool setupDone = setup();
bool setup() bool setup()
{ {
AddPluginPostLoadStep(registerWebsocketVendor); AddPluginPostLoadStep(registerWebsocketVendor);
AddStartStep([]() {
SendWebsocketVendorEvent("AdvancedSceneSwitcherStarted",
nullptr);
});
AddStopStep([]() {
SendWebsocketVendorEvent("AdvancedSceneSwitcherStopped",
nullptr);
});
return true; return true;
} }
@@ -109,6 +117,9 @@ void RegisterWebsocketRequest(
void SendWebsocketVendorEvent(const std::string &eventName, obs_data_t *data) void SendWebsocketVendorEvent(const std::string &eventName, obs_data_t *data)
{ {
if (OBSIsShuttingDown()) {
return;
}
obs_websocket_vendor_emit_event(vendor, eventName.c_str(), data); obs_websocket_vendor_emit_event(vendor, eventName.c_str(), data);
} }

View File

@@ -28,6 +28,7 @@ install_advss_plugin_dependency(...)
... to install the plugin and its dependencies. ... to install the plugin and its dependencies.
#]] #]]
add_plugin(http)
add_plugin(midi) add_plugin(midi)
add_plugin(openvr) add_plugin(openvr)
add_plugin(stream-deck) add_plugin(stream-deck)

View File

@@ -15,8 +15,8 @@ target_sources(
macro-action-filter.hpp macro-action-filter.hpp
macro-action-hotkey.cpp macro-action-hotkey.cpp
macro-action-hotkey.hpp macro-action-hotkey.hpp
macro-action-http.cpp macro-action-http-legacy.cpp
macro-action-http.hpp macro-action-http-legacy.hpp
macro-action-log.cpp macro-action-log.cpp
macro-action-log.hpp macro-action-log.hpp
macro-action-media.cpp macro-action-media.cpp

View File

@@ -449,8 +449,11 @@ void MacroActionFilterEdit::SetWidgetVisibility()
_entryData->_action == MacroActionFilter::Action::SETTINGS && _entryData->_action == MacroActionFilter::Action::SETTINGS &&
_entryData->_settingsInputMethod == _entryData->_settingsInputMethod ==
MacroActionFilter::SettingsInputMethod::JSON_STRING); MacroActionFilter::SettingsInputMethod::JSON_STRING);
_getSettings->setVisible(_entryData->_action == _getSettings->setVisible(
MacroActionFilter::Action::SETTINGS); _entryData->_action == MacroActionFilter::Action::SETTINGS &&
_entryData->_settingsInputMethod !=
MacroActionFilter::SettingsInputMethod::
INDIVIDUAL_TEMPVAR);
_tempVars->setVisible(_entryData->_action == _tempVars->setVisible(_entryData->_action ==
MacroActionFilter::Action::SETTINGS && MacroActionFilter::Action::SETTINGS &&
_entryData->_settingsInputMethod == _entryData->_settingsInputMethod ==

View File

@@ -1,4 +1,4 @@
#include "macro-action-http.hpp" #include "macro-action-http-legacy.hpp"
#include "curl-helper.hpp" #include "curl-helper.hpp"
#include "layout-helpers.hpp" #include "layout-helpers.hpp"
@@ -9,7 +9,7 @@ const std::string MacroActionHttp::id = "http";
bool MacroActionHttp::_registered = MacroActionFactory::Register( bool MacroActionHttp::_registered = MacroActionFactory::Register(
MacroActionHttp::id, MacroActionHttp::id,
{MacroActionHttp::Create, MacroActionHttpEdit::Create, {MacroActionHttp::Create, MacroActionHttpEdit::Create,
"AdvSceneSwitcher.action.http"}); "AdvSceneSwitcher.action.http.legacy", true});
const static std::map<MacroActionHttp::Method, std::string> methods = { const static std::map<MacroActionHttp::Method, std::string> methods = {
{MacroActionHttp::Method::GET, "AdvSceneSwitcher.action.http.type.get"}, {MacroActionHttp::Method::GET, "AdvSceneSwitcher.action.http.type.get"},

View File

@@ -34,11 +34,11 @@ const static std::map<PluginStateAction, std::string> actionTypes = {
const static std::map<NoMatchBehavior, std::string> noMatchValues = { const static std::map<NoMatchBehavior, std::string> noMatchValues = {
{NoMatchBehavior::NO_SWITCH, {NoMatchBehavior::NO_SWITCH,
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch"}, "AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.dontSwitch"},
{NoMatchBehavior::SWITCH, {NoMatchBehavior::SWITCH,
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo"}, "AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchTo"},
{NoMatchBehavior::RANDOM_SWITCH, {NoMatchBehavior::RANDOM_SWITCH,
"AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom"}, "AdvSceneSwitcher.generalTab.generalBehavior.onNoMatch.switchToRandom"},
}; };
static void stopPlugin() static void stopPlugin()

View File

@@ -5,6 +5,8 @@
#include "source-helpers.hpp" #include "source-helpers.hpp"
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <QApplication>
#include <QWindow>
namespace advss { namespace advss {
@@ -28,8 +30,34 @@ const static std::map<MacroActionProjector::Type, std::string> selectionTypes =
"AdvSceneSwitcher.action.projector.type.multiview"}, "AdvSceneSwitcher.action.projector.type.multiview"},
}; };
static void closeOBSProjectorWindows(const std::string expectedWindowTitle,
const RegexConfig &regex)
{
for (QWindow *widget : QApplication::allWindows()) {
if (!widget->property("isOBSProjectorWindow").toBool()) {
continue;
}
auto const windowTitle = widget->title().toStdString();
if (!regex.Enabled() && expectedWindowTitle != windowTitle) {
continue;
}
if (!regex.Matches(windowTitle, expectedWindowTitle)) {
continue;
}
widget->close();
}
}
bool MacroActionProjector::PerformAction() bool MacroActionProjector::PerformAction()
{ {
if (_action == Action::CLOSE) {
closeOBSProjectorWindows(_projectorWindowName, _regex);
return true;
}
std::string name = ""; std::string name = "";
const char *type = ""; const char *type = "";
@@ -78,10 +106,16 @@ bool MacroActionProjector::PerformAction()
void MacroActionProjector::LogAction() const void MacroActionProjector::LogAction() const
{ {
if (_action == Action::CLOSE) {
ablog(LOG_INFO, "closing projector window \"%s\"",
_projectorWindowName.c_str());
return;
}
auto it = selectionTypes.find(_type); auto it = selectionTypes.find(_type);
if (it != selectionTypes.end()) { if (it != selectionTypes.end()) {
ablog(LOG_INFO, ablog(LOG_INFO,
"performed projector action \"%s\" with" "open projector \"%s\" with"
"source \"%s\"," "source \"%s\","
"scene \"%s\"," "scene \"%s\","
"monitor %d", "monitor %d",
@@ -96,24 +130,30 @@ void MacroActionProjector::LogAction() const
bool MacroActionProjector::Save(obs_data_t *obj) const bool MacroActionProjector::Save(obs_data_t *obj) const
{ {
MacroAction::Save(obj); MacroAction::Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
obs_data_set_int(obj, "type", static_cast<int>(_type)); obs_data_set_int(obj, "type", static_cast<int>(_type));
obs_data_set_int(obj, "monitor", _monitor); obs_data_set_int(obj, "monitor", _monitor);
obs_data_set_string(obj, "monitorName", _monitorName.c_str()); obs_data_set_string(obj, "monitorName", _monitorName.c_str());
obs_data_set_bool(obj, "fullscreen", _fullscreen); obs_data_set_bool(obj, "fullscreen", _fullscreen);
_scene.Save(obj); _scene.Save(obj);
_source.Save(obj); _source.Save(obj);
_projectorWindowName.Save(obj, "projectorWindowName");
_regex.Save(obj);
return true; return true;
} }
bool MacroActionProjector::Load(obs_data_t *obj) bool MacroActionProjector::Load(obs_data_t *obj)
{ {
MacroAction::Load(obj); MacroAction::Load(obj);
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
_type = static_cast<Type>(obs_data_get_int(obj, "type")); _type = static_cast<Type>(obs_data_get_int(obj, "type"));
_monitor = obs_data_get_int(obj, "monitor"); _monitor = obs_data_get_int(obj, "monitor");
_monitorName = obs_data_get_string(obj, "monitorName"); _monitorName = obs_data_get_string(obj, "monitorName");
_fullscreen = obs_data_get_bool(obj, "fullscreen"); _fullscreen = obs_data_get_bool(obj, "fullscreen");
_scene.Load(obj); _scene.Load(obj);
_source.Load(obj); _source.Load(obj);
_projectorWindowName.Load(obj, "projectorWindowName");
_regex.Load(obj);
return true; return true;
} }
@@ -131,6 +171,7 @@ void MacroActionProjector::ResolveVariablesToFixedValues()
{ {
_source.ResolveVariables(); _source.ResolveVariables();
_scene.ResolveVariables(); _scene.ResolveVariables();
_projectorWindowName.ResolveVariables();
} }
void MacroActionProjector::SetMonitor(int idx) void MacroActionProjector::SetMonitor(int idx)
@@ -166,14 +207,22 @@ bool MacroActionProjector::MonitorSetupChanged() const
QString::fromStdString(_monitorName); QString::fromStdString(_monitorName);
} }
static inline void populateSelectionTypes(QComboBox *list) static void populateActionSelection(QComboBox *list)
{ {
for (auto entry : selectionTypes) { list->addItem(obs_module_text(
list->addItem(obs_module_text(entry.second.c_str())); "AdvSceneSwitcher.action.projector.action.open"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.action.projector.action.close"));
}
static void populateSelectionTypes(QComboBox *list)
{
for (const auto &[_, name] : selectionTypes) {
list->addItem(obs_module_text(name.c_str()));
} }
} }
static inline void populateWindowTypes(QComboBox *list) static void populateWindowTypes(QComboBox *list)
{ {
list->addItem( list->addItem(
obs_module_text("AdvSceneSwitcher.action.projector.windowed")); obs_module_text("AdvSceneSwitcher.action.projector.windowed"));
@@ -184,14 +233,18 @@ static inline void populateWindowTypes(QComboBox *list)
MacroActionProjectorEdit::MacroActionProjectorEdit( MacroActionProjectorEdit::MacroActionProjectorEdit(
QWidget *parent, std::shared_ptr<MacroActionProjector> entryData) QWidget *parent, std::shared_ptr<MacroActionProjector> entryData)
: QWidget(parent), : QWidget(parent),
_windowTypes(new QComboBox()), _actions(new QComboBox()),
_types(new QComboBox()), _types(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(), QStringList(), true)),
_monitorSelection(new QHBoxLayout()), _monitors(new QComboBox()),
_monitors(new QComboBox()) _projectorWindowName(new VariableLineEdit(this)),
_regex(new RegexConfigWidget(this)),
_layout(new QHBoxLayout(this))
{ {
populateActionSelection(_actions);
populateWindowTypes(_windowTypes); populateWindowTypes(_windowTypes);
populateSelectionTypes(_types); populateSelectionTypes(_types);
auto sources = GetSourceNames(); auto sources = GetSourceNames();
@@ -201,6 +254,8 @@ MacroActionProjectorEdit::MacroActionProjectorEdit(
_monitors->setPlaceholderText( _monitors->setPlaceholderText(
obs_module_text("AdvSceneSwitcher.selectDisplay")); obs_module_text("AdvSceneSwitcher.selectDisplay"));
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_windowTypes, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_windowTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(WindowTypeChanged(int))); SLOT(WindowTypeChanged(int)));
QWidget::connect(_types, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_types, SIGNAL(currentIndexChanged(int)), this,
@@ -212,24 +267,15 @@ MacroActionProjectorEdit::MacroActionProjectorEdit(
SLOT(SourceChanged(const SourceSelection &))); SLOT(SourceChanged(const SourceSelection &)));
QWidget::connect(_monitors, SIGNAL(currentIndexChanged(int)), this, QWidget::connect(_monitors, SIGNAL(currentIndexChanged(int)), this,
SLOT(MonitorChanged(int))); SLOT(MonitorChanged(int)));
QWidget::connect(_projectorWindowName, SIGNAL(editingFinished()), this,
std::unordered_map<std::string, QWidget *> widgetPlaceholders = { SLOT(ProjectorWindowNameChanged()));
{"{{windowTypes}}", _windowTypes}, {"{{types}}", _types}, QWidget::connect(_regex,
{"{{scenes}}", _scenes}, {"{{sources}}", _sources}, SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
{"{{monitors}}", _monitors}, SLOT(RegexChanged(const RegexConfig &)));
};
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.projector.entry.monitor"),
_monitorSelection, widgetPlaceholders);
QHBoxLayout *mainLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.projector.entry"),
mainLayout, widgetPlaceholders);
mainLayout->insertLayout(mainLayout->count() - 1, _monitorSelection);
setLayout(mainLayout);
_entryData = entryData; _entryData = entryData;
SetWidgetLayout();
setLayout(_layout);
UpdateEntryData(); UpdateEntryData();
_loading = false; _loading = false;
} }
@@ -239,11 +285,15 @@ void MacroActionProjectorEdit::UpdateEntryData()
if (!_entryData) { if (!_entryData) {
return; return;
} }
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_windowTypes->setCurrentIndex(_entryData->_fullscreen ? 1 : 0); _windowTypes->setCurrentIndex(_entryData->_fullscreen ? 1 : 0);
_types->setCurrentIndex(static_cast<int>(_entryData->_type)); _types->setCurrentIndex(static_cast<int>(_entryData->_type));
_scenes->SetScene(_entryData->_scene); _scenes->SetScene(_entryData->_scene);
_sources->SetSource(_entryData->_source); _sources->SetSource(_entryData->_source);
_monitors->setCurrentIndex(_entryData->GetMonitor()); _monitors->setCurrentIndex(_entryData->GetMonitor());
_projectorWindowName->setText(_entryData->_projectorWindowName);
_regex->SetRegexConfig(_entryData->_regex);
SetWidgetVisibility(); SetWidgetVisibility();
} }
@@ -265,12 +315,26 @@ void MacroActionProjectorEdit::MonitorChanged(int value)
_entryData->SetMonitor(value); _entryData->SetMonitor(value);
} }
void MacroActionProjectorEdit::ProjectorWindowNameChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_projectorWindowName =
_projectorWindowName->text().toStdString();
}
void MacroActionProjectorEdit::RegexChanged(const RegexConfig &regex)
{
GUARD_LOADING_AND_LOCK();
_entryData->_regex = regex;
}
void MacroActionProjectorEdit::WindowTypeChanged(int) void MacroActionProjectorEdit::WindowTypeChanged(int)
{ {
GUARD_LOADING_AND_LOCK(); GUARD_LOADING_AND_LOCK();
_entryData->_fullscreen = _entryData->_fullscreen =
_windowTypes->currentText() == _windowTypes->currentText() ==
obs_module_text("AdvSceneSwitcher.action.projector.fullscreen"); obs_module_text("AdvSceneSwitcher.action.projector.fullscreen");
SetWidgetLayout();
SetWidgetVisibility(); SetWidgetVisibility();
} }
@@ -281,17 +345,69 @@ void MacroActionProjectorEdit::TypeChanged(int value)
SetWidgetVisibility(); SetWidgetVisibility();
} }
void MacroActionProjectorEdit::ActionChanged(int idx)
{
GUARD_LOADING_AND_LOCK();
_entryData->_action = static_cast<MacroActionProjector::Action>(idx);
SetWidgetLayout();
SetWidgetVisibility();
}
void MacroActionProjectorEdit::SetWidgetLayout()
{
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{actions}}", _actions},
{"{{windowTypes}}", _windowTypes},
{"{{types}}", _types},
{"{{scenes}}", _scenes},
{"{{sources}}", _sources},
{"{{monitors}}", _monitors},
{"{{projectorWindowName}}", _projectorWindowName},
{"{{regex}}", _regex},
};
for (const auto &[_, widget] : widgetPlaceholders) {
_layout->removeWidget(widget);
}
ClearLayout(_layout);
const char *layoutText;
if (_entryData->_action == MacroActionProjector::Action::CLOSE) {
layoutText = "AdvSceneSwitcher.action.projector.entry.close";
} else if (_entryData->_fullscreen) {
layoutText =
"AdvSceneSwitcher.action.projector.entry.open.fullscreen";
} else {
layoutText =
"AdvSceneSwitcher.action.projector.entry.open.windowed";
}
PlaceWidgets(obs_module_text(layoutText), _layout, widgetPlaceholders);
}
void MacroActionProjectorEdit::SetWidgetVisibility() void MacroActionProjectorEdit::SetWidgetVisibility()
{ {
if (!_entryData) { if (!_entryData) {
return; return;
} }
_scenes->setVisible(_entryData->_type == _projectorWindowName->setVisible(_entryData->_action ==
MacroActionProjector::Type::SCENE); MacroActionProjector::Action::CLOSE);
_sources->setVisible(_entryData->_type == _regex->setVisible(_entryData->_action ==
MacroActionProjector::Type::SOURCE); MacroActionProjector::Action::CLOSE);
SetLayoutVisible(_monitorSelection, _entryData->_fullscreen); _types->setVisible(_entryData->_action ==
MacroActionProjector::Action::OPEN);
_windowTypes->setVisible(_entryData->_action ==
MacroActionProjector::Action::OPEN);
_scenes->setVisible(
_entryData->_action == MacroActionProjector::Action::OPEN &&
_entryData->_type == MacroActionProjector::Type::SCENE);
_sources->setVisible(
_entryData->_action == MacroActionProjector::Action::OPEN &&
_entryData->_type == MacroActionProjector::Type::SOURCE);
_monitors->setVisible(_entryData->_action ==
MacroActionProjector::Action::OPEN &&
_entryData->_fullscreen);
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();

View File

@@ -1,7 +1,9 @@
#pragma once #pragma once
#include "macro-action-edit.hpp" #include "macro-action-edit.hpp"
#include "regex-config.hpp"
#include "scene-selection.hpp" #include "scene-selection.hpp"
#include "source-selection.hpp" #include "source-selection.hpp"
#include "variable-line-edit.hpp"
namespace advss { namespace advss {
@@ -19,6 +21,11 @@ public:
void SetMonitor(int); void SetMonitor(int);
int GetMonitor() const; int GetMonitor() const;
enum class Action {
OPEN,
CLOSE,
};
enum class Type { enum class Type {
SOURCE, SOURCE,
SCENE, SCENE,
@@ -27,10 +34,13 @@ public:
MULTIVIEW, MULTIVIEW,
}; };
Action _action = Action::OPEN;
Type _type = Type::SCENE; Type _type = Type::SCENE;
SourceSelection _source; SourceSelection _source;
SceneSelection _scene; SceneSelection _scene;
bool _fullscreen = true; bool _fullscreen = true;
StringVariable _projectorWindowName = "Windowed Projector";
RegexConfig _regex = RegexConfig::PartialMatchRegexConfig(true);
private: private:
bool MonitorSetupChanged() const; bool MonitorSetupChanged() const;
@@ -60,21 +70,28 @@ public:
} }
private slots: private slots:
void ActionChanged(int value);
void WindowTypeChanged(int value); void WindowTypeChanged(int value);
void TypeChanged(int value); void TypeChanged(int value);
void SceneChanged(const SceneSelection &); void SceneChanged(const SceneSelection &);
void SourceChanged(const SourceSelection &); void SourceChanged(const SourceSelection &);
void MonitorChanged(int value); void MonitorChanged(int value);
void ProjectorWindowNameChanged();
void RegexChanged(const RegexConfig &);
private: private:
void SetWidgetLayout();
void SetWidgetVisibility(); void SetWidgetVisibility();
QComboBox *_windowTypes; QComboBox *_actions;
QComboBox *_types; QComboBox *_types;
QComboBox *_windowTypes;
SceneSelectionWidget *_scenes; SceneSelectionWidget *_scenes;
SourceSelectionWidget *_sources; SourceSelectionWidget *_sources;
QHBoxLayout *_monitorSelection;
QComboBox *_monitors; QComboBox *_monitors;
VariableLineEdit *_projectorWindowName;
RegexConfigWidget *_regex;
QHBoxLayout *_layout;
std::shared_ptr<MacroActionProjector> _entryData; std::shared_ptr<MacroActionProjector> _entryData;
bool _loading = true; bool _loading = true;

View File

@@ -42,7 +42,7 @@ const static std::map<obs_deinterlace_mode, std::string> deinterlaceModes = {
{OBS_DEINTERLACE_MODE_DISABLE, {OBS_DEINTERLACE_MODE_DISABLE,
"AdvSceneSwitcher.action.source.deinterlaceMode.disable"}, "AdvSceneSwitcher.action.source.deinterlaceMode.disable"},
{OBS_DEINTERLACE_MODE_DISCARD, {OBS_DEINTERLACE_MODE_DISCARD,
"AdvSceneSwitcher.action.source.deinterlaceMode.disable"}, "AdvSceneSwitcher.action.source.deinterlaceMode.discard"},
{OBS_DEINTERLACE_MODE_RETRO, {OBS_DEINTERLACE_MODE_RETRO,
"AdvSceneSwitcher.action.source.deinterlaceMode.retro"}, "AdvSceneSwitcher.action.source.deinterlaceMode.retro"},
{OBS_DEINTERLACE_MODE_BLEND, {OBS_DEINTERLACE_MODE_BLEND,
@@ -526,8 +526,11 @@ void MacroActionSourceEdit::SetWidgetVisibility()
_entryData->_action == MacroActionSource::Action::SETTINGS && _entryData->_action == MacroActionSource::Action::SETTINGS &&
_entryData->_settingsInputMethod == _entryData->_settingsInputMethod ==
MacroActionSource::SettingsInputMethod::JSON_STRING); MacroActionSource::SettingsInputMethod::JSON_STRING);
_getSettings->setVisible(_entryData->_action == _getSettings->setVisible(
MacroActionSource::Action::SETTINGS); _entryData->_action == MacroActionSource::Action::SETTINGS &&
_entryData->_settingsInputMethod !=
MacroActionSource::SettingsInputMethod::
INDIVIDUAL_TEMPVAR);
_tempVars->setVisible(_entryData->_action == _tempVars->setVisible(_entryData->_action ==
MacroActionSource::Action::SETTINGS && MacroActionSource::Action::SETTINGS &&
_entryData->_settingsInputMethod == _entryData->_settingsInputMethod ==

View File

@@ -442,6 +442,13 @@ void MacroConditionFileEdit::SetWidgetVisibility()
_entryData->_onlyMatchIfChanged && _entryData->_onlyMatchIfChanged &&
_entryData->GetCondition() == _entryData->GetCondition() ==
MacroConditionFile::Condition::MATCH); MacroConditionFile::Condition::MATCH);
// TODO: Remove remote file support in future version in favor of HTTP
// action.
// Hide the option for now, if it is not used already.
_fileTypes->setVisible(_entryData->_fileType ==
MacroConditionFile::FileType::REMOTE);
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();
} }

View File

@@ -41,12 +41,12 @@ public:
StringVariable _file = obs_module_text("AdvSceneSwitcher.enterPath"); StringVariable _file = obs_module_text("AdvSceneSwitcher.enterPath");
StringVariable _text = obs_module_text("AdvSceneSwitcher.enterText"); StringVariable _text = obs_module_text("AdvSceneSwitcher.enterText");
FileType _fileType = FileType::LOCAL;
RegexConfig _regex; RegexConfig _regex;
// TODO: Remove in future version // TODO: Remove in future version
bool _useTime = false; bool _useTime = false;
bool _onlyMatchIfChanged = false; bool _onlyMatchIfChanged = false;
FileType _fileType = FileType::LOCAL;
private: private:
bool MatchFileContent(QString &filedata); bool MatchFileContent(QString &filedata);

View File

@@ -0,0 +1,77 @@
cmake_minimum_required(VERSION 3.14)
project(advanced-scene-switcher-http)
# --- Check requirements ---
get_target_property(ADVSS_SOURCE_DIR advanced-scene-switcher-lib SOURCE_DIR)
set(CPP_HTTPLIB_DIR "${ADVSS_SOURCE_DIR}/deps/cpp-httplib")
if(NOT EXISTS "${CPP_HTTPLIB_DIR}/CMakeLists.txt")
message(WARNING "cpp-httplib directory \"${CPP_HTTPLIB_DIR}\" not found!\n"
"HTTP support will be disabled!")
return()
endif()
if(NOT TARGET httplib)
add_subdirectory("${CPP_HTTPLIB_DIR}" "${CPP_HTTPLIB_DIR}/build"
EXCLUDE_FROM_ALL)
endif()
if(NOT OPENSSL_INCLUDE_DIR OR NOT OPENSSL_LIBRARIES)
find_package(OpenSSL)
if(NOT OPENSSL_FOUND)
message(WARNING "OpenSSL not found!\n" "HTTP support will be disabled!\n\n")
return()
endif()
endif()
find_package(ZLIB)
if(NOT ZLIB_FOUND)
message(WARNING "zlib not found!\n" "HTTP support will be disabled!\n\n")
return()
endif()
# --- End of section ---
add_library(${PROJECT_NAME} MODULE)
target_compile_definitions(${PROJECT_NAME} PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT=1)
if(OS_MACOS)
target_compile_definitions(
${PROJECT_NAME} PRIVATE CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN=1)
target_link_libraries(${PROJECT_NAME} PRIVATE "-framework CoreFoundation")
target_link_libraries(${PROJECT_NAME} PRIVATE "-framework Security")
endif()
target_sources(
${PROJECT_NAME} PRIVATE macro-action-http.cpp macro-action-http.hpp
key-value-list.cpp key-value-list.hpp)
setup_advss_plugin(${PROJECT_NAME})
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
target_include_directories(${PROJECT_NAME} PRIVATE "${CPP_HTTPLIB_DIR}/"
"${OPENSSL_INCLUDE_DIR}")
target_link_libraries(${PROJECT_NAME} PRIVATE ${OPENSSL_LIBRARIES} ZLIB::ZLIB)
install_advss_plugin(${PROJECT_NAME})
if(OS_WINDOWS)
# Couldn't really find a better way to install runtime dependencies for
# Windows TODO: Clean this up at some point
function(FIND_FILES_WITH_PATTERN result pattern dir)
execute_process(
COMMAND
powershell -Command
"Get-ChildItem -Path '${dir}' -Recurse -Include ${pattern} |"
"Select-Object -First 1 |"
"ForEach-Object { $_.FullName -replace '\\\\', '\\\\' }"
OUTPUT_VARIABLE files
OUTPUT_STRIP_TRAILING_WHITESPACE)
set(${result}
${files}
PARENT_SCOPE)
endfunction()
set(OPENSSL_DLL_SEARCH_DIR "${OPENSSL_INCLUDE_DIR}/..")
find_files_with_pattern(CRYPTO_DLL_FILES "libcrypto*.dll"
"${OPENSSL_DLL_SEARCH_DIR}")
find_files_with_pattern(SSL_DLL_FILES "libssl*.dll"
"${OPENSSL_DLL_SEARCH_DIR}")
install_advss_plugin_dependency(TARGET ${PROJECT_NAME} DEPENDENCIES
"${CRYPTO_DLL_FILES}" "${SSL_DLL_FILES}")
endif()

View File

@@ -0,0 +1,213 @@
#include "key-value-list.hpp"
#include "name-dialog.hpp"
#include "ui-helpers.hpp"
#include <QLayout>
#include <QTimer>
namespace advss {
KeyValueListEdit::KeyValueListEdit(QWidget *parent, const QString &addKeyString,
const QString &addKeyStringDescription,
const QString &addValueString,
const QString &addValueStringDescription)
: ListEditor(parent),
_addKeyString(addKeyString),
_addKeyStringDescription(addKeyStringDescription),
_addValueString(addValueString),
_addValueStringDescription(addValueStringDescription)
{
_list->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_list->setAutoScroll(false);
}
void KeyValueListEdit::SetStringList(const StringList &list)
{
_stringList = list;
_list->clear();
for (int i = 0; i < list.size(); i += 2) {
AppendListEntryWidget(
list.at(i), i + 1 >= list.size() ? "" : list.at(i + 1));
}
UpdateListSize();
}
void KeyValueListEdit::Add()
{
StringVariable key;
StringVariable value;
bool accepted = AskForKeyValue(key, value);
if (!accepted) {
return;
}
AppendListEntryWidget(key, value);
_stringList << key << value;
// Delay resizing to make sure the list viewport was already updated
QTimer::singleShot(0, this, [this]() { UpdateListSize(); });
StringListChanged(_stringList);
}
void KeyValueListEdit::Remove()
{
int idx = _list->currentRow();
if (idx == -1) {
return;
}
_stringList.removeAt(idx);
QListWidgetItem *item = _list->currentItem();
if (!item) {
return;
}
delete item;
// Delay resizing to make sure the list viewport was already updated
QTimer::singleShot(0, this, [this]() { UpdateListSize(); });
StringListChanged(_stringList);
}
void KeyValueListEdit::Up()
{
int idx = _list->currentRow();
if (idx <= 0 || idx >= _list->count()) {
return;
}
MoveStringListIdxUp(idx);
auto row = _list->itemWidget(_list->currentItem());
auto newItem = _list->currentItem()->clone();
_list->insertItem(idx - 1, newItem);
_list->setItemWidget(newItem, row);
_list->takeItem(idx + 1);
_list->setCurrentRow(idx - 1);
UpdateListSize();
StringListChanged(_stringList);
}
void KeyValueListEdit::Down()
{
int idx = _list->currentRow();
if (idx == -1 || idx == _list->count() - 1) {
return;
}
MoveStringListIdxUp(idx + 1);
auto row = _list->itemWidget(_list->currentItem());
auto newItem = _list->currentItem()->clone();
_list->insertItem(idx + 2, newItem);
_list->setItemWidget(newItem, row);
_list->takeItem(idx);
_list->setCurrentRow(idx + 1);
UpdateListSize();
StringListChanged(_stringList);
}
void KeyValueListEdit::Clicked(QListWidgetItem *item)
{
int idx = _list->currentRow();
StringVariable key = _stringList[idx * 2];
StringVariable value = _stringList[idx * 2 + 1];
bool accepted = AskForKeyValue(key, value);
if (!accepted) {
return;
}
auto container = static_cast<KeyValueListContainerWidget *>(
_list->itemWidget(item));
container->_key->setText(QString::fromStdString(key.UnresolvedValue()));
container->_value->setText(
QString::fromStdString(value.UnresolvedValue()));
container->adjustSize();
container->updateGeometry();
_stringList[idx * 2] = key;
_stringList[idx * 2 + 1] = value;
// Delay resizing to make sure the list viewport was already updated
QTimer::singleShot(0, this, [this]() { UpdateListSize(); });
StringListChanged(_stringList);
}
void KeyValueListEdit::MoveStringListIdxUp(int idx)
{
if (idx <= 0 || idx >= _list->count()) {
return;
}
_stringList.move(idx * 2, idx * 2 - 2);
_stringList.move(idx * 2 + 1, idx * 2 - 1);
}
bool KeyValueListEdit::AskForKeyValue(StringVariable &keyVariable,
StringVariable &valueVariable)
{
std::string key;
bool accepted = NameDialog::AskForName(
this, _addKeyString, _addKeyStringDescription, key,
QString::fromStdString(keyVariable.UnresolvedValue()), 4096,
false);
if (!accepted) {
return false;
}
std::string value;
accepted = NameDialog::AskForName(
this, _addValueString, _addValueStringDescription, value,
QString::fromStdString(valueVariable.UnresolvedValue()), 4096,
false);
if (!accepted) {
return false;
}
keyVariable = key;
valueVariable = value;
return true;
}
void KeyValueListEdit::AppendListEntryWidget(const StringVariable &key,
const StringVariable &value)
{
QListWidgetItem *item = new QListWidgetItem(_list);
auto container = new KeyValueListContainerWidget(this, _list->count());
container->_key->setText(QString::fromStdString(key.UnresolvedValue()));
container->_value->setText(
QString::fromStdString(value.UnresolvedValue()));
container->adjustSize();
container->updateGeometry();
_list->addItem(item);
_list->setItemWidget(item, container);
UpdateListSize();
}
KeyValueListContainerWidget::KeyValueListContainerWidget(QWidget *parent,
int index)
: QWidget(parent),
_key(new QLabel("Key", this)),
_value(new QLabel("Value", this)),
_index(index)
{
auto layout = new QHBoxLayout();
layout->addWidget(_key);
layout->addWidget(_value);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
}
} // namespace advss

View File

@@ -0,0 +1,53 @@
#pragma once
#include "string-list.hpp"
#include "variable-line-edit.hpp"
namespace advss {
class KeyValueListEdit final : public ListEditor {
Q_OBJECT
public:
KeyValueListEdit(QWidget *parent, const QString &addKeyString,
const QString &addKeyStringDescription,
const QString &addValueString,
const QString &addValueStringDescription);
void SetStringList(const StringList &);
private slots:
void Add();
void Remove();
void Up();
void Down();
void Clicked(QListWidgetItem *);
signals:
void StringListChanged(const StringList &);
private:
void MoveStringListIdxUp(int);
bool AskForKeyValue(StringVariable &key, StringVariable &value);
void AppendListEntryWidget(const StringVariable &key,
const StringVariable &value);
StringList _stringList;
QString _addKeyString;
QString _addKeyStringDescription;
QString _addValueString;
QString _addValueStringDescription;
};
class KeyValueListContainerWidget final : public QWidget {
Q_OBJECT
public:
KeyValueListContainerWidget(QWidget *parent, int index);
private:
QLabel *_key;
QLabel *_value;
int _index = -1;
friend class KeyValueListEdit;
};
} // namespace advss

View File

@@ -0,0 +1,463 @@
#include "macro-action-http.hpp"
#include "layout-helpers.hpp"
#include <httplib.h>
#undef DELETE
namespace advss {
const std::string MacroActionHttp::id = "http_v2";
bool MacroActionHttp::_registered = MacroActionFactory::Register(
MacroActionHttp::id,
{MacroActionHttp::Create, MacroActionHttpEdit::Create,
"AdvSceneSwitcher.action.http"});
static httplib::Headers getHeaders(const StringList &strings)
{
httplib::Headers headers;
for (int i = 0; i < strings.size(); i = i + 2) {
const auto pair =
i + 1 >= strings.size()
? std::make_pair(std::string(strings.at(i)), "")
: std::make_pair(
std::string(strings.at(i)),
std::string(strings.at(i + 1)));
headers.emplace(pair);
}
return headers;
}
static httplib::Params getParams(const StringList &strings)
{
httplib::Params params;
for (int i = 0; i < strings.size(); i = i + 2) {
const auto pair =
i + 1 >= strings.size()
? std::make_pair(std::string(strings.at(i)), "")
: std::make_pair(
std::string(strings.at(i)),
std::string(strings.at(i + 1)));
params.emplace(pair);
}
return params;
}
static void setTimeout(httplib::Client &client, const Duration &timeout)
{
const time_t seconds = timeout.Seconds();
const time_t usecs = timeout.Milliseconds() * 1000;
client.set_read_timeout(seconds, usecs);
client.set_write_timeout(seconds, usecs);
}
void MacroActionHttp::SetupTempVars()
{
MacroAction::SetupTempVars();
AddTempvar("status",
obs_module_text("AdvSceneSwitcher.tempVar.http.status"));
AddTempvar("body",
obs_module_text("AdvSceneSwitcher.tempVar.http.body"));
AddTempvar("error",
obs_module_text("AdvSceneSwitcher.tempVar.http.error"),
obs_module_text(
"AdvSceneSwitcher.tempVar.http.error.description"));
}
bool MacroActionHttp::PerformAction()
{
httplib::Client cli(_url);
setTimeout(cli, _timeout);
const auto params = _setParams ? getParams(_params) : httplib::Params();
const auto headers = _setHeaders ? getHeaders(_headers)
: httplib::Headers();
httplib::Result response;
switch (_method) {
case MacroActionHttp::Method::GET:
response = cli.Get(_path, params, headers);
break;
case MacroActionHttp::Method::POST: {
const auto path = httplib::append_query_params(_path, params);
response = cli.Post(path, headers, _body, _contentType);
break;
}
case MacroActionHttp::Method::PUT: {
const auto path = httplib::append_query_params(_path, params);
response = cli.Put(path, headers, _body, _contentType);
break;
}
case MacroActionHttp::Method::PATCH: {
const auto path = httplib::append_query_params(_path, params);
response = cli.Patch(path, headers, _body, _contentType);
break;
}
case MacroActionHttp::Method::DELETE: {
const auto path = httplib::append_query_params(_path, params);
response = cli.Delete(path, headers, _body, _contentType);
break;
}
default:
break;
}
SetTempVarValue("status",
response ? std::to_string(response->status) : "");
SetTempVarValue("body", response ? response->body : "");
SetTempVarValue("error",
response ? "" : httplib::to_string(response.error()));
return true;
}
static constexpr std::string_view methodToString(MacroActionHttp::Method method)
{
switch (method) {
case MacroActionHttp::Method::GET:
return "GET";
case MacroActionHttp::Method::POST:
return "POST";
case MacroActionHttp::Method::PUT:
return "PUT";
case MacroActionHttp::Method::PATCH:
return "PATCH";
case MacroActionHttp::Method::DELETE:
return "DELETE";
default:
break;
}
return "unknown";
}
static std::string stringListToString(const StringList &list)
{
if (list.empty()) {
return "[]";
}
std::string result = "[";
for (const auto &string : list) {
result += std::string(string) + ", ";
}
result.pop_back();
return result + "]";
}
void MacroActionHttp::LogAction() const
{
ablog(LOG_INFO,
"sent HTTP request (%s) "
"to URL \"%s\" "
"to path \"%s\" "
"with content type \"%s\" "
"with body \"%s\" "
"with headers \"%s\" "
"with parameters \"%s\" "
"with timeout \"%s\"",
methodToString(_method).data(), _url.c_str(), _path.c_str(),
_contentType.c_str(), _body.c_str(),
_setHeaders ? stringListToString(_headers).c_str() : "-",
_setParams ? stringListToString(_params).c_str() : "-",
_timeout.ToString().c_str());
}
bool MacroActionHttp::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
_url.Save(obj, "url");
_path.Save(obj, "path");
_contentType.Save(obj, "contentType");
_body.Save(obj, "body");
obs_data_set_bool(obj, "setHeaders", _setHeaders);
_headers.Save(obj, "headers", "header");
obs_data_set_bool(obj, "setParams", _setParams);
_params.Save(obj, "params", "param");
obs_data_set_int(obj, "method", static_cast<int>(_method));
_timeout.Save(obj);
return true;
}
bool MacroActionHttp::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_url.Load(obj, "url");
_path.Load(obj, "path");
_contentType.Load(obj, "contentType");
_body.Load(obj, "body");
_setHeaders = obs_data_get_bool(obj, "setHeaders");
_headers.Load(obj, "headers", "header");
_setParams = obs_data_get_bool(obj, "setParams");
_params.Load(obj, "params", "param");
_method = static_cast<Method>(obs_data_get_int(obj, "method"));
_timeout.Load(obj);
return true;
}
std::string MacroActionHttp::GetShortDesc() const
{
return _url.UnresolvedValue();
}
std::shared_ptr<MacroAction> MacroActionHttp::Create(Macro *m)
{
return std::make_shared<MacroActionHttp>(m);
}
std::shared_ptr<MacroAction> MacroActionHttp::Copy() const
{
return std::make_shared<MacroActionHttp>(*this);
}
void MacroActionHttp::ResolveVariablesToFixedValues()
{
_url.ResolveVariables();
_path.ResolveVariables();
_contentType.ResolveVariables();
_body.ResolveVariables();
_headers.ResolveVariables();
_params.ResolveVariables();
_timeout.ResolveVariables();
}
static inline void populateMethodSelection(QComboBox *list)
{
const static std::map<MacroActionHttp::Method, std::string> methods = {
{MacroActionHttp::Method::GET,
"AdvSceneSwitcher.action.http.type.get"},
{MacroActionHttp::Method::POST,
"AdvSceneSwitcher.action.http.type.post"},
{MacroActionHttp::Method::PUT,
"AdvSceneSwitcher.action.http.type.put"},
{MacroActionHttp::Method::PATCH,
"AdvSceneSwitcher.action.http.type.patch"},
{MacroActionHttp::Method::DELETE,
"AdvSceneSwitcher.action.http.type.delete"},
};
for (const auto &[value, name] : methods) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
}
MacroActionHttpEdit::MacroActionHttpEdit(
QWidget *parent, std::shared_ptr<MacroActionHttp> entryData)
: QWidget(parent),
_url(new VariableLineEdit(this)),
_path(new VariableLineEdit(this)),
_contentType(new VariableLineEdit(this)),
_contentTypeLayout(new QHBoxLayout()),
_methods(new QComboBox()),
_body(new VariableTextEdit(this)),
_bodyLayout(new QVBoxLayout()),
_setHeaders(new QCheckBox(
obs_module_text("AdvSceneSwitcher.action.http.setHeaders"))),
_headerList(new KeyValueListEdit(
this, obs_module_text("AdvSceneSwitcher.action.http.headers"),
obs_module_text("AdvSceneSwitcher.action.http.addHeader.name"),
obs_module_text("AdvSceneSwitcher.action.http.headers"),
obs_module_text(
"AdvSceneSwitcher.action.http.addHeader.value"))),
_headerListLayout(new QVBoxLayout()),
_setParams(new QCheckBox(
obs_module_text("AdvSceneSwitcher.action.http.setParams"))),
_paramList(new KeyValueListEdit(
this, obs_module_text("AdvSceneSwitcher.action.http.params"),
obs_module_text("AdvSceneSwitcher.action.http.addParam.name"),
obs_module_text("AdvSceneSwitcher.action.http.params"),
obs_module_text(
"AdvSceneSwitcher.action.http.addParam.value"))),
_paramListLayout(new QVBoxLayout()),
_timeout(new DurationSelection(this, false))
{
populateMethodSelection(_methods);
SetWidgetSignalConnections();
SetWidgetLayout();
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionHttpEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_url->setText(_entryData->_url);
_path->setText(_entryData->_path);
_contentType->setText(_entryData->_contentType);
_body->setPlainText(_entryData->_body);
_setHeaders->setChecked(_entryData->_setHeaders);
_headerList->SetStringList(_entryData->_headers);
_setParams->setChecked(_entryData->_setParams);
_paramList->SetStringList(_entryData->_params);
_methods->setCurrentIndex(
_methods->findData(static_cast<int>(_entryData->_method)));
_timeout->SetDuration(_entryData->_timeout);
SetWidgetVisibility();
}
void MacroActionHttpEdit::URLChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_url = _url->text().toStdString();
emit(HeaderInfoChanged(_url->text()));
}
void MacroActionHttpEdit::PathChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_path = _path->text().toStdString();
}
void MacroActionHttpEdit::ContentTypeChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_contentType = _contentType->text().toStdString();
}
void MacroActionHttpEdit::BodyChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_body = _body->toPlainText().toUtf8().constData();
adjustSize();
updateGeometry();
}
void MacroActionHttpEdit::MethodChanged(int idx)
{
GUARD_LOADING_AND_LOCK();
_entryData->_method = static_cast<MacroActionHttp::Method>(
_methods->itemData(idx).toInt());
SetWidgetVisibility();
}
void MacroActionHttpEdit::TimeoutChanged(const Duration &dur)
{
GUARD_LOADING_AND_LOCK();
_entryData->_timeout = dur;
}
void MacroActionHttpEdit::SetHeadersChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_setHeaders = value;
SetWidgetVisibility();
}
void MacroActionHttpEdit::HeadersChanged(const StringList &headers)
{
GUARD_LOADING_AND_LOCK();
_entryData->_headers = headers;
adjustSize();
updateGeometry();
}
void MacroActionHttpEdit::SetParamsChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_setParams = value;
SetWidgetVisibility();
}
void MacroActionHttpEdit::ParamsChanged(const StringList &params)
{
GUARD_LOADING_AND_LOCK();
_entryData->_params = params;
adjustSize();
updateGeometry();
}
void MacroActionHttpEdit::SetWidgetSignalConnections()
{
QWidget::connect(_url, SIGNAL(editingFinished()), this,
SLOT(URLChanged()));
QWidget::connect(_path, SIGNAL(editingFinished()), this,
SLOT(PathChanged()));
QWidget::connect(_contentType, SIGNAL(editingFinished()), this,
SLOT(ContentTypeChanged()));
QWidget::connect(_body, SIGNAL(textChanged()), this,
SLOT(BodyChanged()));
QWidget::connect(_methods, SIGNAL(currentIndexChanged(int)), this,
SLOT(MethodChanged(int)));
QWidget::connect(_setHeaders, SIGNAL(stateChanged(int)), this,
SLOT(SetHeadersChanged(int)));
QWidget::connect(_headerList,
SIGNAL(StringListChanged(const StringList &)), this,
SLOT(HeadersChanged(const StringList &)));
QWidget::connect(_setParams, SIGNAL(stateChanged(int)), this,
SLOT(SetParamsChanged(int)));
QWidget::connect(_paramList,
SIGNAL(StringListChanged(const StringList &)), this,
SLOT(ParamsChanged(const StringList &)));
QWidget::connect(_timeout, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(TimeoutChanged(const Duration &)));
}
void MacroActionHttpEdit::SetWidgetLayout()
{
const std::unordered_map<std::string, QWidget *> widgets = {
{"{{url}}", _url},
{"{{path}}", _path},
{"{{contentType}}", _contentType},
{"{{method}}", _methods},
{"{{body}}", _body},
{"{{timeout}}", _timeout},
};
auto actionLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.action.http.layout.method"),
actionLayout, widgets);
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.http.layout.contentType"),
_contentTypeLayout, widgets);
_bodyLayout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.action.http.body")));
_bodyLayout->addWidget(_body);
auto timeoutLayout = new QHBoxLayout;
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.action.http.layout.timeout"),
timeoutLayout, widgets);
_headerListLayout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.action.http.headers")));
_headerListLayout->addWidget(_headerList);
_paramListLayout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.action.http.params")));
_paramListLayout->addWidget(_paramList);
auto layout = new QVBoxLayout;
layout->addLayout(actionLayout);
layout->addWidget(_setHeaders);
layout->addLayout(_headerListLayout);
layout->addWidget(_setParams);
layout->addLayout(_paramListLayout);
layout->addLayout(_contentTypeLayout);
layout->addLayout(_bodyLayout);
layout->addLayout(timeoutLayout);
setLayout(layout);
}
void MacroActionHttpEdit::SetWidgetVisibility()
{
SetLayoutVisible(_headerListLayout, _entryData->_setHeaders);
SetLayoutVisible(_paramListLayout, _entryData->_setParams);
SetLayoutVisible(_contentTypeLayout,
_entryData->_method != MacroActionHttp::Method::GET);
SetLayoutVisible(_bodyLayout,
_entryData->_method != MacroActionHttp::Method::GET);
adjustSize();
updateGeometry();
}
} // namespace advss

View File

@@ -0,0 +1,107 @@
#pragma once
#include "macro-action-edit.hpp"
#include "key-value-list.hpp"
#include "variable-text-edit.hpp"
#include "variable-line-edit.hpp"
#include "duration-control.hpp"
#include <QLineEdit>
#include <QComboBox>
#include <QCheckBox>
namespace advss {
class MacroActionHttp final : public MacroAction {
public:
MacroActionHttp(Macro *m) : MacroAction(m, true) {}
bool PerformAction();
void LogAction() const;
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<MacroAction> Create(Macro *m);
std::shared_ptr<MacroAction> Copy() const;
void ResolveVariablesToFixedValues();
enum class Method {
GET = 0,
POST,
PUT,
PATCH,
DELETE,
};
StringVariable _url = "127.0.0.1:8080";
StringVariable _path = "/";
StringVariable _body = obs_module_text("AdvSceneSwitcher.enterText");
StringVariable _contentType = "application/json";
bool _setHeaders = false;
StringList _headers;
bool _setParams = false;
StringList _params;
Method _method = Method::GET;
Duration _timeout = Duration(1.0);
private:
void SetupTempVars();
static bool _registered;
static const std::string id;
};
class MacroActionHttpEdit final : public QWidget {
Q_OBJECT
public:
MacroActionHttpEdit(
QWidget *parent,
std::shared_ptr<MacroActionHttp> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionHttpEdit(
parent,
std::dynamic_pointer_cast<MacroActionHttp>(action));
}
private slots:
void URLChanged();
void PathChanged();
void BodyChanged();
void ContentTypeChanged();
void MethodChanged(int);
void TimeoutChanged(const Duration &seconds);
void SetHeadersChanged(int);
void HeadersChanged(const StringList &);
void SetParamsChanged(int);
void ParamsChanged(const StringList &);
signals:
void HeaderInfoChanged(const QString &);
private:
void SetWidgetSignalConnections();
void SetWidgetLayout();
void SetWidgetVisibility();
VariableLineEdit *_url;
VariableLineEdit *_path;
VariableLineEdit *_contentType;
QHBoxLayout *_contentTypeLayout;
QComboBox *_methods;
VariableTextEdit *_body;
QVBoxLayout *_bodyLayout;
QCheckBox *_setHeaders;
KeyValueListEdit *_headerList;
QVBoxLayout *_headerListLayout;
QCheckBox *_setParams;
KeyValueListEdit *_paramList;
QVBoxLayout *_paramListLayout;
DurationSelection *_timeout;
std::shared_ptr<MacroActionHttp> _entryData;
bool _loading = true;
};
} // namespace advss

View File

@@ -19,9 +19,22 @@ add_library(${PROJECT_NAME} MODULE)
if(OS_WINDOWS) if(OS_WINDOWS)
if(MSVC) if(MSVC)
target_compile_options(libremidi PRIVATE /wd4251 /wd4267 /wd4275 /wd4101 target_compile_options(
/wd4244 /wd4018) libremidi
target_compile_options(${PROJECT_NAME} PRIVATE /wd4101 /wd4244 /wd4018) PRIVATE /wd4018
/wd4068
/wd4100
/wd4101
/wd4189
/wd4244
/wd4251
/wd4267
/wd4275
/wd4389
/wd4505
/wd4702)
target_compile_options(${PROJECT_NAME} PRIVATE /wd4017 /wd4068 /wd4100
/wd4101 /wd4244)
endif() endif()
else() else()
target_compile_options( target_compile_options(

View File

@@ -10,8 +10,10 @@ if(NOT EXISTS "${CPP_HTTPLIB_DIR}/CMakeLists.txt")
"Twitch support will be disabled!") "Twitch support will be disabled!")
return() return()
endif() endif()
add_subdirectory("${CPP_HTTPLIB_DIR}" "${CPP_HTTPLIB_DIR}/build" if(NOT TARGET httplib)
EXCLUDE_FROM_ALL) add_subdirectory("${CPP_HTTPLIB_DIR}" "${CPP_HTTPLIB_DIR}/build"
EXCLUDE_FROM_ALL)
endif()
if(NOT OPENSSL_INCLUDE_DIR OR NOT OPENSSL_LIBRARIES) if(NOT OPENSSL_INCLUDE_DIR OR NOT OPENSSL_LIBRARIES)
find_package(OpenSSL) find_package(OpenSSL)

View File

@@ -826,7 +826,7 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
_userInfoQueryType(new QComboBox(this)), _userInfoQueryType(new QComboBox(this)),
_userLogin(new VariableLineEdit(this)), _userLogin(new VariableLineEdit(this)),
_userId(new VariableSpinBox(this)), _userId(new VariableSpinBox(this)),
_pointsReward(new TwitchPointsRewardWidget(this)), _pointsReward(new TwitchPointsRewardWidget(this, false)),
_rewardVariable(new VariableSelection(this)), _rewardVariable(new VariableSelection(this)),
_toggleRewardSelection(new QPushButton()) _toggleRewardSelection(new QPushButton())
{ {

View File

@@ -1450,7 +1450,7 @@ MacroConditionTwitchEdit::MacroConditionTwitchEdit(
_tokens(new TwitchConnectionSelection()), _tokens(new TwitchConnectionSelection()),
_tokenWarning(new QLabel()), _tokenWarning(new QLabel()),
_channel(new TwitchChannelSelection(this)), _channel(new TwitchChannelSelection(this)),
_pointsReward(new TwitchPointsRewardWidget(this)), _pointsReward(new TwitchPointsRewardWidget(this, true)),
_streamTitle(new VariableLineEdit(this)), _streamTitle(new VariableLineEdit(this)),
_regexTitle(new RegexConfigWidget(parent)), _regexTitle(new RegexConfigWidget(parent)),
_chatMesageEdit(new ChatMessageEdit(this)), _chatMesageEdit(new ChatMessageEdit(this)),

View File

@@ -135,9 +135,10 @@ void TwitchPointsRewardSelection::SelectionChanged(int index)
emit PointsRewardChanged(pointsReward); emit PointsRewardChanged(pointsReward);
} }
TwitchPointsRewardWidget::TwitchPointsRewardWidget(QWidget *parent) TwitchPointsRewardWidget::TwitchPointsRewardWidget(QWidget *parent,
bool allowAny)
: QWidget(parent), : QWidget(parent),
_selection(new TwitchPointsRewardSelection(this)), _selection(new TwitchPointsRewardSelection(this, allowAny)),
_refreshButton(new QPushButton(this)) _refreshButton(new QPushButton(this))
{ {
_refreshButton->setMaximumWidth(22); _refreshButton->setMaximumWidth(22);

View File

@@ -20,7 +20,7 @@ class TwitchPointsRewardSelection : public FilterComboBox {
Q_OBJECT Q_OBJECT
public: public:
TwitchPointsRewardSelection(QWidget *parent, bool allowAny = true); TwitchPointsRewardSelection(QWidget *parent, bool allowAny);
void SetPointsReward(const TwitchPointsReward &pointsReward); void SetPointsReward(const TwitchPointsReward &pointsReward);
void SetChannel(const TwitchChannel &channel); void SetChannel(const TwitchChannel &channel);
@@ -53,7 +53,7 @@ class TwitchPointsRewardWidget : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
TwitchPointsRewardWidget(QWidget *parent); TwitchPointsRewardWidget(QWidget *parent, bool allowAny);
void SetPointsReward(const TwitchPointsReward &pointsReward); void SetPointsReward(const TwitchPointsReward &pointsReward);
void SetChannel(const TwitchChannel &channel); void SetChannel(const TwitchChannel &channel);

View File

@@ -29,17 +29,23 @@ public:
_headers(headers) _headers(headers)
{ {
} }
bool operator<(const Args &other) const bool operator==(const Args &other) const
{ {
bool ret = true; bool ret = true;
ret = ret && _uri < other._uri; ret = ret && _uri == other._uri;
ret = ret && _path < other._path; ret = ret && _path == other._path;
ret = ret && _params < other._params; ret = ret && _params == other._params;
ret = ret && _data < other._data; ret = ret && _data == other._data;
ret = ret && _headers < other._headers; ret = ret && _headers == other._headers;
return ret; return ret;
} }
const std::string &uri() const { return _uri; }
const std::string &path() const { return _path; }
const std::string &data() const { return _data; }
const httplib::Params &params() const { return _params; }
const httplib::Headers &headers() const { return _headers; }
private: private:
std::string _uri; std::string _uri;
std::string _path; std::string _path;
@@ -48,6 +54,37 @@ private:
httplib::Headers _headers; httplib::Headers _headers;
}; };
}; // namespace advss
template<> struct std::hash<advss::Args> {
inline std::size_t operator()(const advss::Args &args) const
{
static constexpr auto hash_combine = [](std::size_t &seed,
std::size_t hashValue) {
seed ^= hashValue + 0x9e3779b9 + (seed << 6) +
(seed >> 2);
};
std::size_t seed = 0;
hash_combine(seed, std::hash<std::string>()(args.uri()));
hash_combine(seed, std::hash<std::string>()(args.path()));
hash_combine(seed, std::hash<std::string>()(args.data()));
for (const auto &[key, value] : args.params()) {
hash_combine(seed, std::hash<std::string>()(key));
hash_combine(seed, std::hash<std::string>()(value));
}
for (const auto &[key, value] : args.headers()) {
hash_combine(seed, std::hash<std::string>()(key));
hash_combine(seed, std::hash<std::string>()(value));
}
return seed;
}
};
namespace advss {
struct CacheEntry { struct CacheEntry {
RequestResult result; RequestResult result;
std::chrono::system_clock::time_point cacheTime = std::chrono::system_clock::time_point cacheTime =
@@ -61,14 +98,15 @@ static bool cacheIsTooOld(const CacheEntry &cache)
return diff >= std::chrono::seconds(cacheTimeoutSeconds); return diff >= std::chrono::seconds(cacheTimeoutSeconds);
} }
static bool cacheIsValid(const std::map<Args, CacheEntry> &cache, static bool cacheIsValid(const std::unordered_map<Args, CacheEntry> &cache,
const Args &args) const Args &args)
{ {
auto it = cache.find(args); auto it = cache.find(args);
return it != cache.end() && !cacheIsTooOld(it->second); return it != cache.end() && !cacheIsTooOld(it->second);
} }
static void cleanupCache(std::map<Args, CacheEntry> &cache, std::mutex &mtx) static void cleanupCache(std::unordered_map<Args, CacheEntry> &cache,
std::mutex &mtx)
{ {
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
cache.clear(); cache.clear();
@@ -181,7 +219,7 @@ RequestResult SendGetRequest(const TwitchToken &token, const std::string &uri,
return {}; return {};
} }
static std::map<Args, CacheEntry> cache; static std::unordered_map<Args, CacheEntry> cache;
static std::mutex mtx; static std::mutex mtx;
[[maybe_unused]] static bool _ = []() { [[maybe_unused]] static bool _ = []() {
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); }); AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });
@@ -251,7 +289,7 @@ RequestResult SendPostRequest(const TwitchToken &token, const std::string &uri,
return {}; return {};
} }
static std::map<Args, CacheEntry> cache; static std::unordered_map<Args, CacheEntry> cache;
static std::mutex mtx; static std::mutex mtx;
[[maybe_unused]] static bool _ = []() { [[maybe_unused]] static bool _ = []() {
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); }); AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });
@@ -322,7 +360,7 @@ RequestResult SendPutRequest(const TwitchToken &token, const std::string &uri,
return {}; return {};
} }
static std::map<Args, CacheEntry> cache; static std::unordered_map<Args, CacheEntry> cache;
static std::mutex mtx; static std::mutex mtx;
[[maybe_unused]] static bool _ = []() { [[maybe_unused]] static bool _ = []() {
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); }); AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });
@@ -393,7 +431,7 @@ RequestResult SendPatchRequest(const TwitchToken &token, const std::string &uri,
return {}; return {};
} }
static std::map<Args, CacheEntry> cache; static std::unordered_map<Args, CacheEntry> cache;
static std::mutex mtx; static std::mutex mtx;
[[maybe_unused]] static bool _ = []() { [[maybe_unused]] static bool _ = []() {
AddPluginCleanupStep([]() { cleanupCache(cache, mtx); }); AddPluginCleanupStep([]() { cleanupCache(cache, mtx); });

View File

@@ -148,6 +148,39 @@ def script_load(settings):
], ],
) )
# Other signals and procedures, which might be useful
def plugin_stop_handler(data):
obs.script_log(obs.LOG_INFO, "Hello from stop handler!")
get_running_status()
def plugin_start_handler(data):
obs.script_log(obs.LOG_INFO, "Hello from start handler!")
get_running_status()
def get_running_status():
proc_handler = obs.obs_get_proc_handler()
data = obs.calldata_create()
obs.proc_handler_call(proc_handler, "advss_plugin_running", data)
success = obs.calldata_bool(data, "is_running")
obs.script_log(
obs.LOG_INFO, f"The advanced scene switcher is currently running: {success}"
)
obs.calldata_destroy(data)
def interval_reset_handler(data):
obs.script_log(obs.LOG_INFO, "Hello from reset handler!")
signal_handler = obs.obs_get_signal_handler()
obs.signal_handler_connect(
signal_handler, "advss_plugin_stopped", plugin_stop_handler
)
obs.signal_handler_connect(
signal_handler, "advss_plugin_started", plugin_start_handler
)
obs.signal_handler_connect(
signal_handler, "advss_interval_reset", interval_reset_handler
)
def script_unload(): def script_unload():
# Deregistering is useful if you plan on reloading the script files # Deregistering is useful if you plan on reloading the script files