Compare commits

..

14 Commits

Author SHA1 Message Date
WarmUpTill
4c977a2e2f Add "None" support for scene items to Transition action 2026-06-25 21:25:48 +02:00
WarmUpTill
0f1273ab4c Add support for "None" transition type to scene item visibility action 2026-06-25 21:18:57 +02:00
WarmUpTill
67f6b1b459 Add "current transition" support to scene item visibility action 2026-06-25 20:59:36 +02:00
WarmUpTill
a04c51397d Fix variables of nested macros' actions not being resolved
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
2026-06-17 21:12:48 +02:00
WarmUpTill
2073ef6ce9 Fix nested macro actions being executed twice when added to a queue 2026-06-17 21:12:48 +02:00
WarmUpTill
7dfd9ddcb5 Print plugin enabled/disabled summary during CMake configure
Some checks are pending
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
2026-06-16 17:40:22 +02:00
WarmUpTill
6215c39024 Don't fail cmake configure step if openvr deps are not met 2026-06-16 17:40:22 +02:00
WarmUpTill
1c856911e8 Set default value for CMAKE_GENERATOR_PLATFORM on Windows 2026-06-16 17:40:22 +02:00
WarmUpTill
ed17c2ac6d Disconnect Twitch chat and event connection on plugin stop
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
2026-06-13 22:05:32 +02:00
WarmUpTill
1ce332a91d Track all kWin wayland windows 2026-06-13 22:01:52 +02:00
WarmUpTill
2aa27fc051 Fix window conditions not triggering for native Wayland windows
When KWin compat is active, GetWindows() only returned XWayland windows
since it relied on X11's _NET_CLIENT_LIST. Native Wayland windows were
invisible to conditions even though GetCurrentWindowTitle() reported them
correctly via the KWin DBus notifier.
2026-06-13 22:01:52 +02:00
WarmUpTill
31b239d5dc Focus segment type selection when new segment is added
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
2026-06-11 18:25:29 +02:00
WarmUpTill
7c3779e287 Sort macro export dialog resources alphabetically and add scrollbars 2026-06-11 18:25:29 +02:00
WarmUpTill
3f13c33b10 Fix crash on transition start in studio mode 2026-06-11 18:25:29 +02:00
30 changed files with 384 additions and 83 deletions

View File

@@ -157,7 +157,9 @@ else()
set_target_properties(${target} PROPERTIES INSTALL_RPATH
"$ORIGIN:$ORIGIN/..")
endif()
message(STATUS "ADVSS: ENABLED PLUGIN ${target}")
get_filename_component(_plugin_dir_name "${CMAKE_CURRENT_SOURCE_DIR}" NAME)
set_property(GLOBAL APPEND PROPERTY ADVSS_PLUGINS_ENABLED
${_plugin_dir_name})
endfunction()
function(install_advss_plugin_dependency_target target dep)

View File

@@ -6,7 +6,11 @@ include(buildspec_common)
# _check_dependencies_windows: Set up Windows slice for _check_dependencies
function(_check_dependencies_windows)
set(arch ${CMAKE_GENERATOR_PLATFORM})
if(CMAKE_GENERATOR_PLATFORM)
set(arch ${CMAKE_GENERATOR_PLATFORM})
else()
set(arch x64)
endif()
set(platform windows-${arch})
set(dependencies_dir "${CMAKE_CURRENT_SOURCE_DIR}/.deps")

View File

@@ -2546,6 +2546,7 @@ AdvSceneSwitcher.selectCurrentScene="Current Scene"
AdvSceneSwitcher.selectPreviewScene="Preview Scene"
AdvSceneSwitcher.selectAnyScene="Any Scene"
AdvSceneSwitcher.currentTransition="Current Transition"
AdvSceneSwitcher.noneTransition="None"
AdvSceneSwitcher.anyTransition="Any Transition"
AdvSceneSwitcher.selectTransition="--select transition--"
AdvSceneSwitcher.selectWindow="--select window--"

View File

@@ -294,6 +294,30 @@ std::vector<WindowInfo> GetWindows(const WindowQueryOptions &options)
result.emplace_back(std::move(info));
}
// When KWin compat is active, native Wayland windows are not tracked by
// X11 and therefore do not appear in the list above. Merge in any window
// reported by KWin that is not already present (matched by title) so that
// title and focus conditions work for native Wayland windows.
if (KWin) {
for (const auto &[pid, title] :
FocusNotifier::getWindowList()) {
bool found = false;
for (const auto &info : result) {
if (info.title == title) {
found = true;
break;
}
}
if (!found) {
WindowInfo waylandWindow;
waylandWindow.title = title;
waylandWindow.focused =
(title == foregroundTitle);
result.emplace_back(std::move(waylandWindow));
}
}
}
return result;
}

