Compare commits

...

13 Commits

Author SHA1 Message Date
WarmUpTill
e1020a1909 Fix poad load steps being executed too frequently
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-10-30 20:48:33 +01:00
WarmUpTill
8b0bd4193b Fix temp var save / loading not working 2025-10-30 20:48:33 +01:00
WarmUpTill
d55bb6bc86 Fall back to obs_frontend_get_current_scene()
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
If there wasn't any scene change yet GetCurrentScene() would always
return nullptr and break various scene checks.
For example, this could happen when startup up a fresh OBS install for
the first time.
2025-10-29 12:26:44 +01:00
WarmUpTill
0583331bfd Fix scene selection not working without secondary canvases 2025-10-29 12:26:44 +01:00
WarmUpTill
6932de866d Refactor Twitch event server migration and reconnect handling
Some checks failed
debian-build / build (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
Check locale / ubuntu64 (push) Has been cancelled
This should avoid any events being lost due to server migration.
2025-10-28 19:20:52 +01:00
WarmUpTill
00db0cf7c4 CI: Refactor OpenSSL handling on MacOS to support MQTT SSL 2025-10-28 19:20:52 +01:00
WarmUpTill
e9baf27ca2 Add helper to find recent versions of OpenSSL on Windows 2025-10-28 19:20:52 +01:00
WarmUpTill
b1a5db0c9c Fix crash when switching macros after deleting one containing temp refs 2025-10-28 19:20:52 +01:00
WarmUpTill
8f3b868fd9 Limit projector action to main canvas and improve layout 2025-10-28 19:20:52 +01:00
WarmUpTill
b3bf89840b Add GetPath() 2025-10-28 19:20:52 +01:00
WarmUpTill
84f7d0d214 Add SSL support to MQTT connections
Also fixes crash on startup if SSL was used while there was no support
for encrypted connections yet
2025-10-28 19:20:52 +01:00
WarmUpTill
e1164c4fa3 Refactor help icon usage 2025-10-28 19:20:52 +01:00
WarmUpTill
4534b23bad Enable Windows and MacOS build with OBS versions older than 31.1.1 2025-10-28 19:20:52 +01:00
37 changed files with 651 additions and 217 deletions

View File

@@ -22,7 +22,7 @@ runs:
- name: Setup cmake - name: Setup cmake
uses: jwlawson/actions-setup-cmake@v1.13 uses: jwlawson/actions-setup-cmake@v1.13
with: with:
cmake-version: '3.28.x' cmake-version: '3.x.x'
- name: Restore cached dependencies - name: Restore cached dependencies
id: restore-cache id: restore-cache

View File

@@ -394,8 +394,11 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
popd popd
pushd ${advss_dep_path}
log_info "Prepare openssl ..." log_info "Prepare openssl ..."
rm -rf ${advss_dep_path}/openssl ${advss_dep_path}/openssl_build
mkdir ${advss_dep_path}/openssl_build
pushd ${advss_dep_path}/openssl_build
rm -rf openssl rm -rf openssl
git clone https://github.com/openssl/openssl.git --branch openssl-3.1.2 --depth 1 git clone https://github.com/openssl/openssl.git --branch openssl-3.1.2 --depth 1
mv openssl openssl_x86 mv openssl openssl_x86
@@ -403,25 +406,27 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
log_info "Building openssl x86 ..." log_info "Building openssl x86 ..."
export MACOSX_DEPLOYMENT_TARGET=10.9 export MACOSX_DEPLOYMENT_TARGET=10.9
cd openssl_x86 pushd openssl_x86
./Configure darwin64-x86_64-cc shared ./Configure darwin64-x86_64-cc no-shared no-module no-zlib --prefix=${advss_dep_path}
make make -j$(nproc)
popd
log_info "Building openssl arm ..." log_info "Building openssl arm ..."
export MACOSX_DEPLOYMENT_TARGET=10.15 export MACOSX_DEPLOYMENT_TARGET=10.15
cd ../openssl_arm pushd openssl_arm
./Configure enable-rc5 zlib darwin64-arm64-cc no-asm ./Configure enable-rc5 darwin64-arm64-cc no-shared no-module no-asm no-zlib --prefix=${advss_dep_path}
make make -j$(nproc)
log_info "Install openssl ..."
make install
popd
log_info "Combine arm and x86 openssl binaries ..." log_info "Combine arm and x86 openssl binaries ..."
cd .. lipo -create openssl_x86/libcrypto.a openssl_arm/libcrypto.a -output ${advss_dep_path}/lib/libcrypto.a
mkdir openssl-combined lipo -create openssl_x86/libssl.a openssl_arm/libssl.a -output ${advss_dep_path}/lib/libssl.a
lipo -create openssl_x86/libcrypto.a openssl_arm/libcrypto.a -output openssl-combined/libcrypto.a
lipo -create openssl_x86/libssl.a openssl_arm/libssl.a -output openssl-combined/libssl.a
log_info "Clean up openssl dir ..." log_info "Clean up openssl dir ..."
mv openssl_x86 openssl rm -rf openssl_x86 openssl_arm
rm -rf openssl_arm
popd popd
pushd ${project_root}/deps/libusb pushd ${project_root}/deps/libusb
@@ -439,14 +444,16 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
mkdir ${project_root}/deps/libusb/out_x86 mkdir ${project_root}/deps/libusb/out_x86
./autogen.sh ./autogen.sh
./configure --host=x86_64-apple-darwin --prefix=${advss_dep_path} ./configure --host=x86_64-apple-darwin --prefix=${advss_dep_path}
make && make install make -j$(nproc)
make install
log_info "Configure libusb arm ..." log_info "Configure libusb arm ..."
make clean make clean
rm -r ${project_root}/deps/libusb/out_x86 rm -r ${project_root}/deps/libusb/out_x86
mkdir ${project_root}/deps/libusb/out_x86 mkdir ${project_root}/deps/libusb/out_x86
./configure --host=aarch64-apple-darwin --prefix=${project_root}/deps/libusb/out_x86 ./configure --host=aarch64-apple-darwin --prefix=${project_root}/deps/libusb/out_x86
make && make install make -j$(nproc)
make install
log_info "Building libusb arm ..." log_info "Building libusb arm ..."
make clean make clean
@@ -459,7 +466,8 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
export MACOSX_DEPLOYMENT_TARGET=10.15 export MACOSX_DEPLOYMENT_TARGET=10.15
mkdir ${project_root}/deps/libusb/out_arm mkdir ${project_root}/deps/libusb/out_arm
./configure --host=aarch64-apple-darwin --prefix=${project_root}/deps/libusb/out_arm ./configure --host=aarch64-apple-darwin --prefix=${project_root}/deps/libusb/out_arm
make && make install make -j$(nproc)
make install
log_info "Combine arm and x86 libusb binaries ..." log_info "Combine arm and x86 libusb binaries ..."
lipo -create ${project_root}/deps/libusb/out_x86/lib/libusb-1.0.0.dylib \ lipo -create ${project_root}/deps/libusb/out_x86/lib/libusb-1.0.0.dylib \
@@ -491,8 +499,8 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
-DPAHO_BUILD_SHARED=OFF -DPAHO_BUILD_SHARED=OFF
-DPAHO_BUILD_STATIC=ON -DPAHO_BUILD_STATIC=ON
-DPAHO_WITH_MQTT_C=ON -DPAHO_WITH_MQTT_C=ON
-DOPENSSL_ROOT_DIR="${advss_dep_path}" -DPAHO_WITH_SSL=ON
-DPAHO_WITH_SSL=OFF # TODO: figure out linking issues with openssl -DOPENSSL_USE_STATIC_LIBS=ON
) )
pushd ${mqtt_dir} pushd ${mqtt_dir}

View File

@@ -253,12 +253,8 @@ ${_usage_host:-}"
macos-*) macos-*)
if (( ${+CI} )) typeset -gx NSUnbufferedIO=YES if (( ${+CI} )) typeset -gx NSUnbufferedIO=YES
local openssl_lib_dir="${advss_deps_path}/openssl-combined/"
local openssl_include_dir="${advss_deps_path}/openssl/include"
cmake_args+=( cmake_args+=(
-DOPENSSL_INCLUDE_DIR="${openssl_include_dir}" -DCMAKE_PREFIX_PATH="${advss_deps_path}"
-DOPENSSL_LIBRARIES="${openssl_lib_dir}/libcrypto.a;${openssl_lib_dir}/libssl.a"
--preset ${_preset} --preset ${_preset}
) )

View File

@@ -234,8 +234,24 @@ function Build {
"-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}" "-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}"
"-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}" "-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}"
"-DPAHO_WITH_MQTT_C=ON" "-DPAHO_WITH_MQTT_C=ON"
"-DPAHO_WITH_SSL=ON"
) )
# Try to find OpenSSL installed via winget
$pf64 = Join-Path $Env:ProgramFiles "OpenSSL-Win64"
$pf = Join-Path $Env:ProgramFiles "OpenSSL"
$possibleDirs = @($pf64, $pf)
$opensslDir = $possibleDirs | Where-Object { Test-Path (Join-Path $_ "include\openssl\ssl.h") } | Select-Object -First 1
if ($opensslDir) {
Write-Host "Detected OpenSSL at: $opensslDir"
$MqttCmakeArgs += "-DOPENSSL_ROOT_DIR=$opensslDir"
$MqttCmakeArgs += "-DOPENSSL_CRYPTO_LIBRARY=$opensslDir\lib\VC\x64\MD\libcrypto.lib"
$MqttCmakeArgs += "-DOPENSSL_SSL_LIBRARY=$opensslDir\lib\VC\x64\MD\libssl.lib"
} else {
Write-Warning "OpenSSL not found - maybe cmake will find it ..."
}
Log-Information "Configuring paho.mqtt.cpp..." Log-Information "Configuring paho.mqtt.cpp..."
Invoke-External cmake -S ${MqttPath} -B ${MqttBuildPath} @MqttCmakeArgs Invoke-External cmake -S ${MqttPath} -B ${MqttBuildPath} @MqttCmakeArgs

