mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 04:36:21 -05:00
Compare commits
11 Commits
1.32.0-bet
...
1.32.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d55bb6bc86 | ||
|
|
0583331bfd | ||
|
|
6932de866d | ||
|
|
00db0cf7c4 | ||
|
|
e9baf27ca2 | ||
|
|
b1a5db0c9c | ||
|
|
8f3b868fd9 | ||
|
|
b3bf89840b | ||
|
|
84f7d0d214 | ||
|
|
e1164c4fa3 | ||
|
|
4534b23bad |
@@ -22,7 +22,7 @@ runs:
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1.13
|
||||
with:
|
||||
cmake-version: '3.28.x'
|
||||
cmake-version: '3.x.x'
|
||||
|
||||
- name: Restore cached dependencies
|
||||
id: restore-cache
|
||||
|
||||
44
.github/scripts/.build-deps.zsh
vendored
44
.github/scripts/.build-deps.zsh
vendored
@@ -394,8 +394,11 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
|
||||
popd
|
||||
|
||||
pushd ${advss_dep_path}
|
||||
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
|
||||
git clone https://github.com/openssl/openssl.git --branch openssl-3.1.2 --depth 1
|
||||
mv openssl openssl_x86
|
||||
@@ -403,25 +406,27 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
|
||||
log_info "Building openssl x86 ..."
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.9
|
||||
cd openssl_x86
|
||||
./Configure darwin64-x86_64-cc shared
|
||||
make
|
||||
pushd openssl_x86
|
||||
./Configure darwin64-x86_64-cc no-shared no-module no-zlib --prefix=${advss_dep_path}
|
||||
make -j$(nproc)
|
||||
popd
|
||||
|
||||
log_info "Building openssl arm ..."
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.15
|
||||
cd ../openssl_arm
|
||||
./Configure enable-rc5 zlib darwin64-arm64-cc no-asm
|
||||
make
|
||||
pushd openssl_arm
|
||||
./Configure enable-rc5 darwin64-arm64-cc no-shared no-module no-asm no-zlib --prefix=${advss_dep_path}
|
||||
make -j$(nproc)
|
||||
|
||||
log_info "Install openssl ..."
|
||||
make install
|
||||
popd
|
||||
|
||||
log_info "Combine arm and x86 openssl binaries ..."
|
||||
cd ..
|
||||
mkdir openssl-combined
|
||||
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
|
||||
lipo -create openssl_x86/libcrypto.a openssl_arm/libcrypto.a -output ${advss_dep_path}/lib/libcrypto.a
|
||||
lipo -create openssl_x86/libssl.a openssl_arm/libssl.a -output ${advss_dep_path}/lib/libssl.a
|
||||
|
||||
log_info "Clean up openssl dir ..."
|
||||
mv openssl_x86 openssl
|
||||
rm -rf openssl_arm
|
||||
rm -rf openssl_x86 openssl_arm
|
||||
popd
|
||||
|
||||
pushd ${project_root}/deps/libusb
|
||||
@@ -439,14 +444,16 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
mkdir ${project_root}/deps/libusb/out_x86
|
||||
./autogen.sh
|
||||
./configure --host=x86_64-apple-darwin --prefix=${advss_dep_path}
|
||||
make && make install
|
||||
make -j$(nproc)
|
||||
make install
|
||||
|
||||
log_info "Configure libusb arm ..."
|
||||
make clean
|
||||
rm -r ${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
|
||||
make && make install
|
||||
make -j$(nproc)
|
||||
make install
|
||||
|
||||
log_info "Building libusb arm ..."
|
||||
make clean
|
||||
@@ -459,7 +466,8 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.15
|
||||
mkdir ${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 ..."
|
||||
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_STATIC=ON
|
||||
-DPAHO_WITH_MQTT_C=ON
|
||||
-DOPENSSL_ROOT_DIR="${advss_dep_path}"
|
||||
-DPAHO_WITH_SSL=OFF # TODO: figure out linking issues with openssl
|
||||
-DPAHO_WITH_SSL=ON
|
||||
-DOPENSSL_USE_STATIC_LIBS=ON
|
||||
)
|
||||
|
||||
pushd ${mqtt_dir}
|
||||
|
||||
6
.github/scripts/.build.zsh
vendored
6
.github/scripts/.build.zsh
vendored
@@ -253,12 +253,8 @@ ${_usage_host:-}"
|
||||
macos-*)
|
||||
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+=(
|
||||
-DOPENSSL_INCLUDE_DIR="${openssl_include_dir}"
|
||||
-DOPENSSL_LIBRARIES="${openssl_lib_dir}/libcrypto.a;${openssl_lib_dir}/libssl.a"
|
||||
-DCMAKE_PREFIX_PATH="${advss_deps_path}"
|
||||
--preset ${_preset}
|
||||
)
|
||||
|
||||
|
||||
16
.github/scripts/Build-Deps-Windows.ps1
vendored
16
.github/scripts/Build-Deps-Windows.ps1
vendored
@@ -234,8 +234,24 @@ function Build {
|
||||
"-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}"
|
||||
"-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}"
|
||||
"-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..."
|
||||
Invoke-External cmake -S ${MqttPath} -B ${MqttBuildPath} @MqttCmakeArgs
|
||||
|
||||
|
||||
4
.github/scripts/utils.zsh/check_macos
vendored
4
.github/scripts/utils.zsh/check_macos
vendored
@@ -18,5 +18,7 @@ if (( ! ${+commands[brew]} )) {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
4
.github/workflows/build-project.yaml
vendored
4
.github/workflows/build-project.yaml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
description: "Project name detected by parsing build spec file"
|
||||
value: ${{ jobs.check-event.outputs.pluginName }}
|
||||
env:
|
||||
DEP_DIR: .deps/advss-build-dependencies-2
|
||||
DEP_DIR: .deps/advss-build-dependencies-3
|
||||
jobs:
|
||||
check-event:
|
||||
name: Check GitHub Event Data 🔎
|
||||
@@ -213,7 +213,7 @@ jobs:
|
||||
- name: Set up CMake 🏗️
|
||||
uses: jwlawson/actions-setup-cmake@v1.13
|
||||
with:
|
||||
cmake-version: '3.24.x'
|
||||
cmake-version: '3.x.x'
|
||||
|
||||
- name: Set up Homebrew 🍺
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
|
||||
@@ -34,6 +34,11 @@ include(cmake/common/get_git_revision_description.cmake)
|
||||
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
|
||||
git_describe(GIT_TAG)
|
||||
|
||||
# Helper for OpenSSL
|
||||
if(OS_WINDOWS)
|
||||
include(cmake/windows/wingetssl.cmake)
|
||||
endif()
|
||||
|
||||
if(${GIT_TAG} STREQUAL "GIT-NOTFOUND")
|
||||
set(GIT_TAG ${PROJECT_VERSION})
|
||||
endif()
|
||||
|
||||
78
cmake/windows/wingetssl.cmake
Normal file
78
cmake/windows/wingetssl.cmake
Normal 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()
|
||||
@@ -1514,6 +1514,13 @@ AdvSceneSwitcher.mqttConnection.name="Name:"
|
||||
AdvSceneSwitcher.mqttConnection.address="Address:"
|
||||
AdvSceneSwitcher.mqttConnection.username="Username:"
|
||||
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.reconnectDelay="Automatically reconnect after:"
|
||||
AdvSceneSwitcher.mqttConnection.connectOnStart="Connect on startup:"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "macro-action-variable.hpp"
|
||||
#include "help-icon.hpp"
|
||||
#include "json-helpers.hpp"
|
||||
#include "layout-helpers.hpp"
|
||||
#include "macro-condition-edit.hpp"
|
||||
@@ -828,7 +829,10 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
this)),
|
||||
_randomLayout(new QVBoxLayout()),
|
||||
_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)),
|
||||
_entryLayout(new QHBoxLayout())
|
||||
{
|
||||
@@ -859,14 +863,6 @@ MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
_randomNumberStart->setMaximum(9999999999);
|
||||
_randomNumberEnd->setMinimum(-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);
|
||||
|
||||
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
|
||||
|
||||
@@ -2,31 +2,31 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
AutoUpdateTooltipLabel::AutoUpdateTooltipLabel(
|
||||
AutoUpdateHelpIcon::AutoUpdateHelpIcon(
|
||||
QWidget *parent, const std::function<QString()> &updateTooltipCallback,
|
||||
int updateIntervalMs)
|
||||
: QLabel(parent),
|
||||
: HelpIcon("", parent),
|
||||
_callback(updateTooltipCallback),
|
||||
_timer(new QTimer(this)),
|
||||
_updateIntervalMs(updateIntervalMs)
|
||||
{
|
||||
connect(_timer, &QTimer::timeout, this,
|
||||
&AutoUpdateTooltipLabel::UpdateTooltip);
|
||||
&AutoUpdateHelpIcon::UpdateTooltip);
|
||||
}
|
||||
|
||||
void AutoUpdateTooltipLabel::enterEvent(QEnterEvent *event)
|
||||
void AutoUpdateHelpIcon::enterEvent(QEnterEvent *event)
|
||||
{
|
||||
_timer->start(_updateIntervalMs);
|
||||
QLabel::enterEvent(event);
|
||||
}
|
||||
|
||||
void AutoUpdateTooltipLabel::leaveEvent(QEvent *event)
|
||||
void AutoUpdateHelpIcon::leaveEvent(QEvent *event)
|
||||
{
|
||||
_timer->stop();
|
||||
QLabel::leaveEvent(event);
|
||||
}
|
||||
|
||||
void AutoUpdateTooltipLabel::UpdateTooltip()
|
||||
void AutoUpdateHelpIcon::UpdateTooltip()
|
||||
{
|
||||
setToolTip(_callback());
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
#pragma once
|
||||
#include "export-symbol-helper.hpp"
|
||||
#include "help-icon.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <QLabel>
|
||||
#include <QTimer>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class ADVSS_EXPORT AutoUpdateTooltipLabel : public QLabel {
|
||||
class ADVSS_EXPORT AutoUpdateHelpIcon : public HelpIcon {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AutoUpdateTooltipLabel(
|
||||
QWidget *parent,
|
||||
const std::function<QString()> &updateTooltipCallback,
|
||||
int updateIntervalMs = 300);
|
||||
AutoUpdateHelpIcon(QWidget *parent,
|
||||
const std::function<QString()> &updateTooltipCallback,
|
||||
int updateIntervalMs = 300);
|
||||
|
||||
protected:
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
|
||||
@@ -35,6 +35,11 @@ void FileSelection::SetPath(const QString &path)
|
||||
_filePath->setText(path);
|
||||
}
|
||||
|
||||
QString FileSelection::GetPath() const
|
||||
{
|
||||
return _filePath->text();
|
||||
}
|
||||
|
||||
QString FileSelection::ValidPathOrDesktop(const QString &path)
|
||||
{
|
||||
QFileInfo fileInfo(path);
|
||||
|
||||
@@ -22,6 +22,7 @@ public:
|
||||
QWidget *parent = 0);
|
||||
EXPORT void SetPath(const StringVariable &);
|
||||
EXPORT void SetPath(const QString &);
|
||||
EXPORT QString GetPath() const;
|
||||
EXPORT QPushButton *Button() { return _browseButton; }
|
||||
EXPORT static QString ValidPathOrDesktop(const QString &path);
|
||||
|
||||
|
||||
@@ -239,22 +239,26 @@ void SceneSelection::ResolveVariables()
|
||||
_type = Type::SCENE;
|
||||
}
|
||||
|
||||
SceneSelection SceneSelectionWidget::CurrentSelection()
|
||||
static obs_weak_canvas_t *getWeakRefToMainCanvas()
|
||||
{
|
||||
SceneSelection s;
|
||||
|
||||
static auto mainCanvas = obs_get_main_canvas();
|
||||
static auto mainCanvasWeak = obs_canvas_get_weak_canvas(mainCanvas);
|
||||
static auto canvas = obs_get_main_canvas();
|
||||
static auto weakCanvas = obs_canvas_get_weak_canvas(canvas);
|
||||
[[maybe_unused]] static const bool _ = []() {
|
||||
// Let's just hope we don't have to deal with selecting scenes
|
||||
// when the OBS main canvas gets deleted and release the
|
||||
// references here already to avoid reporting leaks on shutdown
|
||||
obs_canvas_release(mainCanvas);
|
||||
obs_weak_canvas_release(mainCanvasWeak);
|
||||
obs_canvas_release(canvas);
|
||||
obs_weak_canvas_release(weakCanvas);
|
||||
return true;
|
||||
}();
|
||||
return weakCanvas;
|
||||
}
|
||||
|
||||
s._canvas = _forceMainCanvas ? OBSWeakCanvas(mainCanvasWeak)
|
||||
SceneSelection SceneSelectionWidget::CurrentSelection()
|
||||
{
|
||||
SceneSelection s;
|
||||
|
||||
s._canvas = _forceMainCanvas ? OBSWeakCanvas(getWeakRefToMainCanvas())
|
||||
: _canvas->GetCanvas();
|
||||
|
||||
const int idx = _scenes->currentIndex();
|
||||
@@ -342,6 +346,10 @@ void SceneSelectionWidget::Reset()
|
||||
|
||||
void SceneSelectionWidget::PopulateSceneSelection(obs_weak_canvas_t *canvas)
|
||||
{
|
||||
if (_forceMainCanvas) {
|
||||
canvas = getWeakRefToMainCanvas();
|
||||
}
|
||||
|
||||
_scenes->clear();
|
||||
if ((_current || _previous)) {
|
||||
const bool isMain = IsMainCanvas(canvas);
|
||||
@@ -418,7 +426,7 @@ SceneSelectionWidget::SceneSelectionWidget(QWidget *parent, bool variables,
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
if (GetCanvasCount() <= 1) {
|
||||
_canvas->hide();
|
||||
LockToMainCanvas();
|
||||
}
|
||||
|
||||
Resize();
|
||||
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
bool previous = false, bool current = false,
|
||||
bool preview = false);
|
||||
EXPORT void SetScene(const SceneSelection &);
|
||||
void LockToMainCanvas();
|
||||
EXPORT void LockToMainCanvas();
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
@@ -199,7 +199,17 @@ std::chrono::high_resolution_clock::time_point GetLastSceneChangeTime()
|
||||
|
||||
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()
|
||||
|
||||
@@ -299,9 +299,17 @@ void TempVariableRef::Save(obs_data_t *obj, Macro *macro,
|
||||
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::weak_ptr<Macro> macro;
|
||||
for (const auto ¯oShared : GetMacros()) {
|
||||
if (macroShared.get() == macroPtr) {
|
||||
macro = macroShared;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (macro.expired()) {
|
||||
_segment.reset();
|
||||
return;
|
||||
}
|
||||
@@ -324,10 +332,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;
|
||||
}
|
||||
|
||||
Macro *macro = nullptr;
|
||||
for (int i = 0; i < _depth; i++) {
|
||||
macro = getParentMacro(macro);
|
||||
macro = getParentMacro(childMacro.get());
|
||||
}
|
||||
|
||||
if (!macro) {
|
||||
@@ -404,9 +419,8 @@ TempVariableSelection::TempVariableSelection(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_selection(new FilterComboBox(
|
||||
this, obs_module_text("AdvSceneSwitcher.tempVar.select"))),
|
||||
_info(new AutoUpdateTooltipLabel(this, [this]() {
|
||||
return SetupInfoLabel();
|
||||
}))
|
||||
_info(new AutoUpdateHelpIcon(this,
|
||||
[this]() { return SetupInfoLabel(); }))
|
||||
|
||||
{
|
||||
MacroEdit *edit = findMacroEditParent(parent);
|
||||
@@ -416,12 +430,6 @@ TempVariableSelection::TempVariableSelection(QWidget *parent)
|
||||
_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();
|
||||
|
||||
_selection->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
|
||||
@@ -72,7 +72,7 @@ private:
|
||||
enum class SegmentType { NONE, CONDITION, ACTION, ELSEACTION };
|
||||
SegmentType GetType() const;
|
||||
int GetIdx() const;
|
||||
void PostLoad(int idx, SegmentType, Macro *);
|
||||
void PostLoad(int idx, SegmentType, const std::weak_ptr<Macro> &);
|
||||
|
||||
std::string _id = "";
|
||||
std::weak_ptr<MacroSegment> _segment;
|
||||
@@ -105,7 +105,7 @@ private:
|
||||
MacroSegment *GetSegment() const;
|
||||
|
||||
FilterComboBox *_selection;
|
||||
AutoUpdateTooltipLabel *_info;
|
||||
AutoUpdateHelpIcon *_info;
|
||||
std::vector<MacroEdit *> _macroEdits;
|
||||
};
|
||||
|
||||
|
||||
@@ -250,6 +250,10 @@ MacroActionProjectorEdit::MacroActionProjectorEdit(
|
||||
_regex(new RegexConfigWidget(this)),
|
||||
_layout(new QHBoxLayout(this))
|
||||
{
|
||||
// The obs_frontend_open_projector() function does not seem to support
|
||||
// scenes of secondary canvases
|
||||
_scenes->LockToMainCanvas();
|
||||
|
||||
populateActionSelection(_actions);
|
||||
populateWindowTypes(_windowTypes);
|
||||
populateSelectionTypes(_types);
|
||||
@@ -396,26 +400,29 @@ void MacroActionProjectorEdit::SetWidgetVisibility()
|
||||
return;
|
||||
}
|
||||
|
||||
_projectorWindowName->setVisible(_entryData->_action ==
|
||||
const auto &action = _entryData->_action;
|
||||
const auto &type = _entryData->_type;
|
||||
|
||||
_projectorWindowName->setVisible(action ==
|
||||
MacroActionProjector::Action::CLOSE);
|
||||
_regex->setVisible(_entryData->_action ==
|
||||
MacroActionProjector::Action::CLOSE);
|
||||
_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 &&
|
||||
_regex->setVisible(action == MacroActionProjector::Action::CLOSE);
|
||||
_types->setVisible(action == MacroActionProjector::Action::OPEN);
|
||||
_windowTypes->setVisible(action == MacroActionProjector::Action::OPEN);
|
||||
_scenes->setVisible(action == MacroActionProjector::Action::OPEN &&
|
||||
type == MacroActionProjector::Type::SCENE);
|
||||
_sources->setVisible(action == MacroActionProjector::Action::OPEN &&
|
||||
type == MacroActionProjector::Type::SOURCE);
|
||||
_monitors->setVisible(action == MacroActionProjector::Action::OPEN &&
|
||||
_entryData->_fullscreen);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
|
||||
if (action == MacroActionProjector::Action::CLOSE) {
|
||||
RemoveStretchIfPresent(_layout);
|
||||
} else {
|
||||
AddStretchIfNecessary(_layout);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -240,7 +240,7 @@ bool MacroActionSwitchScene::Load(obs_data_t *obj)
|
||||
GetWeakCanvasByName(obs_data_get_string(obj, "canvas"));
|
||||
} else {
|
||||
OBSCanvasAutoRelease main = obs_get_main_canvas();
|
||||
_canvas = OBSGetWeakRef(main);
|
||||
_canvas = obs_canvas_get_weak_canvas(main);
|
||||
}
|
||||
|
||||
_index.Save(obj, "index");
|
||||
|
||||
@@ -242,7 +242,7 @@ MacroConditionStreamEdit::MacroConditionStreamEdit(
|
||||
_keyFrameInterval(new VariableSpinBox()),
|
||||
_streamKey(new VariableLineEdit(this)),
|
||||
_serviceName(new VariableLineEdit(this)),
|
||||
_currentService(new AutoUpdateTooltipLabel(
|
||||
_currentService(new AutoUpdateHelpIcon(
|
||||
this,
|
||||
[]() {
|
||||
QString formatString = obs_module_text(
|
||||
@@ -255,13 +255,6 @@ MacroConditionStreamEdit::MacroConditionStreamEdit(
|
||||
_keyFrameInterval->setMinimum(0);
|
||||
_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);
|
||||
|
||||
populateConditionSelection(_conditions);
|
||||
|
||||
@@ -15,12 +15,15 @@ if(NOT TARGET httplib)
|
||||
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()
|
||||
if(OS_MACOS)
|
||||
set(OPENSSL_USE_STATIC_LIBS
|
||||
ON
|
||||
CACHE BOOL "Use static OpenSSL" FORCE)
|
||||
endif()
|
||||
find_package(OpenSSL)
|
||||
if(NOT OPENSSL_FOUND)
|
||||
message(WARNING "OpenSSL not found!\n" "HTTP support will be disabled!\n\n")
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(ZLIB)
|
||||
|
||||
@@ -3,6 +3,13 @@ project(advanced-scene-switcher-mqtt)
|
||||
|
||||
# --- 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)
|
||||
if(NOT PahoMqttCpp_FOUND)
|
||||
message(WARNING "PahoMqttCpp not found!\n"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "mqtt-helpers.hpp"
|
||||
#include "help-icon.hpp"
|
||||
#include "layout-helpers.hpp"
|
||||
#include "log-helper.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
@@ -22,6 +23,10 @@ MqttConnection::MqttConnection(const MqttConnection &other)
|
||||
_uri(other._uri),
|
||||
_username(other._username),
|
||||
_password(other._password),
|
||||
_trustStore(other._trustStore),
|
||||
_keyStore(other._keyStore),
|
||||
_privateKey(other._privateKey),
|
||||
_verifyServerCert(other._verifyServerCert),
|
||||
_connectOnStart(other._connectOnStart),
|
||||
_reconnect(other._reconnect),
|
||||
_reconnectDelay(other._reconnectDelay)
|
||||
@@ -30,12 +35,20 @@ MqttConnection::MqttConnection(const MqttConnection &other)
|
||||
|
||||
MqttConnection::MqttConnection(const std::string &name, const std::string &uri,
|
||||
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)
|
||||
: Item(name),
|
||||
_uri(uri),
|
||||
_username(username),
|
||||
_password(password),
|
||||
_trustStore(trustStore),
|
||||
_keyStore(keyStore),
|
||||
_privateKey(privateKey),
|
||||
_verifyServerCert(verifyServerCert),
|
||||
_connectOnStart(connectOnStart),
|
||||
_reconnect(reconnect),
|
||||
_reconnectDelay(reconnectDelay)
|
||||
@@ -99,26 +112,41 @@ void MqttConnection::ConnectThread()
|
||||
};
|
||||
|
||||
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 {
|
||||
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\" ...",
|
||||
_name.c_str());
|
||||
auto tok = _client->connect(connOpts);
|
||||
@@ -220,6 +248,10 @@ void MqttConnection::Load(obs_data_t *data)
|
||||
_uri = obs_data_get_string(data, "uri");
|
||||
_username = obs_data_get_string(data, "username");
|
||||
_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");
|
||||
_reconnect = obs_data_get_bool(data, "reconnect");
|
||||
_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, "username", _username.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, "reconnect", _reconnect);
|
||||
obs_data_set_int(data, "reconnectDelay", _reconnectDelay);
|
||||
@@ -308,6 +344,10 @@ MqttConnectionSettingsDialog::MqttConnectionSettingsDialog(
|
||||
_username(new QLineEdit()),
|
||||
_password(new QLineEdit()),
|
||||
_showPassword(new QPushButton()),
|
||||
_trustStore(new FileSelection()),
|
||||
_keyStore(new FileSelection()),
|
||||
_privateKey(new FileSelection()),
|
||||
_verifyServerCert(new QCheckBox()),
|
||||
_topics(new MqttTopicListWidget(this)),
|
||||
_connectOnStart(new QCheckBox()),
|
||||
_reconnect(new QCheckBox()),
|
||||
@@ -324,6 +364,10 @@ MqttConnectionSettingsDialog::MqttConnectionSettingsDialog(
|
||||
_uri->setText(QString::fromStdString(connection._uri));
|
||||
_username->setText(QString::fromStdString(connection._username));
|
||||
_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);
|
||||
_reconnectDelay->setMaximum(9999);
|
||||
_reconnectDelay->setSuffix("s");
|
||||
@@ -364,6 +408,41 @@ MqttConnectionSettingsDialog::MqttConnectionSettingsDialog(
|
||||
passLayout->addWidget(_showPassword);
|
||||
_layout->addLayout(passLayout, row, 1);
|
||||
++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(
|
||||
obs_module_text("AdvSceneSwitcher.mqttConnection.topics")));
|
||||
++row;
|
||||
@@ -412,6 +491,10 @@ bool MqttConnectionSettingsDialog::AskForSettings(QWidget *parent,
|
||||
connection._uri = dialog._uri->text().toStdString();
|
||||
connection._username = dialog._username->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._qos = dialog._topics->GetQoS();
|
||||
connection._connectOnStart = dialog._connectOnStart->isChecked();
|
||||
@@ -455,6 +538,10 @@ void MqttConnectionSettingsDialog::TestConnection()
|
||||
connection->_uri = _uri->text().toStdString();
|
||||
connection->_username = _username->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->_qos = _topics->GetQoS();
|
||||
connection->_connectOnStart = false;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "message-dispatcher.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "item-selection-helpers.hpp"
|
||||
#include "topic-selection.hpp"
|
||||
|
||||
@@ -27,6 +28,9 @@ public:
|
||||
MqttConnection(const MqttConnection &other);
|
||||
MqttConnection(const std::string &name, const std::string &uri,
|
||||
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);
|
||||
static std::shared_ptr<Item> Create()
|
||||
{
|
||||
@@ -51,8 +55,12 @@ private:
|
||||
std::string _uri = "mqtt://localhost:1883";
|
||||
std::string _username = "user";
|
||||
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::thread _thread;
|
||||
@@ -91,6 +99,10 @@ private:
|
||||
QLineEdit *_username;
|
||||
QLineEdit *_password;
|
||||
QPushButton *_showPassword;
|
||||
FileSelection *_trustStore;
|
||||
FileSelection *_keyStore;
|
||||
FileSelection *_privateKey;
|
||||
QCheckBox *_verifyServerCert;
|
||||
MqttTopicListWidget *_topics;
|
||||
QCheckBox *_connectOnStart;
|
||||
QCheckBox *_reconnect;
|
||||
|
||||
@@ -15,13 +15,15 @@ if(NOT TARGET httplib)
|
||||
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"
|
||||
"Twitch support will be disabled!\n\n")
|
||||
return()
|
||||
endif()
|
||||
if(OS_MACOS)
|
||||
set(OPENSSL_USE_STATIC_LIBS
|
||||
ON
|
||||
CACHE BOOL "Use static OpenSSL" FORCE)
|
||||
endif()
|
||||
find_package(OpenSSL)
|
||||
if(NOT OPENSSL_FOUND)
|
||||
message(WARNING "OpenSSL not found!\n" "Twitch support will be disabled!\n\n")
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(ZLIB)
|
||||
|
||||
@@ -32,28 +32,17 @@ static const int reconnectDelay = 15;
|
||||
|
||||
#undef DispatchMessage
|
||||
|
||||
EventSub::EventSub() : QObject(nullptr)
|
||||
EventSub::EventSub()
|
||||
: QObject(nullptr),
|
||||
_client(std::make_unique<EventSubWSClient>())
|
||||
{
|
||||
_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
|
||||
SetupClient(*_client);
|
||||
|
||||
_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));
|
||||
_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));
|
||||
|
||||
#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();
|
||||
RegisterInstance();
|
||||
}
|
||||
@@ -82,28 +71,34 @@ void EventSub::UnregisterInstance()
|
||||
|
||||
void EventSub::ConnectThread()
|
||||
{
|
||||
while (!_disconnect) {
|
||||
std::unique_lock<std::mutex> lock(_waitMtx);
|
||||
_client.reset();
|
||||
_connected = true;
|
||||
websocketpp::lib::error_code ec;
|
||||
EventSubWSClient::connection_ptr con =
|
||||
_client.get_connection(_url, ec);
|
||||
if (ec) {
|
||||
blog(LOG_INFO, "Twitch EventSub failed: %s",
|
||||
ec.message().c_str());
|
||||
} else {
|
||||
_client.connect(con);
|
||||
_connection = connection_hdl(con);
|
||||
_client.run();
|
||||
}
|
||||
_client->reset();
|
||||
_connected = true;
|
||||
websocketpp::lib::error_code ec;
|
||||
EventSubWSClient::connection_ptr con =
|
||||
_client->get_connection(_url, ec);
|
||||
if (ec) {
|
||||
blog(LOG_INFO, "Twitch EventSub failed: %s",
|
||||
ec.message().c_str());
|
||||
} else {
|
||||
_client->connect(con);
|
||||
_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,
|
||||
"Twitch EventSub trying to reconnect to in %d seconds.",
|
||||
reconnectDelay);
|
||||
_cv.wait_for(lock, std::chrono::seconds(reconnectDelay));
|
||||
}
|
||||
_connected = false;
|
||||
Connect();
|
||||
});
|
||||
thread.detach();
|
||||
}
|
||||
|
||||
void EventSub::Connect()
|
||||
@@ -137,8 +132,8 @@ void EventSub::Disconnect()
|
||||
std::lock_guard<std::mutex> lock(_connectMtx);
|
||||
_disconnect = true;
|
||||
websocketpp::lib::error_code ec;
|
||||
_client.close(_connection, websocketpp::close::status::normal,
|
||||
"Twitch EventSub stopping", ec);
|
||||
_client->close(_connection, websocketpp::close::status::normal,
|
||||
"Twitch EventSub stopping", ec);
|
||||
{
|
||||
std::unique_lock<std::mutex> waitLock(_waitMtx);
|
||||
_cv.notify_all();
|
||||
@@ -146,8 +141,8 @@ void EventSub::Disconnect()
|
||||
|
||||
while (_connected) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
_client.close(_connection, websocketpp::close::status::normal,
|
||||
"Twitch EventSub stopping", ec);
|
||||
_client->close(_connection, websocketpp::close::status::normal,
|
||||
"Twitch EventSub stopping", ec);
|
||||
}
|
||||
|
||||
if (_thread.joinable()) {
|
||||
@@ -238,6 +233,24 @@ std::string EventSub::AddEventSubscription(std::shared_ptr<TwitchToken> token,
|
||||
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)
|
||||
{
|
||||
vblog(LOG_INFO, "Twitch EventSub connection opened");
|
||||
@@ -303,13 +316,14 @@ bool EventSub::IsValidID(const std::string &id)
|
||||
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) {
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
if (message->get_opcode() != websocketpp::frame::opcode::text) {
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string payload = message->get_payload();
|
||||
@@ -317,7 +331,7 @@ void EventSub::OnMessage(connection_hdl, EventSubWSClient::message_ptr message)
|
||||
if (!json) {
|
||||
blog(LOG_ERROR, "invalid JSON payload received for '%s'",
|
||||
payload.c_str());
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
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");
|
||||
if (_validateTimestamps && !isValidTimestamp(timestamp)) {
|
||||
blog(LOG_WARNING,
|
||||
"Discarding Twitch EventSub with invalid timestamp %s",
|
||||
"discarding Twitch EventSub with invalid timestamp %s",
|
||||
timestamp.c_str());
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
std::string id = obs_data_get_string(metadata, "message_id");
|
||||
if (!IsValidMessageID(id)) {
|
||||
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;
|
||||
}
|
||||
std::string messageType = obs_data_get_string(metadata, "message_type");
|
||||
OBSDataAutoRelease payloadJson = obs_data_get_obj(json, "payload");
|
||||
if (messageType == "session_welcome") {
|
||||
HandleWelcome(payloadJson);
|
||||
} else if (messageType == "session_keepalive") {
|
||||
|
||||
const auto &type = msg->type;
|
||||
const auto &data = msg->payload;
|
||||
if (type == "session_welcome") {
|
||||
HandleWelcome(data);
|
||||
} else if (type == "session_keepalive") {
|
||||
HandleKeepAlive();
|
||||
} else if (messageType == "notification") {
|
||||
HandleNotification(payloadJson);
|
||||
} else if (messageType == "session_reconnect") {
|
||||
HandleReconnect(payloadJson);
|
||||
} else if (messageType == "revocation") {
|
||||
HandleRevocation(payloadJson);
|
||||
} else if (type == "notification") {
|
||||
HandleNotification(data);
|
||||
} else if (type == "session_reconnect") {
|
||||
HandleServerMigration(data);
|
||||
} else if (type == "revocation") {
|
||||
HandleRevocation(data);
|
||||
} else {
|
||||
vblog(LOG_INFO, "ignoring message of unknown type '%s'",
|
||||
messageType.c_str());
|
||||
vblog(LOG_INFO,
|
||||
"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);
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
OBSDataAutoRelease session = obs_data_get_obj(data, "session");
|
||||
auto id = obs_data_get_string(session, "id");
|
||||
if (!IsValidID(id)) {
|
||||
@@ -388,8 +504,25 @@ void EventSub::HandleReconnect(obs_data_t *data)
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// Implement proper reconnect handing to avoid dropped events
|
||||
const std::string newURL =
|
||||
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)
|
||||
@@ -425,23 +558,41 @@ void EventSub::HandleRevocation(obs_data_t *data)
|
||||
|
||||
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 reason = con->get_remote_close_reason();
|
||||
const auto code = con->get_remote_close_code();
|
||||
blog(LOG_INFO, "Twitch EventSub connection closed: %s / %s (%d)",
|
||||
msg.c_str(), reason.c_str(), code);
|
||||
ClearActiveSubscriptions();
|
||||
|
||||
if (_migrating) {
|
||||
ClearActiveSubscriptions();
|
||||
}
|
||||
_connected = false;
|
||||
|
||||
if (_disconnect) {
|
||||
return;
|
||||
}
|
||||
|
||||
WaitAndReconnect();
|
||||
}
|
||||
|
||||
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();
|
||||
blog(LOG_INFO, "Twitch EventSub connection failed: %s", msg.c_str());
|
||||
ClearActiveSubscriptions();
|
||||
|
||||
if (!_migrating) {
|
||||
ClearActiveSubscriptions();
|
||||
}
|
||||
_connected = false;
|
||||
|
||||
if (_disconnect) {
|
||||
return;
|
||||
}
|
||||
|
||||
WaitAndReconnect();
|
||||
}
|
||||
|
||||
bool Subscription::operator<(const Subscription &other) const
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
namespace advss {
|
||||
|
||||
#ifdef USE_TWITCH_CLI_MOCK
|
||||
typedef websocketpp::client<websocketpp::config::asio_client> EventSubWSClient;
|
||||
using EventSubWSClient = websocketpp::client<websocketpp::config::asio_client>;
|
||||
#else
|
||||
typedef websocketpp::client<websocketpp::config::asio_tls_client>
|
||||
EventSubWSClient;
|
||||
using EventSubWSClient =
|
||||
websocketpp::client<websocketpp::config::asio_tls_client>;
|
||||
#endif
|
||||
|
||||
struct Event;
|
||||
@@ -61,12 +61,16 @@ public:
|
||||
void EnableTimestampValidation(bool enable);
|
||||
|
||||
private:
|
||||
static void SetupClient(EventSubWSClient &);
|
||||
|
||||
void OnOpen(connection_hdl hdl);
|
||||
void OnMessage(connection_hdl hdl,
|
||||
EventSubWSClient::message_ptr message);
|
||||
void OnClose(connection_hdl hdl);
|
||||
void OnFail(connection_hdl hdl);
|
||||
|
||||
void ConnectThread();
|
||||
void WaitAndReconnect();
|
||||
|
||||
bool IsValidMessageID(const std::string &);
|
||||
bool IsValidID(const std::string &);
|
||||
@@ -74,20 +78,39 @@ private:
|
||||
void HandleWelcome(obs_data_t *);
|
||||
void HandleKeepAlive() const;
|
||||
void HandleNotification(obs_data_t *);
|
||||
void HandleReconnect(obs_data_t *);
|
||||
void HandleServerMigration(obs_data_t *);
|
||||
void HandleRevocation(obs_data_t *);
|
||||
|
||||
void RegisterInstance();
|
||||
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;
|
||||
|
||||
std::unique_ptr<EventSubWSClient> _migrationClient;
|
||||
connection_hdl _migrationConnection;
|
||||
std::atomic_bool _migrating{false};
|
||||
std::string _migrationSessionID;
|
||||
|
||||
std::thread _thread;
|
||||
std::mutex _waitMtx;
|
||||
std::mutex _connectMtx;
|
||||
std::condition_variable _cv;
|
||||
std::atomic_bool _connected{false};
|
||||
std::atomic_bool _disconnect{false};
|
||||
|
||||
std::string _url;
|
||||
std::string _sessionID;
|
||||
bool _validateTimestamps = true;
|
||||
|
||||
Reference in New Issue
Block a user