View File

@@ -12,35 +12,82 @@
namespace advss {
std::mutex FocusNotifier::_mutex;
int FocusNotifier::activePID = -1;
std::string FocusNotifier::activeTitle = {};
std::map<std::string, std::pair<int, std::string>> FocusNotifier::_windows;
int FocusNotifier::getActiveWindowPID()
{
std::lock_guard<std::mutex> lock(_mutex);
return activePID;
}
std::string FocusNotifier::getActiveWindowTitle()
{
std::lock_guard<std::mutex> lock(_mutex);
return activeTitle;
}
std::vector<std::pair<int, std::string>> FocusNotifier::getWindowList()
{
std::lock_guard<std::mutex> lock(_mutex);
std::vector<std::pair<int, std::string>> result;
result.reserve(_windows.size());
for (const auto &entry : _windows) {
result.emplace_back(entry.second);
}
return result;
}
void FocusNotifier::focusChanged(const int pid)
{
std::lock_guard<std::mutex> lock(_mutex);
activePID = pid;
}
void FocusNotifier::focusTitle(const QString &title)
{
std::lock_guard<std::mutex> lock(_mutex);
activeTitle = title.toStdString();
}
void FocusNotifier::focusUpdate(const int pid, const QString &title)
{
std::lock_guard<std::mutex> lock(_mutex);
activePID = pid;
activeTitle = title.toStdString();
}
void FocusNotifier::windowAdded(const QString &id, const int pid,
const QString &title)
{
std::lock_guard<std::mutex> lock(_mutex);
_windows[id.toStdString()] = {pid, title.toStdString()};
}
void FocusNotifier::windowRemoved(const QString &id)
{
std::lock_guard<std::mutex> lock(_mutex);
_windows.erase(id.toStdString());
}
void FocusNotifier::windowTitleChanged(const QString &id, const QString &title)
{
std::lock_guard<std::mutex> lock(_mutex);
auto it = _windows.find(id.toStdString());
if (it != _windows.end()) {
it->second.second = title.toStdString();
}
}
bool isKWinAvailable()
{
const QDBusConnectionInterface *interface =
QDBusConnection::sessionBus().interface();
if (!interface)
if (!interface) {
return false;
}
const QStringList services =
interface->registeredServiceNames().value();
@@ -53,25 +100,32 @@ bool startKWinScript(QString &scriptObjectPath)
"/tmp/AdvancedSceneSwitcher/KWinFocusNotifier.js";
const QString script =
R"(workspace.windowActivated.connect(function(client) {
if (!client) return;
if (!client.pid) return;
if (!client.caption) return;
R"(var adss = "com.github.AdvancedSceneSwitcher";
var adssPath = "/com/github/AdvancedSceneSwitcher";
callDBus(
"com.github.AdvancedSceneSwitcher",
"/com/github/AdvancedSceneSwitcher",
"com.github.AdvancedSceneSwitcher",
"focusChanged",
client.pid
);
callDBus(
"com.github.AdvancedSceneSwitcher",
"/com/github/AdvancedSceneSwitcher",
"com.github.AdvancedSceneSwitcher",
"focusTitle",
client.caption
);
function trackWindow(window) {
var id = window.internalId.toString();
callDBus(adss, adssPath, adss, "windowAdded", id, window.pid, window.caption);
window.captionChanged.connect(function() {
callDBus(adss, adssPath, adss, "windowTitleChanged", id, window.caption);
});
}
var windows = workspace.windowList();
for (var i = 0; i < windows.length; i++) {
trackWindow(windows[i]);
}
workspace.windowAdded.connect(trackWindow);
workspace.windowRemoved.connect(function(window) {
callDBus(adss, adssPath, adss, "windowRemoved", window.internalId.toString());
});
workspace.windowActivated.connect(function(client) {
callDBus(adss, adssPath, adss, "focusUpdate",
client ? client.pid : 0,
client ? client.caption : "");
}))";
if (const QDir dir; !dir.mkpath(QFileInfo(scriptPath).absolutePath())) {

View File

@@ -2,7 +2,11 @@
#include <QObject>
#include <QString>
#include <map>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
namespace advss {
@@ -10,18 +14,27 @@ class FocusNotifier final : public QObject {
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "com.github.AdvancedSceneSwitcher")
static std::mutex _mutex;
static int activePID;
static std::string activeTitle;
// Maps KWin internal window ID to {pid, title}
static std::map<std::string, std::pair<int, std::string>> _windows;
public:
using QObject::QObject;
static int getActiveWindowPID();
static std::string getActiveWindowTitle();
static std::vector<std::pair<int, std::string>> getWindowList();
public slots:
void focusChanged(const int pid);
void focusTitle(const QString &title);
void focusUpdate(const int pid, const QString &title);
void windowAdded(const QString &id, const int pid,
const QString &title);
void windowRemoved(const QString &id);
void windowTitleChanged(const QString &id, const QString &title);
};
bool isKWinAvailable();

View File

@@ -164,4 +164,9 @@ std::shared_ptr<MacroSegment> MacroActionEdit::Data() const
return *_entryData;
}
void MacroActionEdit::FocusTypeSelection()
{
_actionSelection->setFocus();
}
} // namespace advss