View File

@@ -18,5 +18,7 @@ if (( ! ${+commands[brew]} )) {
} }
brew bundle --file ${SCRIPT_HOME}/.Brewfile brew bundle --file ${SCRIPT_HOME}/.Brewfile
rehash # Workaround to make sure locally built openssl is picked up by cmake
brew uninstall --ignore-dependencies openssl@3 || true
rehash || true
log_group log_group

View File

@@ -6,7 +6,7 @@ on:
description: "Project name detected by parsing build spec file" description: "Project name detected by parsing build spec file"
value: ${{ jobs.check-event.outputs.pluginName }} value: ${{ jobs.check-event.outputs.pluginName }}
env: env:
DEP_DIR: .deps/advss-build-dependencies-2 DEP_DIR: .deps/advss-build-dependencies-3
jobs: jobs:
check-event: check-event:
name: Check GitHub Event Data 🔎 name: Check GitHub Event Data 🔎
@@ -213,7 +213,7 @@ jobs:
- name: Set up CMake 🏗️ - name: Set up CMake 🏗️
uses: jwlawson/actions-setup-cmake@v1.13 uses: jwlawson/actions-setup-cmake@v1.13
with: with:
cmake-version: '3.24.x' cmake-version: '3.x.x'
- name: Set up Homebrew 🍺 - name: Set up Homebrew 🍺
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@master

View File

@@ -34,6 +34,11 @@ include(cmake/common/get_git_revision_description.cmake)
get_git_head_revision(GIT_REFSPEC GIT_SHA1) get_git_head_revision(GIT_REFSPEC GIT_SHA1)
git_describe(GIT_TAG) git_describe(GIT_TAG)
# Helper for OpenSSL
if(OS_WINDOWS)
include(cmake/windows/wingetssl.cmake)
endif()
if(${GIT_TAG} STREQUAL "GIT-NOTFOUND") if(${GIT_TAG} STREQUAL "GIT-NOTFOUND")
set(GIT_TAG ${PROJECT_VERSION}) set(GIT_TAG ${PROJECT_VERSION})
endif() endif()

View File

@@ -0,0 +1,78 @@
# ---------------------------------------------------------------------------
# Detects OpenSSL installed via winget or other common Windows locations,
# without requiring the user to manually set OPENSSL_ROOT_DIR.
# ---------------------------------------------------------------------------
if(WIN32 AND (NOT OpenSSL_FOUND))
set(_openssl_roots
"$ENV{ProgramFiles}/OpenSSL-Win64" "$ENV{ProgramFiles}/OpenSSL"
"$ENV{ProgramW6432}/OpenSSL-Win64")
set(_openssl_lib_suffixes "lib/VC/x64/MD" "lib/VC/x64/MDd" "lib/VC/x64/MT"
"lib/VC/x64/MTd" "lib")
# Determine which configuration we're building
if(CMAKE_BUILD_TYPE MATCHES "Debug")
set(_is_debug TRUE)
else()
set(_is_debug FALSE)
endif()
# Determine which runtime we use Default to /MD (shared CRT)
set(_crt_kind "MD")
if(MSVC)
if(CMAKE_MSVC_RUNTIME_LIBRARY MATCHES "MultiThreaded")
if(CMAKE_MSVC_RUNTIME_LIBRARY MATCHES "Debug")
set(_crt_kind "MTd")
else()
set(_crt_kind "MT")
endif()
else()
if(_is_debug)
set(_crt_kind "MDd")
else()
set(_crt_kind "MD")
endif()
endif()
endif()
message(STATUS "Looking for OpenSSL built with CRT variant: ${_crt_kind}")
# Try to find the root and corresponding lib path
foreach(_root ${_openssl_roots})
if(EXISTS "${_root}/include/openssl/ssl.h")
foreach(_suffix ${_openssl_lib_suffixes})
if(_suffix MATCHES "${_crt_kind}$"
AND EXISTS "${_root}/${_suffix}/libcrypto.lib")
set(OPENSSL_ROOT_DIR
"${_root}"
CACHE PATH "Path to OpenSSL root")
set(OPENSSL_CRYPTO_LIBRARY
"${_root}/${_suffix}/libcrypto.lib"
CACHE FILEPATH "OpenSSL crypto lib")
set(OPENSSL_SSL_LIBRARY
"${_root}/${_suffix}/libssl.lib"
CACHE FILEPATH "OpenSSL ssl lib")
set(OPENSSL_INCLUDE_DIR
"${_root}/include"
CACHE PATH "OpenSSL include dir")
set(OpenSSL_FOUND
TRUE
CACHE BOOL "Whether OpenSSL was found")
message(STATUS "Found OpenSSL at: ${_root}/${_suffix}")
return()
endif()
endforeach()
endif()
if(OpenSSL_FOUND)
break()
endif()
endforeach()
if(NOT OpenSSL_FOUND)
message(WARNING "Could not auto-detect OpenSSL under Program Files. "
"Might have to set OPENSSL_ROOT_DIR manually.")
endif()
endif()

View File

@@ -1514,6 +1514,13 @@ AdvSceneSwitcher.mqttConnection.name="Name:"
AdvSceneSwitcher.mqttConnection.address="Address:" AdvSceneSwitcher.mqttConnection.address="Address:"
AdvSceneSwitcher.mqttConnection.username="Username:" AdvSceneSwitcher.mqttConnection.username="Username:"
AdvSceneSwitcher.mqttConnection.password="Password:" AdvSceneSwitcher.mqttConnection.password="Password:"
AdvSceneSwitcher.mqttConnection.trustStore="Trust store:"
AdvSceneSwitcher.mqttConnection.trustStore.help="The file in PEM format containing the public digital certificates trusted by the client."
AdvSceneSwitcher.mqttConnection.keyStore="Key store:"
AdvSceneSwitcher.mqttConnection.keyStore.help="The file in PEM format containing the public certificate chain of the client.\nIt may also include the client's private key."
AdvSceneSwitcher.mqttConnection.privateKey="Private key:"
AdvSceneSwitcher.mqttConnection.privateKey.help="If not included in the key store, this is the file in PEM format containing the client's private key."
AdvSceneSwitcher.mqttConnection.verifyServerCert="Verify server certificate"
AdvSceneSwitcher.mqttConnection.reconnect="Reconnect automatically:" AdvSceneSwitcher.mqttConnection.reconnect="Reconnect automatically:"
AdvSceneSwitcher.mqttConnection.reconnectDelay="Automatically reconnect after:" AdvSceneSwitcher.mqttConnection.reconnectDelay="Automatically reconnect after:"
AdvSceneSwitcher.mqttConnection.connectOnStart="Connect on startup:" AdvSceneSwitcher.mqttConnection.connectOnStart="Connect on startup:"

View File

@@ -459,7 +459,7 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
LoadHotkeys(obj); LoadHotkeys(obj);
LoadUISettings(obj); LoadUISettings(obj);
RunPostLoadSteps(); RunAndClearPostLoadSteps();
// Reset on startup and scene collection change // Reset on startup and scene collection change
ResetLastOpenedTab(); ResetLastOpenedTab();

View File

@@ -117,7 +117,7 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
*_entryData = MacroActionFactory::Create(id, macro); *_entryData = MacroActionFactory::Create(id, macro);
(*_entryData)->SetIndex(idx); (*_entryData)->SetIndex(idx);
(*_entryData)->PostLoad(); (*_entryData)->PostLoad();
RunPostLoadSteps(); RunAndClearPostLoadSteps();
} }
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData); auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)), QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),

View File

@@ -1,4 +1,5 @@
#include "macro-action-variable.hpp" #include "macro-action-variable.hpp"
#include "help-icon.hpp"
#include "json-helpers.hpp" #include "json-helpers.hpp"
#include "layout-helpers.hpp" #include "layout-helpers.hpp"
#include "macro-condition-edit.hpp" #include "macro-condition-edit.hpp"
@@ -828,7 +829,10 @@ MacroActionVariableEdit::MacroActionVariableEdit(
this)), this)),
_randomLayout(new QVBoxLayout()), _randomLayout(new QVBoxLayout()),
_jsonQuery(new VariableLineEdit(this)), _jsonQuery(new VariableLineEdit(this)),
_jsonQueryHelp(new QLabel(this)), _jsonQueryHelp(new HelpIcon(
obs_module_text(
"AdvSceneSwitcher.action.variable.type.queryJson.info"),
this)),
_jsonIndex(new VariableSpinBox(this)), _jsonIndex(new VariableSpinBox(this)),
_entryLayout(new QHBoxLayout()) _entryLayout(new QHBoxLayout())
{ {
@@ -859,14 +863,6 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_randomNumberStart->setMaximum(9999999999); _randomNumberStart->setMaximum(9999999999);
_randomNumberEnd->setMinimum(-9999999999); _randomNumberEnd->setMinimum(-9999999999);
_randomNumberEnd->setMaximum(9999999999); _randomNumberEnd->setMaximum(9999999999);
const QString path = GetThemeTypeName() == "Light"
? ":/res/images/help.svg"
: ":/res/images/help_light.svg";
const QIcon icon(path);
const QPixmap pixmap = icon.pixmap(QSize(16, 16));
_jsonQueryHelp->setPixmap(pixmap);
_jsonQueryHelp->setToolTip(obs_module_text(
"AdvSceneSwitcher.action.variable.type.queryJson.info"));
_jsonIndex->setMaximum(999); _jsonIndex->setMaximum(999);
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)), QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),

