#include "macro-segment-script.hpp" #include "layout-helpers.hpp" #include "macro-action.hpp" #include "macro-condition.hpp" #include "macro-helpers.hpp" #include "macro-script-handler.hpp" #include "obs-module-helper.hpp" #include "properties-view.hpp" #include "sync-helpers.hpp" namespace advss { static std::atomic_int completionIdCounter = 0; static std::atomic_int instanceIdCounter = 0; static std::mutex instanceMtx; static std::vector instances{}; MacroSegmentScript::MacroSegmentScript( obs_data_t *defaultSettings, const std::string &propertiesSignalName, const std::string &triggerSignalName, const std::string &completionSignalName, const std::string &newInstanceSignalName, const std::string &deletedInstanceSignalName) : _settings(obs_data_get_defaults(defaultSettings)), _propertiesSignal(propertiesSignalName), _triggerSignal(triggerSignalName), _completionSignal(completionSignalName), _newInstanceSignal(newInstanceSignalName), _deletedInstanceSignal(deletedInstanceSignalName), _instanceId(++instanceIdCounter) { signal_handler_connect(obs_get_signal_handler(), completionSignalName.c_str(), &MacroSegmentScript::CompletionSignalReceived, this); std::lock_guard lock(instanceMtx); instances.emplace_back(this); SignalNewInstance(); } MacroSegmentScript::MacroSegmentScript(const MacroSegmentScript &other) : _settings(obs_data_create()), _propertiesSignal(other._propertiesSignal), _triggerSignal(other._triggerSignal), _completionSignal(other._completionSignal), _newInstanceSignal(other._newInstanceSignal), _deletedInstanceSignal(other._deletedInstanceSignal), _instanceId(++instanceIdCounter) { signal_handler_connect(obs_get_signal_handler(), _completionSignal.c_str(), &MacroSegmentScript::CompletionSignalReceived, this); obs_data_apply(_settings.Get(), other._settings.Get()); std::lock_guard lock(instanceMtx); instances.emplace_back(this); SignalNewInstance(); } MacroSegmentScript::~MacroSegmentScript() { auto data = calldata_create(); calldata_set_int(data, GetInstanceIdParamName().data(), _instanceId); signal_handler_signal(obs_get_signal_handler(), _deletedInstanceSignal.c_str(), data); calldata_destroy(data); std::lock_guard lock(instanceMtx); instances.erase(std::remove(instances.begin(), instances.end(), this), instances.end()); } void MacroSegmentScript::RegisterTempVar(const std::string &variableId, const std::string &name, const std::string &helpText, int instanceId) { std::lock_guard lock(instanceMtx); for (auto instance : instances) { if (instance->_instanceId != instanceId) { continue; } instance->RegisterTempVarHelper(variableId, name, helpText); break; } } void MacroSegmentScript::DeregisterAllTempVars(int instanceId) { std::lock_guard lock(instanceMtx); for (auto instance : instances) { if (instance->_instanceId != instanceId) { continue; } instance->DeregisterAllTempVarsHelper(); } } void MacroSegmentScript::SetTempVarValue(const std::string &variableId, const std::string &value, int instanceId) { std::lock_guard lock(instanceMtx); for (auto instance : instances) { if (instance->_instanceId != instanceId) { continue; } instance->SetTempVarValueHelper(variableId, value); } } bool MacroSegmentScript::Save(obs_data_t *obj) const { obs_data_set_obj(obj, "settings", _settings.Get()); _timeout.Save(obj); return true; } bool MacroSegmentScript::Load(obs_data_t *obj) { OBSDataAutoRelease settings = obs_data_get_obj(obj, "settings"); obs_data_apply(_settings.Get(), settings); _timeout.Load(obj); return true; } obs_properties_t *MacroSegmentScript::GetProperties() const { auto data = calldata_create(); signal_handler_signal(obs_get_signal_handler(), _propertiesSignal.c_str(), data); obs_properties_t *properties = nullptr; if (!calldata_get_ptr(data, GetPropertiesSignalParamName().data(), &properties)) { calldata_destroy(data); return nullptr; } calldata_destroy(data); return properties; } void MacroSegmentScript::UpdateSettings(obs_data_t *newSettings) const { obs_data_clear(_settings.Get()); obs_data_apply(_settings.Get(), newSettings); } bool MacroSegmentScript::SendTriggerSignal() { _completionId = ++completionIdCounter; _triggerIsComplete = false; _triggerResult = false; auto data = calldata_create(); calldata_set_string(data, GetActionCompletionSignalParamName().data(), _completionSignal.c_str()); calldata_set_int(data, GetCompletionIdParamName().data(), _completionId); calldata_set_string(data, "settings", obs_data_get_json(GetSettings())); calldata_set_int(data, GetInstanceIdParamName().data(), _instanceId); signal_handler_signal(obs_get_signal_handler(), _triggerSignal.c_str(), data); calldata_destroy(data); SetMacroAbortWait(false); WaitForCompletion(); return _triggerResult; } void MacroSegmentScript::CompletionSignalReceived(void *param, calldata_t *data) { auto segment = static_cast(param); long long int id; if (!calldata_get_int(data, GetCompletionIdParamName().data(), &id)) { blog(LOG_WARNING, "received completion signal without \"%s\" parameter", GetCompletionIdParamName().data()); return; } bool result; if (!calldata_get_bool(data, GetResultSignalParamName().data(), &result)) { blog(LOG_WARNING, "received completion signal without \"%s\" parameter", GetResultSignalParamName().data()); return; } if (id != segment->_completionId) { return; } segment->_triggerIsComplete = true; segment->_triggerResult = result; } void MacroSegmentScript::SignalNewInstance() const { std::thread signalNewInstanceThread([this]() { auto data = calldata_create(); calldata_set_int(data, GetInstanceIdParamName().data(), _instanceId); signal_handler_signal(obs_get_signal_handler(), _newInstanceSignal.c_str(), data); calldata_destroy(data); }); signalNewInstanceThread.detach(); } obs_properties_t *MacroSegmentScriptEdit::GetProperties(void *obj) { auto segmentEdit = reinterpret_cast(obj); if (!segmentEdit) { return nullptr; } return segmentEdit->_entryData->GetProperties(); } void MacroSegmentScriptEdit::UpdateSettings(void *obj, obs_data_t *settings) { auto segmentEdit = reinterpret_cast(obj); if (!segmentEdit || segmentEdit->_loading || !segmentEdit->_entryData) { return; } auto lock = LockContext(); segmentEdit->_entryData->UpdateSettings(settings); } MacroSegmentScriptEdit::MacroSegmentScriptEdit( QWidget *parent, std::shared_ptr entryData) : QWidget(parent), _timeout(new DurationSelection(this)) { QWidget::connect(_timeout, &DurationSelection::DurationChanged, this, &MacroSegmentScriptEdit::TimeoutChanged); auto timeoutLayout = new QHBoxLayout(); PlaceWidgets(obs_module_text("AdvSceneSwitcher.script.timeout"), timeoutLayout, {{"{{timeout}}", _timeout}}); auto layout = new QVBoxLayout(); auto properties = entryData->GetProperties(); if (!!properties) { obs_properties_destroy(properties); // We need a separate OBSData object here as we can't risk // entryData->_settings being modified while it is currently used OBSDataAutoRelease data = obs_data_create(); obs_data_apply(data, entryData->GetSettings()); #if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(30, 0, 0) auto propertiesView = new OBSPropertiesView(data.Get(), this, GetProperties, nullptr, UpdateSettings); layout->addWidget(propertiesView); connect(propertiesView, &OBSPropertiesView::PropertiesResized, this, [this]() { adjustSize(); updateGeometry(); }); #else layout->addWidget(new QLabel( "Displaying script properties not supported when compiled for OBS 29!")); #endif } layout->addLayout(timeoutLayout); setLayout(layout); _entryData = entryData; UpdateEntryData(); _loading = false; adjustSize(); updateGeometry(); } void MacroSegmentScriptEdit::UpdateEntryData() { _timeout->SetDuration(_entryData->_timeout); } QWidget *MacroSegmentScriptEdit::Create(QWidget *parent, std::shared_ptr segment) { return new MacroSegmentScriptEdit( parent, std::dynamic_pointer_cast(segment)); } QWidget *MacroSegmentScriptEdit::Create(QWidget *parent, std::shared_ptr segment) { return new MacroSegmentScriptEdit( parent, std::dynamic_pointer_cast(segment)); } void MacroSegmentScriptEdit::TimeoutChanged(const Duration &timeout) { GUARD_LOADING_AND_LOCK(); _entryData->_timeout = timeout; } } // namespace advss