View File

@@ -17,6 +17,7 @@ public:
std::shared_ptr<MacroAction> * = nullptr);
void SetupWidgets(bool basicSetup = false);
void SetEntryData(std::shared_ptr<MacroAction> *);
void FocusTypeSelection() override;
private slots:
void ActionSelectionChanged(const QString &text);

View File

@@ -263,6 +263,7 @@ bool MacroActionMacro::Load(obs_data_t *obj)
if (obs_data_has_user_value(obj, "nestedMacro")) {
OBSDataAutoRelease nestedMacroData =
obs_data_get_obj(obj, "nestedMacro");
_nestedMacro = std::make_shared<Macro>();
_nestedMacro->Load(nestedMacroData);
}
@@ -309,6 +310,15 @@ void MacroActionMacro::ResolveVariablesToFixedValues()
{
_actionIndex.ResolveVariables();
_label.ResolveVariables();
if (_action == Action::NESTED_MACRO) {
for (auto &action : _nestedMacro->Actions()) {
action->ResolveVariablesToFixedValues();
}
for (auto &action : _nestedMacro->ElseActions()) {
action->ResolveVariablesToFixedValues();
}
}
}
static void runActionsHelper(Macro *macro, bool runElseActions, bool setInputs,

View File

@@ -305,4 +305,9 @@ std::shared_ptr<MacroSegment> MacroConditionEdit::Data() const
return *_entryData;
}
void MacroConditionEdit::FocusTypeSelection()
{
_conditionSelection->setFocus();
}
} // namespace advss

View File

@@ -43,6 +43,7 @@ public:
void SetRootNode(bool);
void SetupWidgets(bool basicSetup = false);
void SetEntryData(std::shared_ptr<MacroCondition> *);
void FocusTypeSelection() override;
private slots:
void LogicSelectionChanged(int idx);

View File

@@ -650,6 +650,18 @@ void MacroEdit::HighlightCondition(int idx, QColor color) const
ui->conditionsList->Highlight(idx, color);
}
void MacroEdit::ScrollAndFocusNewSegment(MacroSegmentList *list, int newIdx,
int *currentIdx)
{
QTimer::singleShot(0, this, [this, list, newIdx, currentIdx]() {
list->ensureWidgetVisible(list->WidgetAt(*currentIdx));
auto *widget = list->WidgetAt(newIdx);
if (widget) {
widget->FocusTypeSelection();
}
});
}
static void resetSegmentHighlights(MacroSegmentList *list)
{
MacroSegmentEdit *widget = nullptr;
@@ -1087,10 +1099,7 @@ void MacroEdit::AddMacroAction(Macro *macro, int idx, const std::string &id,
ui->actionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged());
QTimer::singleShot(0, this, [this]() {
ui->actionsList->ensureWidgetVisible(
ui->actionsList->WidgetAt(currentActionIdx));
});
ScrollAndFocusNewSegment(ui->actionsList, idx, &currentActionIdx);
}
void MacroEdit::AddMacroAction(int idx)
@@ -1398,10 +1407,8 @@ void MacroEdit::AddMacroElseAction(Macro *macro, int idx, const std::string &id,
ui->elseActionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged());
QTimer::singleShot(0, this, [this]() {
ui->elseActionsList->ensureWidgetVisible(
ui->elseActionsList->WidgetAt(currentElseActionIdx));
});
ScrollAndFocusNewSegment(ui->elseActionsList, idx,
&currentElseActionIdx);
}
void MacroEdit::AddMacroElseAction(int idx)
@@ -1611,10 +1618,7 @@ void MacroEdit::AddMacroCondition(Macro *macro, int idx, const std::string &id,
ui->conditionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged());
QTimer::singleShot(0, this, [this]() {
ui->conditionsList->ensureWidgetVisible(
ui->conditionsList->WidgetAt(currentConditionIdx));
});
ScrollAndFocusNewSegment(ui->conditionsList, idx, &currentConditionIdx);
}
void MacroEdit::on_conditionAdd_clicked()