View File

@@ -239,7 +239,7 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
(*_entryData)->SetIndex(idx); (*_entryData)->SetIndex(idx);
(*_entryData)->SetLogicType(logic); (*_entryData)->SetLogicType(logic);
(*_entryData)->PostLoad(); (*_entryData)->PostLoad();
RunPostLoadSteps(); RunAndClearPostLoadSteps();
} }
auto widget = auto widget =
MacroConditionFactory::CreateWidget(id, this, *_entryData); MacroConditionFactory::CreateWidget(id, this, *_entryData);

View File

@@ -1080,7 +1080,7 @@ void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id,
macro->Actions().at(idx)->Load(data); macro->Actions().at(idx)->Load(data);
} }
macro->Actions().at(idx)->PostLoad(); macro->Actions().at(idx)->PostLoad();
RunPostLoadSteps(); RunAndClearPostLoadSteps();
macro->UpdateActionIndices(); macro->UpdateActionIndices();
ui->actionsList->Insert( ui->actionsList->Insert(
idx, idx,
@@ -1383,7 +1383,7 @@ void MacroEdit::AddMacroElseAction(Macro *macro, int idx, const std::string &id,
macro->ElseActions().at(idx)->Load(data); macro->ElseActions().at(idx)->Load(data);
} }
macro->ElseActions().at(idx)->PostLoad(); macro->ElseActions().at(idx)->PostLoad();
RunPostLoadSteps(); RunAndClearPostLoadSteps();
macro->UpdateElseActionIndices(); macro->UpdateElseActionIndices();
ui->elseActionsList->Insert( ui->elseActionsList->Insert(
idx, new MacroActionEdit( idx, new MacroActionEdit(
@@ -1583,7 +1583,7 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
macro->Conditions().at(idx)->Load(data); macro->Conditions().at(idx)->Load(data);
} }
macro->Conditions().at(idx)->PostLoad(); macro->Conditions().at(idx)->PostLoad();
RunPostLoadSteps(); RunAndClearPostLoadSteps();
(*cond)->SetLogicType(logic); (*cond)->SetLogicType(logic);
macro->UpdateConditionIndices(); macro->UpdateConditionIndices();
ui->conditionsList->Insert( ui->conditionsList->Insert(

View File

@@ -416,7 +416,7 @@ void AdvSceneSwitcher::ImportMacros()
OBSDataAutoRelease array_obj = obs_data_array_item(array, i); OBSDataAutoRelease array_obj = obs_data_array_item(array, i);
auto macro = std::make_shared<Macro>(); auto macro = std::make_shared<Macro>();
macro->Load(array_obj); macro->Load(array_obj);
RunPostLoadSteps(); RunAndClearPostLoadSteps();
if (macroNameExists(macro->Name()) && if (macroNameExists(macro->Name()) &&
!ResolveMacroImportNameConflict(macro)) { !ResolveMacroImportNameConflict(macro)) {
@@ -444,7 +444,7 @@ void AdvSceneSwitcher::ImportMacros()
for (const auto &macro : importedMacros) { for (const auto &macro : importedMacros) {
macro->PostLoad(); macro->PostLoad();
} }
RunPostLoadSteps(); RunAndClearPostLoadSteps();
ui->macros->Reset(GetMacros(), ui->macros->Reset(GetMacros(),
GetGlobalMacroSettings()._highlightExecuted); GetGlobalMacroSettings()._highlightExecuted);
@@ -752,7 +752,7 @@ void AdvSceneSwitcher::CopyMacro()
newMacro->Load(data); newMacro->Load(data);
newMacro->PostLoad(); newMacro->PostLoad();
newMacro->SetName(name); newMacro->SetName(name);
RunPostLoadSteps(); RunAndClearPostLoadSteps();
Macro::PrepareMoveToGroup(macro->Parent(), newMacro); Macro::PrepareMoveToGroup(macro->Parent(), newMacro);
ui->macros->Add(newMacro, macro); ui->macros->Add(newMacro, macro);

View File

@@ -113,7 +113,7 @@ void ActionQueue::Add(const std::shared_ptr<MacroAction> &action)
action->Save(data); action->Save(data);
copy->Load(data); copy->Load(data);
copy->PostLoad(); copy->PostLoad();
RunPostLoadSteps(); RunAndClearPostLoadSteps();
copy->ResolveVariablesToFixedValues(); copy->ResolveVariablesToFixedValues();
_actions.emplace_back(copy); _actions.emplace_back(copy);
} else { } else {

View File

@@ -2,31 +2,31 @@
namespace advss { namespace advss {
AutoUpdateTooltipLabel::AutoUpdateTooltipLabel( AutoUpdateHelpIcon::AutoUpdateHelpIcon(
QWidget *parent, const std::function<QString()> &updateTooltipCallback, QWidget *parent, const std::function<QString()> &updateTooltipCallback,
int updateIntervalMs) int updateIntervalMs)
: QLabel(parent), : HelpIcon("", parent),
_callback(updateTooltipCallback), _callback(updateTooltipCallback),
_timer(new QTimer(this)), _timer(new QTimer(this)),
_updateIntervalMs(updateIntervalMs) _updateIntervalMs(updateIntervalMs)
{ {
connect(_timer, &QTimer::timeout, this, connect(_timer, &QTimer::timeout, this,
&AutoUpdateTooltipLabel::UpdateTooltip); &AutoUpdateHelpIcon::UpdateTooltip);
} }
void AutoUpdateTooltipLabel::enterEvent(QEnterEvent *event) void AutoUpdateHelpIcon::enterEvent(QEnterEvent *event)
{ {
_timer->start(_updateIntervalMs); _timer->start(_updateIntervalMs);
QLabel::enterEvent(event); QLabel::enterEvent(event);
} }
void AutoUpdateTooltipLabel::leaveEvent(QEvent *event) void AutoUpdateHelpIcon::leaveEvent(QEvent *event)
{ {
_timer->stop(); _timer->stop();
QLabel::leaveEvent(event); QLabel::leaveEvent(event);
} }
void AutoUpdateTooltipLabel::UpdateTooltip() void AutoUpdateHelpIcon::UpdateTooltip()
{ {
setToolTip(_callback()); setToolTip(_callback());
} }

View File

@@ -1,20 +1,19 @@
#pragma once #pragma once
#include "export-symbol-helper.hpp" #include "export-symbol-helper.hpp"
#include "help-icon.hpp"
#include <functional> #include <functional>
#include <QLabel>
#include <QTimer> #include <QTimer>
namespace advss { namespace advss {
class ADVSS_EXPORT AutoUpdateTooltipLabel : public QLabel { class ADVSS_EXPORT AutoUpdateHelpIcon : public HelpIcon {
Q_OBJECT Q_OBJECT
public: public:
AutoUpdateTooltipLabel( AutoUpdateHelpIcon(QWidget *parent,
QWidget *parent, const std::function<QString()> &updateTooltipCallback,
const std::function<QString()> &updateTooltipCallback, int updateIntervalMs = 300);
int updateIntervalMs = 300);
protected: protected:
void enterEvent(QEnterEvent *event) override; void enterEvent(QEnterEvent *event) override;

View File

@@ -35,6 +35,11 @@ void FileSelection::SetPath(const QString &path)
_filePath->setText(path); _filePath->setText(path);
} }
QString FileSelection::GetPath() const
{
return _filePath->text();
}
QString FileSelection::ValidPathOrDesktop(const QString &path) QString FileSelection::ValidPathOrDesktop(const QString &path)
{ {
QFileInfo fileInfo(path); QFileInfo fileInfo(path);

View File

@@ -22,6 +22,7 @@ public:
QWidget *parent = 0); QWidget *parent = 0);
EXPORT void SetPath(const StringVariable &); EXPORT void SetPath(const StringVariable &);
EXPORT void SetPath(const QString &); EXPORT void SetPath(const QString &);
EXPORT QString GetPath() const;
EXPORT QPushButton *Button() { return _browseButton; } EXPORT QPushButton *Button() { return _browseButton; }
EXPORT static QString ValidPathOrDesktop(const QString &path); EXPORT static QString ValidPathOrDesktop(const QString &path);

View File

@@ -113,12 +113,13 @@ void RunLoadSteps(obs_data_t *obj)
} }
} }
void RunPostLoadSteps() void RunAndClearPostLoadSteps()
{ {
std::lock_guard<std::mutex> lock(postLoadMutex); std::lock_guard<std::mutex> lock(postLoadMutex);
for (const auto &func : getPostLoadSteps()) { for (const auto &func : getPostLoadSteps()) {
func(); func();
} }
getPostLoadSteps().clear();
} }
void ClearPostLoadSteps() void ClearPostLoadSteps()

View File

@@ -14,7 +14,7 @@ EXPORT void AddPostLoadStep(std::function<void()>);
EXPORT void AddIntervalResetStep(std::function<void()>); EXPORT void AddIntervalResetStep(std::function<void()>);
void RunSaveSteps(obs_data_t *); void RunSaveSteps(obs_data_t *);
void RunLoadSteps(obs_data_t *); void RunLoadSteps(obs_data_t *);
EXPORT void RunPostLoadSteps(); EXPORT void RunAndClearPostLoadSteps();
void ClearPostLoadSteps(); void ClearPostLoadSteps();
EXPORT void AddPluginInitStep(std::function<void()>); EXPORT void AddPluginInitStep(std::function<void()>);

View File

@@ -239,22 +239,26 @@ void SceneSelection::ResolveVariables()
_type = Type::SCENE; _type = Type::SCENE;
} }
SceneSelection SceneSelectionWidget::CurrentSelection() static obs_weak_canvas_t *getWeakRefToMainCanvas()
{ {
SceneSelection s; static auto canvas = obs_get_main_canvas();
static auto weakCanvas = obs_canvas_get_weak_canvas(canvas);
static auto mainCanvas = obs_get_main_canvas();
static auto mainCanvasWeak = obs_canvas_get_weak_canvas(mainCanvas);
[[maybe_unused]] static const bool _ = []() { [[maybe_unused]] static const bool _ = []() {
// Let's just hope we don't have to deal with selecting scenes // Let's just hope we don't have to deal with selecting scenes
// when the OBS main canvas gets deleted and release the // when the OBS main canvas gets deleted and release the
// references here already to avoid reporting leaks on shutdown // references here already to avoid reporting leaks on shutdown
obs_canvas_release(mainCanvas); obs_canvas_release(canvas);
obs_weak_canvas_release(mainCanvasWeak); obs_weak_canvas_release(weakCanvas);
return true; return true;
}(); }();
return weakCanvas;
}
s._canvas = _forceMainCanvas ? OBSWeakCanvas(mainCanvasWeak) SceneSelection SceneSelectionWidget::CurrentSelection()
{
SceneSelection s;
s._canvas = _forceMainCanvas ? OBSWeakCanvas(getWeakRefToMainCanvas())
: _canvas->GetCanvas(); : _canvas->GetCanvas();
const int idx = _scenes->currentIndex(); const int idx = _scenes->currentIndex();
@@ -342,6 +346,10 @@ void SceneSelectionWidget::Reset()
void SceneSelectionWidget::PopulateSceneSelection(obs_weak_canvas_t *canvas) void SceneSelectionWidget::PopulateSceneSelection(obs_weak_canvas_t *canvas)
{ {
if (_forceMainCanvas) {
canvas = getWeakRefToMainCanvas();
}
_scenes->clear(); _scenes->clear();
if ((_current || _previous)) { if ((_current || _previous)) {
const bool isMain = IsMainCanvas(canvas); const bool isMain = IsMainCanvas(canvas);
@@ -418,7 +426,7 @@ SceneSelectionWidget::SceneSelectionWidget(QWidget *parent, bool variables,
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
if (GetCanvasCount() <= 1) { if (GetCanvasCount() <= 1) {
_canvas->hide(); LockToMainCanvas();
} }
Resize(); Resize();

View File

@@ -46,7 +46,7 @@ public:
bool previous = false, bool current = false, bool previous = false, bool current = false,
bool preview = false); bool preview = false);
EXPORT void SetScene(const SceneSelection &); EXPORT void SetScene(const SceneSelection &);
void LockToMainCanvas(); EXPORT void LockToMainCanvas();
protected: protected:
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;

View File

@@ -199,7 +199,17 @@ std::chrono::high_resolution_clock::time_point GetLastSceneChangeTime()
OBSWeakSource GetCurrentScene() OBSWeakSource GetCurrentScene()
{ {
return switcher->currentScene; if (switcher->currentScene) {
return switcher->currentScene;
}
// If there wasn't any scene switch yet switcher->currentScene will be
// null and we must use obs_frontend_get_current_scene() instead
OBSSourceAutoRelease currentSceneSource =
obs_frontend_get_current_scene();
OBSWeakSourceAutoRelease currentSceneWeakSource =
obs_source_get_weak_source(currentSceneSource);
return currentSceneWeakSource.Get();
} }
OBSWeakSource GetPreviousScene() OBSWeakSource GetPreviousScene()

View File

@@ -202,6 +202,8 @@ static void appendNestedMacros(std::deque<std::shared_ptr<Macro>> &macros,
dynamic_cast<MacroActionMacro *>(action.get()); dynamic_cast<MacroActionMacro *>(action.get());
if (nestedMacroAction) { if (nestedMacroAction) {
macros.push_back(nestedMacroAction->_nestedMacro); macros.push_back(nestedMacroAction->_nestedMacro);
appendNestedMacros(
macros, nestedMacroAction->_nestedMacro.get());
} }
} }
for (const auto &action : macro->ElseActions()) { for (const auto &action : macro->ElseActions()) {
@@ -209,6 +211,8 @@ static void appendNestedMacros(std::deque<std::shared_ptr<Macro>> &macros,
dynamic_cast<MacroActionMacro *>(action.get()); dynamic_cast<MacroActionMacro *>(action.get());
if (nestedMacroAction) { if (nestedMacroAction) {
macros.push_back(nestedMacroAction->_nestedMacro); macros.push_back(nestedMacroAction->_nestedMacro);
appendNestedMacros(
macros, nestedMacroAction->_nestedMacro.get());
} }
} }
} }
@@ -299,9 +303,22 @@ void TempVariableRef::Save(obs_data_t *obj, Macro *macro,
obs_data_set_obj(obj, name, data); obs_data_set_obj(obj, name, data);
} }
void TempVariableRef::Load(obs_data_t *obj, Macro *macro, const char *name) void TempVariableRef::Load(obs_data_t *obj, Macro *macroPtr, const char *name)
{ {
if (!macro) { std::deque<std::shared_ptr<Macro>> allMacros = GetMacros();
for (const auto &topLevelMacro : GetMacros()) {
appendNestedMacros(allMacros, topLevelMacro.get());
}
std::weak_ptr<Macro> macro;
for (const auto &macroShared : allMacros) {
if (macroShared.get() == macroPtr) {
macro = macroShared;
break;
}
}
if (macro.expired()) {
_segment.reset(); _segment.reset();
return; return;
} }
@@ -324,10 +341,17 @@ void TempVariableRef::Load(obs_data_t *obj, Macro *macro, const char *name)
}); });
} }
void TempVariableRef::PostLoad(int idx, SegmentType type, Macro *macro) void TempVariableRef::PostLoad(int idx, SegmentType type,
const std::weak_ptr<Macro> &weakMacro)
{ {
auto childMacro = weakMacro.lock();
if (!childMacro) {
return;
}
auto macro = childMacro.get();
for (int i = 0; i < _depth; i++) { for (int i = 0; i < _depth; i++) {
macro = getParentMacro(macro); macro = getParentMacro(childMacro.get());
} }
if (!macro) { if (!macro) {
@@ -404,9 +428,8 @@ TempVariableSelection::TempVariableSelection(QWidget *parent)
: QWidget(parent), : QWidget(parent),
_selection(new FilterComboBox( _selection(new FilterComboBox(
this, obs_module_text("AdvSceneSwitcher.tempVar.select"))), this, obs_module_text("AdvSceneSwitcher.tempVar.select"))),
_info(new AutoUpdateTooltipLabel(this, [this]() { _info(new AutoUpdateHelpIcon(this,
return SetupInfoLabel(); [this]() { return SetupInfoLabel(); }))
}))
{ {
MacroEdit *edit = findMacroEditParent(parent); MacroEdit *edit = findMacroEditParent(parent);
@@ -416,12 +439,6 @@ TempVariableSelection::TempVariableSelection(QWidget *parent)
_macroEdits.push_back(edit); _macroEdits.push_back(edit);
} }
QString path = GetThemeTypeName() == "Light"
? ":/res/images/help.svg"
: ":/res/images/help_light.svg";
QIcon icon(path);
QPixmap pixmap = icon.pixmap(QSize(16, 16));
_info->setPixmap(pixmap);
_info->hide(); _info->hide();
_selection->setSizeAdjustPolicy(QComboBox::AdjustToContents); _selection->setSizeAdjustPolicy(QComboBox::AdjustToContents);

View File

@@ -72,10 +72,10 @@ private:
enum class SegmentType { NONE, CONDITION, ACTION, ELSEACTION }; enum class SegmentType { NONE, CONDITION, ACTION, ELSEACTION };
SegmentType GetType() const; SegmentType GetType() const;
int GetIdx() const; int GetIdx() const;
void PostLoad(int idx, SegmentType, Macro *); void PostLoad(int idx, SegmentType, const std::weak_ptr<Macro> &);
std::string _id = ""; std::string _id = "";
std::weak_ptr<MacroSegment> _segment; std::weak_ptr<MacroSegment> _segment = {};
int _depth = 0; int _depth = 0;
friend TempVariable; friend TempVariable;
@@ -105,7 +105,7 @@ private:
MacroSegment *GetSegment() const; MacroSegment *GetSegment() const;
FilterComboBox *_selection; FilterComboBox *_selection;
AutoUpdateTooltipLabel *_info; AutoUpdateHelpIcon *_info;
std::vector<MacroEdit *> _macroEdits; std::vector<MacroEdit *> _macroEdits;
}; };

View File

@@ -250,6 +250,10 @@ MacroActionProjectorEdit::MacroActionProjectorEdit(
_regex(new RegexConfigWidget(this)), _regex(new RegexConfigWidget(this)),
_layout(new QHBoxLayout(this)) _layout(new QHBoxLayout(this))
{ {
// The obs_frontend_open_projector() function does not seem to support
// scenes of secondary canvases
_scenes->LockToMainCanvas();
populateActionSelection(_actions); populateActionSelection(_actions);
populateWindowTypes(_windowTypes); populateWindowTypes(_windowTypes);
populateSelectionTypes(_types); populateSelectionTypes(_types);
@@ -396,26 +400,29 @@ void MacroActionProjectorEdit::SetWidgetVisibility()
return; return;
} }
_projectorWindowName->setVisible(_entryData->_action == const auto &action = _entryData->_action;
const auto &type = _entryData->_type;
_projectorWindowName->setVisible(action ==
MacroActionProjector::Action::CLOSE); MacroActionProjector::Action::CLOSE);
_regex->setVisible(_entryData->_action == _regex->setVisible(action == MacroActionProjector::Action::CLOSE);
MacroActionProjector::Action::CLOSE); _types->setVisible(action == MacroActionProjector::Action::OPEN);
_types->setVisible(_entryData->_action == _windowTypes->setVisible(action == MacroActionProjector::Action::OPEN);
MacroActionProjector::Action::OPEN); _scenes->setVisible(action == MacroActionProjector::Action::OPEN &&
_windowTypes->setVisible(_entryData->_action == type == MacroActionProjector::Type::SCENE);
MacroActionProjector::Action::OPEN); _sources->setVisible(action == MacroActionProjector::Action::OPEN &&
_scenes->setVisible( type == MacroActionProjector::Type::SOURCE);
_entryData->_action == MacroActionProjector::Action::OPEN && _monitors->setVisible(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); _entryData->_fullscreen);
adjustSize(); adjustSize();
updateGeometry(); updateGeometry();
if (action == MacroActionProjector::Action::CLOSE) {
RemoveStretchIfPresent(_layout);
} else {
AddStretchIfNecessary(_layout);
}
} }
} // namespace advss } // namespace advss

View File

@@ -240,7 +240,7 @@ bool MacroActionSwitchScene::Load(obs_data_t *obj)
GetWeakCanvasByName(obs_data_get_string(obj, "canvas")); GetWeakCanvasByName(obs_data_get_string(obj, "canvas"));
} else { } else {
OBSCanvasAutoRelease main = obs_get_main_canvas(); OBSCanvasAutoRelease main = obs_get_main_canvas();
_canvas = OBSGetWeakRef(main); _canvas = obs_canvas_get_weak_canvas(main);
} }
_index.Save(obj, "index"); _index.Save(obj, "index");

View File

@@ -242,7 +242,7 @@ MacroConditionStreamEdit::MacroConditionStreamEdit(
_keyFrameInterval(new VariableSpinBox()), _keyFrameInterval(new VariableSpinBox()),
_streamKey(new VariableLineEdit(this)), _streamKey(new VariableLineEdit(this)),
_serviceName(new VariableLineEdit(this)), _serviceName(new VariableLineEdit(this)),
_currentService(new AutoUpdateTooltipLabel( _currentService(new AutoUpdateHelpIcon(
this, this,
[]() { []() {
QString formatString = obs_module_text( QString formatString = obs_module_text(
@@ -255,13 +255,6 @@ MacroConditionStreamEdit::MacroConditionStreamEdit(
_keyFrameInterval->setMinimum(0); _keyFrameInterval->setMinimum(0);
_keyFrameInterval->setMaximum(25); _keyFrameInterval->setMaximum(25);
QString path = GetThemeTypeName() == "Light"
? ":/res/images/help.svg"
: ":/res/images/help_light.svg";
QIcon icon(path);
QPixmap pixmap = icon.pixmap(QSize(16, 16));
_currentService->setPixmap(pixmap);
_streamKey->setEchoMode(QLineEdit::PasswordEchoOnEdit); _streamKey->setEchoMode(QLineEdit::PasswordEchoOnEdit);
populateConditionSelection(_conditions); populateConditionSelection(_conditions);

View File

@@ -15,12 +15,15 @@ if(NOT TARGET httplib)
EXCLUDE_FROM_ALL) EXCLUDE_FROM_ALL)
endif() endif()
if(NOT OPENSSL_INCLUDE_DIR OR NOT OPENSSL_LIBRARIES) if(OS_MACOS)
find_package(OpenSSL) set(OPENSSL_USE_STATIC_LIBS
if(NOT OPENSSL_FOUND) ON
message(WARNING "OpenSSL not found!\n" "HTTP support will be disabled!\n\n") CACHE BOOL "Use static OpenSSL" FORCE)
return() endif()
endif() 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) find_package(ZLIB)

View File

@@ -3,6 +3,13 @@ project(advanced-scene-switcher-mqtt)
# --- Check paho.mqtt.cpp requirements --- # --- Check paho.mqtt.cpp requirements ---
if(OS_MACOS)
set(OPENSSL_USE_STATIC_LIBS
ON
CACHE BOOL "Use static OpenSSL" FORCE)
endif()
find_package(OpenSSL)
find_package(PahoMqttCpp) find_package(PahoMqttCpp)
if(NOT PahoMqttCpp_FOUND) if(NOT PahoMqttCpp_FOUND)
message(WARNING "PahoMqttCpp not found!\n" message(WARNING "PahoMqttCpp not found!\n"

View File

@@ -1,4 +1,5 @@
#include "mqtt-helpers.hpp" #include "mqtt-helpers.hpp"
#include "help-icon.hpp"
#include "layout-helpers.hpp" #include "layout-helpers.hpp"
#include "log-helper.hpp" #include "log-helper.hpp"
#include "obs-module-helper.hpp" #include "obs-module-helper.hpp"
@@ -22,6 +23,10 @@ MqttConnection::MqttConnection(const MqttConnection &other)
_uri(other._uri), _uri(other._uri),
_username(other._username), _username(other._username),
_password(other._password), _password(other._password),
_trustStore(other._trustStore),
_keyStore(other._keyStore),
_privateKey(other._privateKey),
_verifyServerCert(other._verifyServerCert),
_connectOnStart(other._connectOnStart), _connectOnStart(other._connectOnStart),
_reconnect(other._reconnect), _reconnect(other._reconnect),
_reconnectDelay(other._reconnectDelay) _reconnectDelay(other._reconnectDelay)
@@ -30,12 +35,20 @@ MqttConnection::MqttConnection(const MqttConnection &other)
MqttConnection::MqttConnection(const std::string &name, const std::string &uri, MqttConnection::MqttConnection(const std::string &name, const std::string &uri,
const std::string &username, const std::string &username,
const std::string &password, bool connectOnStart, const std::string &password,
const std::string &trustStore,
const std::string &keyStore,
const std::string &privateKey,
bool verifyServerCert, bool connectOnStart,
bool reconnect, int reconnectDelay) bool reconnect, int reconnectDelay)
: Item(name), : Item(name),
_uri(uri), _uri(uri),
_username(username), _username(username),
_password(password), _password(password),
_trustStore(trustStore),
_keyStore(keyStore),
_privateKey(privateKey),
_verifyServerCert(verifyServerCert),
_connectOnStart(connectOnStart), _connectOnStart(connectOnStart),
_reconnect(reconnect), _reconnect(reconnect),
_reconnectDelay(reconnectDelay) _reconnectDelay(reconnectDelay)
@@ -99,26 +112,41 @@ void MqttConnection::ConnectThread()
}; };
do { do {
std::unique_lock<std::mutex> clientLock(_clientMtx);
_client = std::make_shared<mqtt::async_client>(
_uri, std::string("advss_") + _name);
#ifdef ENABLE_MQTT5_SUPPORT
auto connOpts = mqtt::connect_options_builder::v5()
#else
auto connOpts = mqtt::connect_options_builder()
#endif
.clean_start(false)
.clean_session(true)
.connect_timeout(5s)
.user_name(_username)
.password(_password)
.finalize();
_client->set_connection_lost_handler(logConnectionLost);
_client->set_message_callback(dispatchMessage);
_client->start_consuming();
try { try {
std::unique_lock<std::mutex> clientLock(_clientMtx);
_client = std::make_shared<mqtt::async_client>(
_uri, std::string("advss_") + _name);
mqtt::ssl_options sslOptions;
if (!_trustStore.empty()) {
sslOptions.set_trust_store(_trustStore);
}
if (!_keyStore.empty()) {
sslOptions.set_key_store(_keyStore);
}
if (!_privateKey.empty()) {
sslOptions.set_private_key(_privateKey);
}
sslOptions.set_enable_server_cert_auth(
_verifyServerCert);
#ifdef ENABLE_MQTT5_SUPPORT
auto connOpts = mqtt::connect_options_builder::v5()
#else
auto connOpts = mqtt::connect_options_builder()
#endif
.clean_start(false)
.clean_session(true)
.connect_timeout(5s)
.user_name(_username)
.password(_password)
.ssl(sslOptions)
.finalize();
_client->set_connection_lost_handler(logConnectionLost);
_client->set_message_callback(dispatchMessage);
_client->start_consuming();
vblog(LOG_INFO, "connecting to MQTT server \"%s\" ...", vblog(LOG_INFO, "connecting to MQTT server \"%s\" ...",
_name.c_str()); _name.c_str());
auto tok = _client->connect(connOpts); auto tok = _client->connect(connOpts);
@@ -220,6 +248,10 @@ void MqttConnection::Load(obs_data_t *data)
_uri = obs_data_get_string(data, "uri"); _uri = obs_data_get_string(data, "uri");
_username = obs_data_get_string(data, "username"); _username = obs_data_get_string(data, "username");
_password = obs_data_get_string(data, "password"); _password = obs_data_get_string(data, "password");
_trustStore = obs_data_get_string(data, "trustStore");
_keyStore = obs_data_get_string(data, "keyStore");
_privateKey = obs_data_get_string(data, "privateKey");
_verifyServerCert = obs_data_get_bool(data, "verifyServerCert");
_connectOnStart = obs_data_get_bool(data, "connectOnStart"); _connectOnStart = obs_data_get_bool(data, "connectOnStart");
_reconnect = obs_data_get_bool(data, "reconnect"); _reconnect = obs_data_get_bool(data, "reconnect");
_reconnectDelay = obs_data_get_int(data, "reconnectDelay"); _reconnectDelay = obs_data_get_int(data, "reconnectDelay");
@@ -251,6 +283,10 @@ void MqttConnection::Save(obs_data_t *data) const
obs_data_set_string(data, "uri", _uri.c_str()); obs_data_set_string(data, "uri", _uri.c_str());
obs_data_set_string(data, "username", _username.c_str()); obs_data_set_string(data, "username", _username.c_str());
obs_data_set_string(data, "password", _password.c_str()); obs_data_set_string(data, "password", _password.c_str());
obs_data_set_string(data, "trustStore", _trustStore.c_str());
obs_data_set_string(data, "keyStore", _keyStore.c_str());
obs_data_set_string(data, "privateKey", _privateKey.c_str());
obs_data_set_bool(data, "verifyServerCert", _verifyServerCert);
obs_data_set_bool(data, "connectOnStart", _connectOnStart); obs_data_set_bool(data, "connectOnStart", _connectOnStart);
obs_data_set_bool(data, "reconnect", _reconnect); obs_data_set_bool(data, "reconnect", _reconnect);
obs_data_set_int(data, "reconnectDelay", _reconnectDelay); obs_data_set_int(data, "reconnectDelay", _reconnectDelay);
@@ -308,6 +344,10 @@ MqttConnectionSettingsDialog::MqttConnectionSettingsDialog(
_username(new QLineEdit()), _username(new QLineEdit()),
_password(new QLineEdit()), _password(new QLineEdit()),
_showPassword(new QPushButton()), _showPassword(new QPushButton()),
_trustStore(new FileSelection()),
_keyStore(new FileSelection()),
_privateKey(new FileSelection()),
_verifyServerCert(new QCheckBox()),
_topics(new MqttTopicListWidget(this)), _topics(new MqttTopicListWidget(this)),
_connectOnStart(new QCheckBox()), _connectOnStart(new QCheckBox()),
_reconnect(new QCheckBox()), _reconnect(new QCheckBox()),
@@ -324,6 +364,10 @@ MqttConnectionSettingsDialog::MqttConnectionSettingsDialog(
_uri->setText(QString::fromStdString(connection._uri)); _uri->setText(QString::fromStdString(connection._uri));
_username->setText(QString::fromStdString(connection._username)); _username->setText(QString::fromStdString(connection._username));
_password->setText(QString::fromStdString(connection._password)); _password->setText(QString::fromStdString(connection._password));
_trustStore->SetPath(QString::fromStdString(connection._trustStore));
_keyStore->SetPath(QString::fromStdString(connection._keyStore));
_privateKey->SetPath(QString::fromStdString(connection._privateKey));
_verifyServerCert->setChecked(connection._verifyServerCert);
_topics->SetValues(connection._topics, connection._qos); _topics->SetValues(connection._topics, connection._qos);
_reconnectDelay->setMaximum(9999); _reconnectDelay->setMaximum(9999);
_reconnectDelay->setSuffix("s"); _reconnectDelay->setSuffix("s");
@@ -364,6 +408,41 @@ MqttConnectionSettingsDialog::MqttConnectionSettingsDialog(
passLayout->addWidget(_showPassword); passLayout->addWidget(_showPassword);
_layout->addLayout(passLayout, row, 1); _layout->addLayout(passLayout, row, 1);
++row; ++row;
_layout->addWidget(
new QLabel(obs_module_text(
"AdvSceneSwitcher.mqttConnection.trustStore")),
row, 0);
auto trustStoreLayout = new QHBoxLayout();
trustStoreLayout->addWidget(_trustStore);
trustStoreLayout->addWidget(new HelpIcon(obs_module_text(
"AdvSceneSwitcher.mqttConnection.trustStore.help")));
_layout->addLayout(trustStoreLayout, row, 1);
++row;
_layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.mqttConnection.keyStore")),
row, 0);
auto keyStoreLayout = new QHBoxLayout();
keyStoreLayout->addWidget(_keyStore);
keyStoreLayout->addWidget(new HelpIcon(obs_module_text(
"AdvSceneSwitcher.mqttConnection.keyStore.help")));
_layout->addLayout(keyStoreLayout, row, 1);
++row;
_layout->addWidget(
new QLabel(obs_module_text(
"AdvSceneSwitcher.mqttConnection.privateKey")),
row, 0);
auto privateKeyLayout = new QHBoxLayout();
privateKeyLayout->addWidget(_privateKey);
privateKeyLayout->addWidget(new HelpIcon(obs_module_text(
"AdvSceneSwitcher.mqttConnection.privateKey.help")));
_layout->addLayout(privateKeyLayout, row, 1);
++row;
_layout->addWidget(
new QLabel(obs_module_text(
"AdvSceneSwitcher.mqttConnection.verifyServerCert")),
row, 0);
_layout->addWidget(_verifyServerCert, row, 1);
++row;
_layout->addWidget(new QLabel( _layout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.mqttConnection.topics"))); obs_module_text("AdvSceneSwitcher.mqttConnection.topics")));
++row; ++row;
@@ -412,6 +491,10 @@ bool MqttConnectionSettingsDialog::AskForSettings(QWidget *parent,
connection._uri = dialog._uri->text().toStdString(); connection._uri = dialog._uri->text().toStdString();
connection._username = dialog._username->text().toStdString(); connection._username = dialog._username->text().toStdString();
connection._password = dialog._password->text().toStdString(); connection._password = dialog._password->text().toStdString();
connection._trustStore = dialog._trustStore->GetPath().toStdString();
connection._keyStore = dialog._keyStore->GetPath().toStdString();
connection._privateKey = dialog._privateKey->GetPath().toStdString();
connection._verifyServerCert = dialog._verifyServerCert->isChecked();
connection._topics = dialog._topics->GetTopics(); connection._topics = dialog._topics->GetTopics();
connection._qos = dialog._topics->GetQoS(); connection._qos = dialog._topics->GetQoS();
connection._connectOnStart = dialog._connectOnStart->isChecked(); connection._connectOnStart = dialog._connectOnStart->isChecked();
@@ -455,6 +538,10 @@ void MqttConnectionSettingsDialog::TestConnection()
connection->_uri = _uri->text().toStdString(); connection->_uri = _uri->text().toStdString();
connection->_username = _username->text().toStdString(); connection->_username = _username->text().toStdString();
connection->_password = _password->text().toStdString(); connection->_password = _password->text().toStdString();
connection->_trustStore = _trustStore->GetPath().toStdString();
connection->_keyStore = _keyStore->GetPath().toStdString();
connection->_privateKey = _privateKey->GetPath().toStdString();
connection->_verifyServerCert = _verifyServerCert->isChecked();
connection->_topics = _topics->GetTopics(); connection->_topics = _topics->GetTopics();
connection->_qos = _topics->GetQoS(); connection->_qos = _topics->GetQoS();
connection->_connectOnStart = false; connection->_connectOnStart = false;

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "message-dispatcher.hpp" #include "message-dispatcher.hpp"
#include "file-selection.hpp"
#include "item-selection-helpers.hpp" #include "item-selection-helpers.hpp"
#include "topic-selection.hpp" #include "topic-selection.hpp"
@@ -27,6 +28,9 @@ public:
MqttConnection(const MqttConnection &other); MqttConnection(const MqttConnection &other);
MqttConnection(const std::string &name, const std::string &uri, MqttConnection(const std::string &name, const std::string &uri,
const std::string &username, const std::string &password, const std::string &username, const std::string &password,
const std::string &trustStore,
const std::string &keyStore,
const std::string &privateKey, bool verifyServerCert,
bool connectOnStart, bool reconnect, int reconnectDelay); bool connectOnStart, bool reconnect, int reconnectDelay);
static std::shared_ptr<Item> Create() static std::shared_ptr<Item> Create()
{ {
@@ -51,8 +55,12 @@ private:
std::string _uri = "mqtt://localhost:1883"; std::string _uri = "mqtt://localhost:1883";
std::string _username = "user"; std::string _username = "user";
std::string _password = "password"; std::string _password = "password";
std::string _trustStore = "";
std::string _keyStore = "";
std::string _privateKey = "";
bool _verifyServerCert = false;
std::vector<std::string> _topics = {"/#"}; std::vector<std::string> _topics = {"/example"};
std::vector<int> _qos = {1}; std::vector<int> _qos = {1};
std::thread _thread; std::thread _thread;
@@ -91,6 +99,10 @@ private:
QLineEdit *_username; QLineEdit *_username;
QLineEdit *_password; QLineEdit *_password;
QPushButton *_showPassword; QPushButton *_showPassword;
FileSelection *_trustStore;
FileSelection *_keyStore;
FileSelection *_privateKey;
QCheckBox *_verifyServerCert;
MqttTopicListWidget *_topics; MqttTopicListWidget *_topics;
QCheckBox *_connectOnStart; QCheckBox *_connectOnStart;
QCheckBox *_reconnect; QCheckBox *_reconnect;

View File

@@ -15,13 +15,15 @@ if(NOT TARGET httplib)
EXCLUDE_FROM_ALL) EXCLUDE_FROM_ALL)
endif() endif()
if(NOT OPENSSL_INCLUDE_DIR OR NOT OPENSSL_LIBRARIES) if(OS_MACOS)
find_package(OpenSSL) set(OPENSSL_USE_STATIC_LIBS
if(NOT OPENSSL_FOUND) ON
message(WARNING "OpenSSL not found!\n" CACHE BOOL "Use static OpenSSL" FORCE)
"Twitch support will be disabled!\n\n") endif()
return() find_package(OpenSSL)
endif() if(NOT OPENSSL_FOUND)
message(WARNING "OpenSSL not found!\n" "Twitch support will be disabled!\n\n")
return()
endif() endif()
find_package(ZLIB) find_package(ZLIB)

View File

@@ -32,28 +32,17 @@ static const int reconnectDelay = 15;
#undef DispatchMessage #undef DispatchMessage
EventSub::EventSub() : QObject(nullptr) EventSub::EventSub()
: QObject(nullptr),
_client(std::make_unique<EventSubWSClient>())
{ {
_client.get_alog().clear_channels( SetupClient(*_client);
websocketpp::log::alevel::frame_header |
websocketpp::log::alevel::frame_payload |
websocketpp::log::alevel::control);
_client.init_asio();
#ifndef _WIN32
_client.set_reuse_addr(true);
#endif
_client.set_open_handler(bind(&EventSub::OnOpen, this, _1)); _client->set_open_handler(bind(&EventSub::OnOpen, this, _1));
_client.set_message_handler(bind(&EventSub::OnMessage, this, _1, _2)); _client->set_message_handler(bind(&EventSub::OnMessage, this, _1, _2));
_client.set_close_handler(bind(&EventSub::OnClose, this, _1)); _client->set_close_handler(bind(&EventSub::OnClose, this, _1));
_client.set_fail_handler(bind(&EventSub::OnFail, this, _1)); _client->set_fail_handler(bind(&EventSub::OnFail, this, _1));
#ifndef USE_TWITCH_CLI_MOCK
_client.set_tls_init_handler([](websocketpp::connection_hdl) {
return websocketpp::lib::make_shared<asio::ssl::context>(
asio::ssl::context::sslv23_client);
});
#endif
_url = defaultURL.data(); _url = defaultURL.data();
RegisterInstance(); RegisterInstance();
} }
@@ -82,28 +71,34 @@ void EventSub::UnregisterInstance()
void EventSub::ConnectThread() void EventSub::ConnectThread()
{ {
while (!_disconnect) { _client->reset();
std::unique_lock<std::mutex> lock(_waitMtx); _connected = true;
_client.reset(); websocketpp::lib::error_code ec;
_connected = true; EventSubWSClient::connection_ptr con =
websocketpp::lib::error_code ec; _client->get_connection(_url, ec);
EventSubWSClient::connection_ptr con = if (ec) {
_client.get_connection(_url, ec); blog(LOG_INFO, "Twitch EventSub failed: %s",
if (ec) { ec.message().c_str());
blog(LOG_INFO, "Twitch EventSub failed: %s", } else {
ec.message().c_str()); _client->connect(con);
} else { _connection = connection_hdl(con);
_client.connect(con); _client->run();
_connection = connection_hdl(con); }
_client.run();
}
_connected = false;
}
void EventSub::WaitAndReconnect()
{
auto thread = std::thread([this]() {
std::unique_lock<std::mutex> lock(_waitMtx);
blog(LOG_INFO, blog(LOG_INFO,
"Twitch EventSub trying to reconnect to in %d seconds.", "Twitch EventSub trying to reconnect to in %d seconds.",
reconnectDelay); reconnectDelay);
_cv.wait_for(lock, std::chrono::seconds(reconnectDelay)); _cv.wait_for(lock, std::chrono::seconds(reconnectDelay));
} Connect();
_connected = false; });
thread.detach();
} }
void EventSub::Connect() void EventSub::Connect()
@@ -137,8 +132,8 @@ void EventSub::Disconnect()
std::lock_guard<std::mutex> lock(_connectMtx); std::lock_guard<std::mutex> lock(_connectMtx);
_disconnect = true; _disconnect = true;
websocketpp::lib::error_code ec; websocketpp::lib::error_code ec;
_client.close(_connection, websocketpp::close::status::normal, _client->close(_connection, websocketpp::close::status::normal,
"Twitch EventSub stopping", ec); "Twitch EventSub stopping", ec);
{ {
std::unique_lock<std::mutex> waitLock(_waitMtx); std::unique_lock<std::mutex> waitLock(_waitMtx);
_cv.notify_all(); _cv.notify_all();
@@ -146,8 +141,8 @@ void EventSub::Disconnect()
while (_connected) { while (_connected) {
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(10));
_client.close(_connection, websocketpp::close::status::normal, _client->close(_connection, websocketpp::close::status::normal,
"Twitch EventSub stopping", ec); "Twitch EventSub stopping", ec);
} }
if (_thread.joinable()) { if (_thread.joinable()) {
@@ -238,6 +233,24 @@ std::string EventSub::AddEventSubscription(std::shared_ptr<TwitchToken> token,
return subscription.id; return subscription.id;
} }
void EventSub::SetupClient(EventSubWSClient &client)
{
client.get_alog().clear_channels(
websocketpp::log::alevel::frame_header |
websocketpp::log::alevel::frame_payload |
websocketpp::log::alevel::control);
client.init_asio();
#ifndef _WIN32
client.set_reuse_addr(true);
#endif
#ifndef USE_TWITCH_CLI_MOCK
client.set_tls_init_handler([](websocketpp::connection_hdl) {
return websocketpp::lib::make_shared<asio::ssl::context>(
asio::ssl::context::sslv23_client);
});
#endif
}
void EventSub::OnOpen(connection_hdl) void EventSub::OnOpen(connection_hdl)
{ {
vblog(LOG_INFO, "Twitch EventSub connection opened"); vblog(LOG_INFO, "Twitch EventSub connection opened");
@@ -303,13 +316,14 @@ bool EventSub::IsValidID(const std::string &id)
return !_sessionID.empty() && id == _sessionID; return !_sessionID.empty() && id == _sessionID;
} }
void EventSub::OnMessage(connection_hdl, EventSubWSClient::message_ptr message) std::optional<const EventSub::ParsedMessage>
EventSub::ParseWebSocketMessage(const EventSubWSClient::message_ptr &message)
{ {
if (!message) { if (!message) {
return; return {};
} }
if (message->get_opcode() != websocketpp::frame::opcode::text) { if (message->get_opcode() != websocketpp::frame::opcode::text) {
return; return {};
} }
std::string payload = message->get_payload(); std::string payload = message->get_payload();
@@ -317,7 +331,7 @@ void EventSub::OnMessage(connection_hdl, EventSubWSClient::message_ptr message)
if (!json) { if (!json) {
blog(LOG_ERROR, "invalid JSON payload received for '%s'", blog(LOG_ERROR, "invalid JSON payload received for '%s'",
payload.c_str()); payload.c_str());
return; return {};
} }
OBSDataAutoRelease metadata = obs_data_get_obj(json, "metadata"); OBSDataAutoRelease metadata = obs_data_get_obj(json, "metadata");
@@ -325,31 +339,46 @@ void EventSub::OnMessage(connection_hdl, EventSubWSClient::message_ptr message)
obs_data_get_string(metadata, "message_timestamp"); obs_data_get_string(metadata, "message_timestamp");
if (_validateTimestamps && !isValidTimestamp(timestamp)) { if (_validateTimestamps && !isValidTimestamp(timestamp)) {
blog(LOG_WARNING, blog(LOG_WARNING,
"Discarding Twitch EventSub with invalid timestamp %s", "discarding Twitch EventSub with invalid timestamp %s",
timestamp.c_str()); timestamp.c_str());
return; return {};
} }
std::string id = obs_data_get_string(metadata, "message_id"); std::string id = obs_data_get_string(metadata, "message_id");
if (!IsValidMessageID(id)) { if (!IsValidMessageID(id)) {
blog(LOG_WARNING, blog(LOG_WARNING,
"Discarding Twitch EventSub with invalid message_id"); "discarding Twitch EventSub with invalid message_id");
return {};
}
ParsedMessage parsedMessage{obs_data_get_string(metadata,
"message_type"),
obs_data_get_obj(json, "payload")};
return parsedMessage;
}
void EventSub::OnMessage(connection_hdl, EventSubWSClient::message_ptr message)
{
const auto msg = ParseWebSocketMessage(message);
if (!msg) {
return; return;
} }
std::string messageType = obs_data_get_string(metadata, "message_type");
OBSDataAutoRelease payloadJson = obs_data_get_obj(json, "payload"); const auto &type = msg->type;
if (messageType == "session_welcome") { const auto &data = msg->payload;
HandleWelcome(payloadJson); if (type == "session_welcome") {
} else if (messageType == "session_keepalive") { HandleWelcome(data);
} else if (type == "session_keepalive") {
HandleKeepAlive(); HandleKeepAlive();
} else if (messageType == "notification") { } else if (type == "notification") {
HandleNotification(payloadJson); HandleNotification(data);
} else if (messageType == "session_reconnect") { } else if (type == "session_reconnect") {
HandleReconnect(payloadJson); HandleServerMigration(data);
} else if (messageType == "revocation") { } else if (type == "revocation") {
HandleRevocation(payloadJson); HandleRevocation(data);
} else { } else {
vblog(LOG_INFO, "ignoring message of unknown type '%s'", vblog(LOG_INFO,
messageType.c_str()); "Twitch EventSub ignoring message of unknown type '%s'",
type.c_str());
} }
} }
@@ -377,9 +406,96 @@ void EventSub::HandleNotification(obs_data_t *data)
_dispatcher.DispatchMessage(event); _dispatcher.DispatchMessage(event);
} }
void EventSub::HandleReconnect(obs_data_t *data) void EventSub::OnServerMigrationWelcome(
connection_hdl newHdl, std::unique_ptr<EventSubWSClient> &newClient)
{
std::lock_guard<std::mutex> lock(_connectMtx);
// Disable reconnect handling for old connection which will be closed
_client->set_close_handler([](connection_hdl) {});
_client->set_fail_handler([](connection_hdl) {});
auto connection = _client->get_con_from_hdl(_connection);
connection->set_close_handler([](connection_hdl) {
vblog(LOG_INFO, "previous Twitch EventSub connection closed");
});
connection->set_fail_handler([](connection_hdl) {});
websocketpp::lib::error_code ec;
_client->close(_connection, websocketpp::close::status::normal,
"Switching to new connection", ec);
_client.swap(newClient);
_sessionID = _migrationSessionID;
_connection = newHdl;
_client->set_open_handler(bind(&EventSub::OnOpen, this, _1));
_client->set_message_handler(bind(&EventSub::OnMessage, this, _1, _2));
_client->set_close_handler(bind(&EventSub::OnClose, this, _1));
_client->set_fail_handler(bind(&EventSub::OnFail, this, _1));
auto newConnection = _client->get_con_from_hdl(_connection);
newConnection->set_open_handler(bind(&EventSub::OnOpen, this, _1));
newConnection->set_message_handler(
bind(&EventSub::OnMessage, this, _1, _2));
newConnection->set_close_handler(bind(&EventSub::OnClose, this, _1));
newConnection->set_fail_handler(bind(&EventSub::OnFail, this, _1));
_connected = true;
_migrating = false;
}
void EventSub::StartServerMigrationClient(const std::string &url)
{
auto client = std::make_unique<EventSubWSClient>();
SetupClient(*client);
client->set_open_handler([this](connection_hdl hdl) {
vblog(LOG_INFO, "Twitch EventSub migration client opened");
});
client->set_message_handler([this,
&client](connection_hdl hdl,
EventSubWSClient::message_ptr
message) {
const auto msg = ParseWebSocketMessage(message);
if (!msg) {
return;
}
const auto &type = msg->type;
const auto &data = msg->payload;
if (type == "session_welcome") {
vblog(LOG_INFO,
"Twitch EventSub migration successful - switching to new connection");
OBSDataAutoRelease session =
obs_data_get_obj(data, "session");
_migrationSessionID =
obs_data_get_string(session, "id");
OnServerMigrationWelcome(hdl, client);
} else {
OnMessage(hdl, message);
}
});
websocketpp::lib::error_code ec;
auto con = client->get_connection(url, ec);
if (ec) {
blog(LOG_ERROR,
"Twitch EventSub migration connection failed: %s",
ec.message().c_str());
_migrating = false;
return;
}
_migrationConnection = con;
client->connect(con);
client->run();
}
void EventSub::HandleServerMigration(obs_data_t *data)
{ {
blog(LOG_INFO, "Twitch EventSub session_reconnect received"); blog(LOG_INFO, "Twitch EventSub session_reconnect received");
OBSDataAutoRelease session = obs_data_get_obj(data, "session"); OBSDataAutoRelease session = obs_data_get_obj(data, "session");
auto id = obs_data_get_string(session, "id"); auto id = obs_data_get_string(session, "id");
if (!IsValidID(id)) { if (!IsValidID(id)) {
@@ -388,8 +504,25 @@ void EventSub::HandleReconnect(obs_data_t *data)
return; return;
} }
// TODO: const std::string newURL =
// Implement proper reconnect handing to avoid dropped events obs_data_get_string(session, "reconnect_url");
if (newURL.empty()) {
blog(LOG_WARNING, "missing reconnect_url in session_reconnect");
return;
}
if (_migrating.exchange(true)) {
vblog(LOG_INFO,
"ignoring Twitch EventSub session_reconnect - already in progress");
return;
}
vblog(LOG_INFO, "Twitch EventSub reconnect: connecting to %s",
newURL.c_str());
std::thread([this, newURL]() {
StartServerMigrationClient(newURL);
}).detach();
} }
void EventSub::HandleRevocation(obs_data_t *data) void EventSub::HandleRevocation(obs_data_t *data)
@@ -425,23 +558,41 @@ void EventSub::HandleRevocation(obs_data_t *data)
void EventSub::OnClose(connection_hdl hdl) void EventSub::OnClose(connection_hdl hdl)
{ {
EventSubWSClient::connection_ptr con = _client.get_con_from_hdl(hdl); EventSubWSClient::connection_ptr con = _client->get_con_from_hdl(hdl);
const auto msg = con->get_ec().message(); const auto msg = con->get_ec().message();
const auto reason = con->get_remote_close_reason(); const auto reason = con->get_remote_close_reason();
const auto code = con->get_remote_close_code(); const auto code = con->get_remote_close_code();
blog(LOG_INFO, "Twitch EventSub connection closed: %s / %s (%d)", blog(LOG_INFO, "Twitch EventSub connection closed: %s / %s (%d)",
msg.c_str(), reason.c_str(), code); msg.c_str(), reason.c_str(), code);
ClearActiveSubscriptions();
if (_migrating) {
ClearActiveSubscriptions();
}
_connected = false; _connected = false;
if (_disconnect) {
return;
}
WaitAndReconnect();
} }
void EventSub::OnFail(connection_hdl hdl) void EventSub::OnFail(connection_hdl hdl)
{ {
EventSubWSClient::connection_ptr con = _client.get_con_from_hdl(hdl); EventSubWSClient::connection_ptr con = _client->get_con_from_hdl(hdl);
auto msg = con->get_ec().message(); auto msg = con->get_ec().message();
blog(LOG_INFO, "Twitch EventSub connection failed: %s", msg.c_str()); blog(LOG_INFO, "Twitch EventSub connection failed: %s", msg.c_str());
ClearActiveSubscriptions();
if (!_migrating) {
ClearActiveSubscriptions();
}
_connected = false; _connected = false;
if (_disconnect) {
return;
}
WaitAndReconnect();
} }
bool Subscription::operator<(const Subscription &other) const bool Subscription::operator<(const Subscription &other) const

View File

@@ -19,10 +19,10 @@
namespace advss { namespace advss {
#ifdef USE_TWITCH_CLI_MOCK #ifdef USE_TWITCH_CLI_MOCK
typedef websocketpp::client<websocketpp::config::asio_client> EventSubWSClient; using EventSubWSClient = websocketpp::client<websocketpp::config::asio_client>;
#else #else
typedef websocketpp::client<websocketpp::config::asio_tls_client> using EventSubWSClient =
EventSubWSClient; websocketpp::client<websocketpp::config::asio_tls_client>;
#endif #endif
struct Event; struct Event;
@@ -61,12 +61,16 @@ public:
void EnableTimestampValidation(bool enable); void EnableTimestampValidation(bool enable);
private: private:
static void SetupClient(EventSubWSClient &);
void OnOpen(connection_hdl hdl); void OnOpen(connection_hdl hdl);
void OnMessage(connection_hdl hdl, void OnMessage(connection_hdl hdl,
EventSubWSClient::message_ptr message); EventSubWSClient::message_ptr message);
void OnClose(connection_hdl hdl); void OnClose(connection_hdl hdl);
void OnFail(connection_hdl hdl); void OnFail(connection_hdl hdl);
void ConnectThread(); void ConnectThread();
void WaitAndReconnect();
bool IsValidMessageID(const std::string &); bool IsValidMessageID(const std::string &);
bool IsValidID(const std::string &); bool IsValidID(const std::string &);
@@ -74,20 +78,39 @@ private:
void HandleWelcome(obs_data_t *); void HandleWelcome(obs_data_t *);
void HandleKeepAlive() const; void HandleKeepAlive() const;
void HandleNotification(obs_data_t *); void HandleNotification(obs_data_t *);
void HandleReconnect(obs_data_t *); void HandleServerMigration(obs_data_t *);
void HandleRevocation(obs_data_t *); void HandleRevocation(obs_data_t *);
void RegisterInstance(); void RegisterInstance();
void UnregisterInstance(); void UnregisterInstance();
EventSubWSClient _client; void StartServerMigrationClient(const std::string &url);
void OnServerMigrationWelcome(connection_hdl,
std::unique_ptr<EventSubWSClient> &);
struct ParsedMessage {
std::string type;
OBSDataAutoRelease payload;
};
std::optional<const ParsedMessage>
ParseWebSocketMessage(const EventSubWSClient::message_ptr &);
std::unique_ptr<EventSubWSClient> _client;
connection_hdl _connection; connection_hdl _connection;
std::unique_ptr<EventSubWSClient> _migrationClient;
connection_hdl _migrationConnection;
std::atomic_bool _migrating{false};
std::string _migrationSessionID;
std::thread _thread; std::thread _thread;
std::mutex _waitMtx; std::mutex _waitMtx;
std::mutex _connectMtx; std::mutex _connectMtx;
std::condition_variable _cv; std::condition_variable _cv;
std::atomic_bool _connected{false}; std::atomic_bool _connected{false};
std::atomic_bool _disconnect{false}; std::atomic_bool _disconnect{false};
std::string _url; std::string _url;
std::string _sessionID; std::string _sessionID;
bool _validateTimestamps = true; bool _validateTimestamps = true;