SceneSwitcher/plugins/base/macro-action-scene-switch.cpp
WarmUpTill 0b774c171d Cleanup
* Use GetSettingsWindow() instead of window() to avoid connecting to the
  wrong widget
* Fix typos
2025-07-13 18:17:46 +02:00

356 lines
10 KiB
C++

#include "macro-action-scene-switch.hpp"
#include "layout-helpers.hpp"
#include "macro-helpers.hpp"
#include "plugin-state-helpers.hpp"
#include "scene-switch-helpers.hpp"
#include "source-helpers.hpp"
#include <obs-frontend-api.h>
namespace advss {
using namespace std::chrono_literals;
const std::string MacroActionSwitchScene::id =
MacroAction::GetDefaultID().data();
bool MacroActionSwitchScene::_registered = MacroActionFactory::Register(
MacroActionSwitchScene::id,
{MacroActionSwitchScene::Create, MacroActionSwitchSceneEdit::Create,
"AdvSceneSwitcher.action.scene"});
const static std::map<MacroActionSwitchScene::SceneType, std::string>
sceneTypes = {
{MacroActionSwitchScene::SceneType::PROGRAM,
"AdvSceneSwitcher.action.scene.type.program"},
{MacroActionSwitchScene::SceneType::PREVIEW,
"AdvSceneSwitcher.action.scene.type.preview"},
};
static void waitForTransitionChange(OBSWeakSource &transition,
std::unique_lock<std::mutex> *lock,
Macro *macro)
{
const auto time = 100ms;
obs_source_t *source = obs_weak_source_get_source(transition);
if (!source) {
return;
}
bool stillTransitioning = true;
while (stillTransitioning && !MacroWaitShouldAbort() &&
!MacroIsStopped(macro)) {
GetMacroTransitionCV().wait_for(*lock, time);
float t = obs_transition_get_time(source);
stillTransitioning = t < 1.0f && t > 0.0f;
}
obs_source_release(source);
}
static void waitForTransitionChangeFixedDuration(
int duration, std::unique_lock<std::mutex> *lock, Macro *macro)
{
duration += 200; // It seems to be necessary to add a small buffer
auto time = std::chrono::high_resolution_clock::now() +
std::chrono::milliseconds(duration);
while (!MacroWaitShouldAbort() && !MacroIsStopped(macro)) {
if (GetMacroTransitionCV().wait_until(*lock, time) ==
std::cv_status::timeout) {
break;
}
}
}
static int getTransitionOverrideDuration(OBSWeakSource &scene)
{
int duration = 0;
obs_source_t *source = obs_weak_source_get_source(scene);
obs_data_t *data = obs_source_get_private_settings(source);
auto name = obs_data_get_string(data, "transition");
if (strlen(name) != 0) {
duration = obs_data_get_int(data, "transition_duration");
}
obs_data_release(data);
obs_source_release(source);
return duration;
}
static bool isUsingFixedLengthTransition(const OBSWeakSource &transition)
{
obs_source_t *source = obs_weak_source_get_source(transition);
bool ret = obs_transition_fixed(source);
obs_source_release(source);
return ret;
}
static OBSWeakSource getOverrideTransition(OBSWeakSource &scene)
{
OBSWeakSource transition;
obs_source_t *source = obs_weak_source_get_source(scene);
obs_data_t *data = obs_source_get_private_settings(source);
transition = GetWeakTransitionByName(
obs_data_get_string(data, "transition"));
obs_data_release(data);
obs_source_release(source);
return transition;
}
static int getExpectedTransitionDuration(OBSWeakSource &scene,
OBSWeakSource &transiton_,
double duration)
{
OBSWeakSource transition = transiton_;
// If we are not modifying the transition override, we will have to
// check, if a transition override is set by the user and use its
// duration instead
if (!ShouldModifyTransitionOverrides()) {
auto overrideTransition = getOverrideTransition(scene);
if (overrideTransition) {
transition = overrideTransition;
if (!isUsingFixedLengthTransition(transition)) {
return getTransitionOverrideDuration(scene);
}
}
}
if (isUsingFixedLengthTransition(transition)) {
return -1; // no API is available to access the fixed duration
}
if (duration != 0) {
return duration * 1000;
}
return obs_frontend_get_transition_duration();
}
bool MacroActionSwitchScene::WaitForTransition(OBSWeakSource &scene,
OBSWeakSource &transition)
{
const int expectedTransitionDuration = getExpectedTransitionDuration(
scene, transition, _duration.Seconds());
SetMacroAbortWait(false);
std::unique_lock<std::mutex> lock(*GetMutex());
if (expectedTransitionDuration < 0) {
waitForTransitionChange(transition, &lock, GetMacro());
} else {
waitForTransitionChangeFixedDuration(expectedTransitionDuration,
&lock, GetMacro());
}
return !MacroWaitShouldAbort();
}
bool MacroActionSwitchScene::PerformAction()
{
auto scene = _scene.GetScene();
if (_sceneType == SceneType::PREVIEW) {
OBSSourceAutoRelease previewScneSource =
obs_weak_source_get_source(scene);
obs_frontend_set_current_preview_scene(previewScneSource);
return true;
}
auto transition = _transition.GetTransition();
SwitchScene({scene, transition, (int)(_duration.Milliseconds())},
obs_frontend_preview_program_mode_active());
if (_blockUntilTransitionDone && scene && scene != GetCurrentScene()) {
return WaitForTransition(scene, transition);
}
return true;
}
void MacroActionSwitchScene::LogAction() const
{
ablog(LOG_INFO, "switch%s scene to '%s'",
_sceneType == SceneType::PREVIEW ? " preview" : "",
_scene.ToString(true).c_str());
}
bool MacroActionSwitchScene::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
_scene.Save(obj);
_transition.Save(obj);
_duration.Save(obj);
obs_data_set_bool(obj, "blockUntilTransitionDone",
_blockUntilTransitionDone);
obs_data_set_int(obj, "sceneType", static_cast<int>(_sceneType));
return true;
}
bool MacroActionSwitchScene::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_scene.Load(obj);
_transition.Load(obj);
_duration.Load(obj);
_blockUntilTransitionDone =
obs_data_get_bool(obj, "blockUntilTransitionDone");
_sceneType = static_cast<SceneType>(obs_data_get_int(obj, "sceneType"));
return true;
}
std::string MacroActionSwitchScene::GetShortDesc() const
{
return _scene.ToString();
}
std::shared_ptr<MacroAction> MacroActionSwitchScene::Create(Macro *m)
{
return std::make_shared<MacroActionSwitchScene>(m);
}
std::shared_ptr<MacroAction> MacroActionSwitchScene::Copy() const
{
return std::make_shared<MacroActionSwitchScene>(*this);
}
void MacroActionSwitchScene::ResolveVariablesToFixedValues()
{
_scene.ResolveVariables();
_duration.ResolveVariables();
}
static inline void populateTypeSelection(QComboBox *list)
{
for (const auto &[_, name] : sceneTypes) {
list->addItem(obs_module_text(name.c_str()));
}
}
MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
QWidget *parent, std::shared_ptr<MacroActionSwitchScene> entryData)
: QWidget(parent),
_scenes(new SceneSelectionWidget(this, true, true, true)),
_transitions(new TransitionSelectionWidget(this)),
_duration(new DurationSelection(parent, false)),
_blockUntilTransitionDone(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.action.scene.blockUntilTransitionDone"))),
_sceneTypes(new QComboBox()),
_entryLayout(new QHBoxLayout())
{
_duration->SpinBox()->setSpecialValueText("-");
populateTypeSelection(_sceneTypes);
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_transitions,
SIGNAL(TransitionChanged(const TransitionSelection &)),
this,
SLOT(TransitionChanged(const TransitionSelection &)));
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(DurationChanged(const Duration &)));
QWidget::connect(_blockUntilTransitionDone, SIGNAL(stateChanged(int)),
this, SLOT(BlockUntilTransitionDoneChanged(int)));
QWidget::connect(_sceneTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(SceneTypeChanged(int)));
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.scene.entry"),
_entryLayout,
{{"{{scenes}}", _scenes},
{"{{transitions}}", _transitions},
{"{{duration}}", _duration},
{"{{sceneTypes}}", _sceneTypes}});
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(_entryLayout);
mainLayout->addWidget(_blockUntilTransitionDone);
setLayout(mainLayout);
_entryData = entryData;
_sceneTypes->setCurrentIndex(static_cast<int>(_entryData->_sceneType));
_scenes->SetScene(_entryData->_scene);
_transitions->SetTransition(_entryData->_transition);
_duration->SetDuration(_entryData->_duration);
_blockUntilTransitionDone->setChecked(
_entryData->_blockUntilTransitionDone);
SetWidgetVisibility();
_loading = false;
}
void MacroActionSwitchSceneEdit::DurationChanged(const Duration &dur)
{
GUARD_LOADING_AND_LOCK();
_entryData->_duration = dur;
}
void MacroActionSwitchSceneEdit::BlockUntilTransitionDoneChanged(int state)
{
GUARD_LOADING_AND_LOCK();
_entryData->_blockUntilTransitionDone = state;
}
void MacroActionSwitchSceneEdit::SceneTypeChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_sceneType =
static_cast<MacroActionSwitchScene::SceneType>(value);
SetWidgetVisibility();
}
void MacroActionSwitchSceneEdit::SetWidgetVisibility()
{
_entryLayout->removeWidget(_scenes);
_entryLayout->removeWidget(_transitions);
_entryLayout->removeWidget(_duration);
_entryLayout->removeWidget(_sceneTypes);
ClearLayout(_entryLayout);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes},
{"{{transitions}}", _transitions},
{"{{duration}}", _duration},
{"{{sceneTypes}}", _sceneTypes},
};
if (_entryData->_sceneType ==
MacroActionSwitchScene::SceneType::PREVIEW) {
_transitions->hide();
_duration->hide();
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.scene.entry.preview"),
_entryLayout, widgetPlaceholders);
return;
}
_transitions->show();
if (_entryData->_transition.GetType() !=
TransitionSelection::Type::TRANSITION) {
_duration->show();
}
const bool fixedDuration = isUsingFixedLengthTransition(
_entryData->_transition.GetTransition());
_duration->setVisible(!fixedDuration);
if (fixedDuration) {
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.scene.entry.noDuration"),
_entryLayout, widgetPlaceholders);
} else {
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.action.scene.entry"),
_entryLayout, widgetPlaceholders);
}
}
void MacroActionSwitchSceneEdit::SceneChanged(const SceneSelection &s)
{
GUARD_LOADING_AND_LOCK();
_entryData->_scene = s;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionSwitchSceneEdit::TransitionChanged(const TransitionSelection &t)
{
GUARD_LOADING_AND_LOCK();
_entryData->_transition = t;
SetWidgetVisibility();
}
} // namespace advss