View File

@@ -116,6 +116,8 @@ private:
void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0);
void PopulateMacroElseActions(Macro &m, uint32_t afterIdx = 0);
void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0);
void ScrollAndFocusNewSegment(MacroSegmentList *list, int newIdx,
int *currentIdx);
void SetupMacroSegmentSelection(MacroSection type, int idx);
void
SetupContextMenu(const QPoint &pos,

View File

@@ -1,11 +1,14 @@
#include "macro-export-import-dialog.hpp"
#include "macro-export-extensions.hpp"
#include "obs-module-helper.hpp"
#include "section.hpp"
#include <obs.hpp>
#include <QDialogButtonBox>
#include <QLabel>
#include <QLayout>
#include <QScrollArea>
#include <QSplitter>
#include <algorithm>
#include <numeric>
@@ -44,10 +47,14 @@ static bool isValidData(const QString &json)
// Extension section
// ---------------------------------------------------------------------------
Section *MacroExportImportDialog::BuildExtensionSection()
QWidget *MacroExportImportDialog::BuildExtensionWidget()
{
auto outerSection = new Section(300, this);
outerSection->AddHeaderWidget(new QLabel(obs_module_text(
auto wrapper = new QWidget(this);
auto wrapperLayout = new QVBoxLayout(wrapper);
wrapperLayout->setContentsMargins(0, 0, 0, 0);
wrapperLayout->setSpacing(4);
wrapperLayout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.macroTab.export.additionalContent")));
auto outerContent = new QWidget();
@@ -57,7 +64,6 @@ Section *MacroExportImportDialog::BuildExtensionSection()
const auto &extensions = GetMacroExportExtensions();
// Build a sorted index list (alphabetical by translated display name).
std::vector<int> order(extensions.size());
std::iota(order.begin(), order.end(), 0);
std::sort(order.begin(), order.end(), [&](int a, int b) {
@@ -81,6 +87,7 @@ Section *MacroExportImportDialog::BuildExtensionSection()
&MacroExportImportDialog::UpdateExportString);
outerLayout->addWidget(ui.mainCheck);
_extensionUIs.append(std::move(ui));
_extensionOrder.push_back(extIdx);
continue;
}
@@ -88,11 +95,18 @@ Section *MacroExportImportDialog::BuildExtensionSection()
// Section: the main checkbox sits in the header (next to the
// toggle arrow) and the per-item checkboxes live in the
// collapsible content area.
const auto items = ext.getExportItems();
auto items = ext.getExportItems();
if (items.isEmpty()) {
continue;
}
std::sort(items.begin(), items.end(),
[](const QPair<QString, QString> &a,
const QPair<QString, QString> &b) {
return QString::localeAwareCompare(
a.second, b.second) < 0;
});
auto innerSection = new Section(200, outerContent);
ui.mainCheck =
@@ -132,13 +146,22 @@ Section *MacroExportImportDialog::BuildExtensionSection()
UpdateExportString();
});
innerSection->SetContent(subWidget, false);
innerSection->SetContent(subWidget, true);
outerLayout->addWidget(innerSection);
_extensionUIs.append(std::move(ui));
_extensionOrder.push_back(extIdx);
}
outerSection->SetContent(outerContent, true);
return outerSection;
outerLayout->addStretch();
auto scrollArea = new QScrollArea(wrapper);
scrollArea->setWidget(outerContent);
scrollArea->setWidgetResizable(true);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setFrameShape(QFrame::NoFrame);
wrapperLayout->addWidget(scrollArea);
return wrapper;
}
// ---------------------------------------------------------------------------
@@ -180,12 +203,25 @@ MacroExportImportDialog::MacroExportImportDialog(Type type,
auto layout = new QVBoxLayout(this);
if (type == Type::EXPORT_MACRO && !GetMacroExportExtensions().empty()) {
layout->addWidget(BuildExtensionSection());
auto splitter = new QSplitter(Qt::Vertical, this);
splitter->setChildrenCollapsible(false);
splitter->addWidget(BuildExtensionWidget());
auto textWidget = new QWidget(splitter);
auto textLayout = new QVBoxLayout(textWidget);
textLayout->setContentsMargins(0, 0, 0, 0);
textLayout->addWidget(label);
textLayout->addWidget(_importExportString);
textLayout->addWidget(_usePlainText);
splitter->addWidget(textWidget);
layout->addWidget(splitter);
} else {
layout->addWidget(label);
layout->addWidget(_importExportString);
layout->addWidget(_usePlainText);
}
layout->addWidget(label);
layout->addWidget(_importExportString);
layout->addWidget(_usePlainText);
layout->addWidget(buttons);
setLayout(layout);
@@ -209,19 +245,26 @@ QString MacroExportImportDialog::BuildExportJson() const
}
const auto &extensions = GetMacroExportExtensions();
for (int i = 0;
i < (int)extensions.size() && i < (int)_extensionUIs.size(); ++i) {
if (!_extensionUIs[i].mainCheck->isChecked()) {
continue;
}
for (int i = 0; i < (int)_extensionUIs.size(); ++i) {
const auto &ui = _extensionUIs[i];
QStringList selectedIds;
for (const auto &[id, cb] : _extensionUIs[i].itemChecks) {
if (cb->isChecked()) {
selectedIds << id;
if (ui.itemChecks.isEmpty()) {
if (!ui.mainCheck->isChecked()) {
continue;
}
} else {
for (const auto &[id, cb] : ui.itemChecks) {
if (cb->isChecked()) {
selectedIds << id;
}
}
if (selectedIds.isEmpty()) {
continue;
}
}
extensions[i].save(data, selectedIds);
extensions[_extensionOrder[i]].save(data, selectedIds);
}
const char *json = obs_data_get_json(data);

View File

@@ -1,13 +1,12 @@
#pragma once
#include "section.hpp"
#include <QCheckBox>
#include <QDialog>
#include <QList>
#include <QPlainTextEdit>
#include <obs-data.h>
#include <vector>
namespace advss {
@@ -34,7 +33,7 @@ private:
QList<QPair<QString, QCheckBox *>> itemChecks;
};
Section *BuildExtensionSection();
QWidget *BuildExtensionWidget();
QString BuildExportJson() const;
void RefreshExportText();
@@ -42,6 +41,7 @@ private:
QPlainTextEdit *_importExportString;
QCheckBox *_usePlainText;
QList<ExtensionUI> _extensionUIs;
std::vector<int> _extensionOrder;
};
} // namespace advss

View File

@@ -134,6 +134,7 @@ public:
void SetSelected(bool);
virtual std::shared_ptr<MacroSegment> Data() const = 0;
virtual void SetupWidgets(bool basicSetup = false) = 0;
virtual void FocusTypeSelection() {}
void SetupVarMappings(MacroSegment *segment);

View File

@@ -135,7 +135,7 @@ QStringList GetSourceNames()
}
void PopulateTransitionSelection(QComboBox *sel, bool addCurrent, bool addAny,
bool addSelect)
bool addSelect, bool addNone)
{
obs_frontend_source_list *transitions = new obs_frontend_source_list();
@@ -168,6 +168,11 @@ void PopulateTransitionSelection(QComboBox *sel, bool addCurrent, bool addAny,
addSelect ? 1 : 0,
obs_module_text("AdvSceneSwitcher.anyTransition"));
}
if (addNone) {
sel->insertItem(
addSelect ? 1 : 0,
obs_module_text("AdvSceneSwitcher.noneTransition"));
}
}
void PopulateWindowSelection(QComboBox *sel, bool addSelect)

View File

@@ -19,7 +19,8 @@ EXPORT QStringList GetSourceNames();
EXPORT void PopulateTransitionSelection(QComboBox *sel, bool addCurrent = true,
bool addAny = false,
bool addSelect = true);
bool addSelect = true,
bool addNone = false);
EXPORT void PopulateWindowSelection(QComboBox *sel, bool addSelect = true);
void PopulateAudioSelection(QComboBox *sel, bool addSelect = true);
void PopulateVideoSelection(QComboBox *sel, bool addMainOutput = false,

View File

@@ -1,7 +1,11 @@
macro(add_plugin plugin)
option(ENABLE_${plugin}_PLUGIN "Enable the ${plugin} plugin" ON)
if(ENABLE_${plugin}_PLUGIN)
set_property(GLOBAL APPEND PROPERTY ADVSS_PLUGINS_PENDING ${plugin})
add_subdirectory(${plugin})
else()
set_property(GLOBAL APPEND PROPERTY ADVSS_PLUGINS_DISABLED_BY_OPTION
${plugin})
endif()
endmacro()
@@ -37,3 +41,32 @@ add_plugin(stream-deck)
add_plugin(twitch)
add_plugin(usb)
add_plugin(video)
# ---------------------------------------------------------------------------- #
# Print plugin summary
get_property(_advss_enabled GLOBAL PROPERTY ADVSS_PLUGINS_ENABLED)
get_property(_advss_pending GLOBAL PROPERTY ADVSS_PLUGINS_PENDING)
get_property(_advss_disabled_opt GLOBAL
PROPERTY ADVSS_PLUGINS_DISABLED_BY_OPTION)
set(_advss_disabled_deps)
foreach(_p ${_advss_pending})
if(NOT _p IN_LIST _advss_enabled)
list(APPEND _advss_disabled_deps ${_p})
endif()
endforeach()
message(STATUS "")
message(STATUS "ADVSS Plugin Summary")
message(STATUS "====================")
foreach(_p ${_advss_enabled})
message(STATUS " ENABLED: ${_p}")
endforeach()
foreach(_p ${_advss_disabled_deps})
message(STATUS " DISABLED: ${_p} (missing dependencies)")
endforeach()
foreach(_p ${_advss_disabled_opt})
message(STATUS " DISABLED: ${_p} (ENABLE_${_p}_PLUGIN=OFF)")
endforeach()
message(STATUS "")

View File

@@ -160,12 +160,11 @@ static void attachRestoreContext(obs_sceneitem_t *item,
static void setSceneItemVisibility(obs_sceneitem_t *item,
const bool setTransition,
const OBSWeakSource &transitionWeak,
const TransitionSelection &transitionSel,
const bool setDuration,
const Duration &duration,
MacroActionSceneVisibility::Action action)
{
const OBSSourceAutoRelease transition = OBSGetStrongRef(transitionWeak);
const bool itemIsVisible = obs_sceneitem_visible(item);
const OBSSource currentTransition =
@@ -175,6 +174,16 @@ static void setSceneItemVisibility(obs_sceneitem_t *item,
OBSSource privateTransitionSource = nullptr;
if (setTransition) {
OBSSourceAutoRelease transition;
if (transitionSel.GetType() ==
TransitionSelection::Type::CURRENT) {
transition = obs_source_get_ref(
obs_sceneitem_get_transition(item,
!itemIsVisible));
} else {
transition =
OBSGetStrongRef(transitionSel.GetTransition());
}
privateTransitionSource = SetSceneItemTransition(
item, transition, !itemIsVisible);
} else {
@@ -229,8 +238,7 @@ bool MacroActionSceneVisibility::PerformAction()
{
auto items = _source.GetSceneItems(_scene);
for (const auto &item : items) {
setSceneItemVisibility(item, _updateTransition,
_transition.GetTransition(),
setSceneItemVisibility(item, _updateTransition, _transition,
_updateDuration, _duration, _action);
}
return true;
@@ -340,7 +348,7 @@ MacroActionSceneVisibilityEdit::MacroActionSceneVisibilityEdit(
},
SceneItemSelectionWidget::NameClashMode::ALL)),
_updateTransition(new QCheckBox(this)),
_transitions(new TransitionSelectionWidget(this, false, false)),
_transitions(new TransitionSelectionWidget(this, true, false, true)),
_updateDuration(new QCheckBox(this)),
_duration(new DurationSelection(this, false)),
_durationLayout(new QHBoxLayout),
@@ -474,8 +482,11 @@ void MacroActionSceneVisibilityEdit::ActionChanged(int value)
void MacroActionSceneVisibilityEdit::SetWidgetVisibility()
{
const auto transitionType = _entryData->_transition.GetType();
const bool hideDurationSelection =
_entryData->_updateTransition &&
transitionType != TransitionSelection::Type::CURRENT &&
transitionType != TransitionSelection::Type::NONE &&
IsFixedLengthTransition(
_entryData->_transition.GetTransition());

View File

@@ -406,15 +406,17 @@ void MacroActionTransitionEdit::SetWidgetVisibility()
MacroActionTransition::Type::TBAR;
const bool isReleaseTbar = _entryData->_type ==
MacroActionTransition::Type::RELEASE_TBAR;
_sources->setVisible(
_entryData->_type == MacroActionTransition::Type::SOURCE_HIDE ||
_entryData->_type == MacroActionTransition::Type::SOURCE_SHOW);
const bool isSourceTransition =
_entryData->_type == MacroActionTransition::Type::SOURCE_SHOW ||
_entryData->_type == MacroActionTransition::Type::SOURCE_HIDE;
_sources->setVisible(isSourceTransition);
_scenes->setVisible(_entryData->_type !=
MacroActionTransition::Type::SCENE &&
!isTbar && !isReleaseTbar);
SetLayoutVisible(_transitionLayout, !isTbar && !isReleaseTbar);
SetLayoutVisible(_durationLayout, !isTbar && !isReleaseTbar);
SetLayoutVisible(_tbarLayout, isTbar);
_transitions->EnableNoneEntry(isSourceTransition);
adjustSize();
}

View File

@@ -163,7 +163,7 @@ void MacroConditionTransition::TransitionStarted(void *data, calldata_t *cd)
// Look up the original by name to get a comparable reference.
const bool studioModeActive =
obs_frontend_preview_program_mode_active();
if (studioModeActive) {
if (studioModeActive && startSource) {
OBSSourceAutoRelease startSourceByName = obs_get_source_by_name(
obs_source_get_name(startSource));
if (startSourceByName) {

View File

@@ -48,6 +48,8 @@ OBSWeakSource TransitionSelection::GetTransition() const
obs_source_release(source);
return weakSource;
}
case Type::NONE:
return nullptr;
default:
break;
}
@@ -63,6 +65,8 @@ std::string TransitionSelection::ToString() const
return obs_module_text("AdvSceneSwitcher.currentTransition");
case Type::ANY:
return obs_module_text("AdvSceneSwitcher.anyTransition");
case Type::NONE:
return obs_module_text("AdvSceneSwitcher.noneTransition");
default:
break;
}
@@ -70,14 +74,16 @@ std::string TransitionSelection::ToString() const
}
TransitionSelectionWidget::TransitionSelectionWidget(QWidget *parent,
bool current, bool any)
bool current, bool any,
bool none)
: FilterComboBox(parent,
obs_module_text("AdvSceneSwitcher.selectTransition")),
_addCurrent(current),
_addAny(any)
_addAny(any),
_addNone(none)
{
setDuplicatesEnabled(true);
PopulateTransitionSelection(this, current, any, false);
PopulateTransitionSelection(this, current, any, false, none);
QWidget::connect(this, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(SelectionChanged(const QString &)));
@@ -86,8 +92,9 @@ TransitionSelectionWidget::TransitionSelectionWidget(QWidget *parent,
void TransitionSelectionWidget::SetTransition(const TransitionSelection &t)
{
// Order of entries
// 1. Any transition
// 2. Current transition
// 1. None transition
// 2. Any transition
// 3. Current transition
// 4. Transitions
switch (t.GetType()) {
@@ -102,6 +109,10 @@ void TransitionSelectionWidget::SetTransition(const TransitionSelection &t)
setCurrentIndex(findText(QString::fromStdString(
obs_module_text("AdvSceneSwitcher.anyTransition"))));
break;
case TransitionSelection::Type::NONE:
setCurrentIndex(findText(QString::fromStdString(
obs_module_text("AdvSceneSwitcher.noneTransition"))));
break;
default:
setCurrentIndex(-1);
break;
@@ -120,6 +131,17 @@ void TransitionSelectionWidget::EnableAnyEntry(bool enable)
Populate();
}
void TransitionSelectionWidget::EnableNoneEntry(bool enable)
{
if (_addNone == enable) {
return;
}
const auto selection = GetCurrentSelection();
_addNone = enable;
Populate();
SetTransition(selection);
}
void TransitionSelectionWidget::showEvent(QShowEvent *event)
{
FilterComboBox::showEvent(event);
@@ -133,7 +155,7 @@ void TransitionSelectionWidget::Populate()
{
const QSignalBlocker blocker(this);
clear();
PopulateTransitionSelection(this, _addCurrent, _addAny);
PopulateTransitionSelection(this, _addCurrent, _addAny, true, _addNone);
}
static bool isFirstEntry(const QComboBox *l, QString name, int idx)
@@ -163,6 +185,9 @@ TransitionSelection TransitionSelectionWidget::GetCurrentSelection() const
if (IsAnyTransitionSelected(text)) {
result._type = TransitionSelection::Type::ANY;
}
if (IsNoneTransitionSelected(text)) {
result._type = TransitionSelection::Type::NONE;
}
}
return result;
}
@@ -187,6 +212,16 @@ bool TransitionSelectionWidget::IsAnyTransitionSelected(
return false;
}
bool TransitionSelectionWidget::IsNoneTransitionSelected(
const QString &name) const
{
if (name == QString::fromStdString((obs_module_text(
"AdvSceneSwitcher.noneTransition")))) {
return isFirstEntry(this, name, currentIndex());
}
return false;
}
void TransitionSelectionWidget::SelectionChanged(const QString &)
{
emit TransitionChanged(GetCurrentSelection());

View File

@@ -16,6 +16,7 @@ public:
TRANSITION,
CURRENT,
ANY,
NONE,
};
Type GetType() const { return _type; }
@@ -33,10 +34,11 @@ class TransitionSelectionWidget : public FilterComboBox {
public:
TransitionSelectionWidget(QWidget *parent, bool current = true,
bool any = false);
bool any = false, bool none = false);
void SetTransition(const TransitionSelection &);
void EnableCurrentEntry(bool enable);
void EnableAnyEntry(bool enable);
void EnableNoneEntry(bool enable);
protected:
void showEvent(QShowEvent *event) override;
@@ -52,9 +54,11 @@ private:
TransitionSelection GetCurrentSelection() const;
bool IsCurrentTransitionSelected(const QString &name) const;
bool IsAnyTransitionSelected(const QString &name) const;
bool IsNoneTransitionSelected(const QString &name) const;
bool _addCurrent;
bool _addAny;
bool _addNone;
};
} // namespace advss

View File

@@ -26,7 +26,7 @@ if(NOT OpenVR_DIR)
set(OpenVR_DIR ${ADVSS_SOURCE_DIR}/deps/openvr)
endif()
if(EXISTS ${OpenVR_DIR})
if(EXISTS "${OpenVR_DIR}/CMakeLists.txt")
set(SIZEOF_VOIDP ${CMAKE_SIZEOF_VOID_P})
if((NOT APPLE) AND (CMAKE_SIZEOF_VOID_P EQUAL 8))
set(PROCESSOR_ARCH "64")

View File

@@ -3,6 +3,7 @@
#include "twitch-helpers.hpp"
#include <log-helper.hpp>
#include <plugin-state-helpers.hpp>
#undef DispatchMessage
@@ -339,6 +340,24 @@ static constexpr std::string_view defaultURL =
std::map<TwitchChatConnection::ChatMapKey, std::weak_ptr<TwitchChatConnection>>
TwitchChatConnection::_chatMap = {};
void TwitchChatConnection::DisconnectAll()
{
for (auto &[key, weakConn] : _chatMap) {
auto conn = weakConn.lock();
if (conn) {
conn->Disconnect();
}
}
}
static bool setupChatConnectionSupport()
{
AddStopStep(TwitchChatConnection::DisconnectAll);
return true;
}
static bool _chatSetup = setupChatConnectionSupport();
TwitchChatConnection::TwitchChatConnection(const TwitchToken &token,
const TwitchChannel &channel)
: QObject(nullptr),

View File

@@ -84,6 +84,7 @@ public:
static std::shared_ptr<TwitchChatConnection>
GetChatConnection(const TwitchToken &token,
const TwitchChannel &channel);
static void DisconnectAll();
[[nodiscard]] ChatMessageBuffer RegisterForMessages();
[[nodiscard]] ChatMessageBuffer RegisterForWhispers();
void SendChatMessage(const std::string &message);

View File

@@ -3,6 +3,7 @@
#include "twitch-helpers.hpp"
#include <log-helper.hpp>
#include <plugin-state-helpers.hpp>
#ifdef VERIFY_TIMESTAMPS
#include "date/tz.h"
@@ -71,6 +72,22 @@ void EventSub::UnregisterInstance()
_instances.erase(it, _instances.end());
}
void EventSub::DisconnectAll()
{
std::lock_guard<std::mutex> lock(_instancesMtx);
for (auto *instance : _instances) {
instance->Disconnect();
}
}
static bool setupEventSubSupport()
{
AddStopStep(EventSub::DisconnectAll);
return true;
}
static bool _eventSubSetup = setupEventSubSupport();
void EventSub::ConnectThread()
{
_client->reset();

View File

@@ -53,6 +53,7 @@ public:
void Connect();
void Disconnect();
static void DisconnectAll();
[[nodiscard]] EventSubMessageBuffer RegisterForEvents();
bool SubscriptionIsActive(const std::string &id);
static std::string AddEventSubscription(std::shared_ptr<TwitchToken>,

View File

@@ -547,16 +547,18 @@ bool MacroConditionTwitch::CheckChatMessageRemove(TwitchToken &token)
bool MacroConditionTwitch::ChatConnectionIsSetup(TwitchToken &token)
{
if (_chatConnection) {
_chatConnection->ConnectToChat();
return true;
}
_chatConnection =
TwitchChatConnection::GetChatConnection(token, _channel);
if (!_chatConnection) {
_chatConnection = TwitchChatConnection::GetChatConnection(
token, _channel);
if (!_chatConnection) {
return false;
}
_chatBuffer = _chatConnection->RegisterForMessages();
return false;
}
return true;
_chatBuffer = _chatConnection->RegisterForMessages();
return false;
}
bool MacroConditionTwitch::HandleChatEvents(