Add API to register new macro condition and action types

This commit is contained in:
WarmUpTill
2024-07-13 20:38:41 +02:00
committed by WarmUpTill
parent 685e28d161
commit 36201cbfb4
20 changed files with 4670 additions and 40 deletions

View File

@@ -1,7 +1,28 @@
#include "macro-action-factory.hpp"
#include <mutex>
namespace advss {
namespace {
class MacroActionUnknown : public MacroAction {
public:
MacroActionUnknown(Macro *m) : MacroAction(m) {}
std::shared_ptr<MacroAction> Copy() const
{
return std::make_shared<MacroActionUnknown>(GetMacro());
}
bool PerformAction() { return true; };
bool Save(obs_data_t *obj) const { return MacroAction::Save(obj); };
bool Load(obs_data_t *obj) { return MacroAction::Load(obj); };
std::string GetId() const { return "unknown"; }
};
} // namespace
static std::mutex mutex;
std::map<std::string, MacroActionInfo> &MacroActionFactory::GetMap()
{
static std::map<std::string, MacroActionInfo> _methods;
@@ -10,6 +31,7 @@ std::map<std::string, MacroActionInfo> &MacroActionFactory::GetMap()
bool MacroActionFactory::Register(const std::string &id, MacroActionInfo info)
{
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it == GetMap().end()) {
GetMap()[id] = info;
return true;
@@ -17,35 +39,61 @@ bool MacroActionFactory::Register(const std::string &id, MacroActionInfo info)
return false;
}
bool MacroActionFactory::Deregister(const std::string &id)
{
std::lock_guard<std::mutex> lock(mutex);
if (GetMap().count(id) == 0) {
return false;
}
GetMap().erase(id);
return true;
}
static std::shared_ptr<MacroAction> createUnknownAction(Macro *m)
{
return std::make_shared<MacroActionUnknown>(m);
}
std::shared_ptr<MacroAction> MacroActionFactory::Create(const std::string &id,
Macro *m)
{
if (auto it = GetMap().find(id); it != GetMap().end())
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._create(m);
}
return nullptr;
return createUnknownAction(m);
}
static QWidget *createUnknownActionWidget()
{
return new QLabel(obs_module_text("AdvSceneSwitcher.action.unknown"));
}
QWidget *MacroActionFactory::CreateWidget(const std::string &id,
QWidget *parent,
std::shared_ptr<MacroAction> action)
{
if (auto it = GetMap().find(id); it != GetMap().end())
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._createWidget(parent, action);
}
return nullptr;
return createUnknownActionWidget();
}
std::string MacroActionFactory::GetActionName(const std::string &id)
{
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._name;
}
return "unknown action";
return obs_module_text("AdvSceneSwitcher.action.unknown");
}
std::string MacroActionFactory::GetIdByName(const QString &name)
{
std::lock_guard<std::mutex> lock(mutex);
for (auto it : GetMap()) {
if (name == obs_module_text(it.second._name.c_str())) {
return it.first;

View File

@@ -6,10 +6,10 @@
namespace advss {
struct MacroActionInfo {
using CreateAction = std::shared_ptr<MacroAction> (*)(Macro *m);
using CreateActionWidget = QWidget *(*)(QWidget *parent,
std::shared_ptr<MacroAction>);
CreateAction _create = nullptr;
std::function<std::shared_ptr<MacroAction>(Macro *m)> _create = nullptr;
CreateActionWidget _createWidget = nullptr;
std::string _name;
};
@@ -19,6 +19,7 @@ public:
MacroActionFactory() = delete;
EXPORT static bool Register(const std::string &id, MacroActionInfo);
static bool Deregister(const std::string &id);
static std::shared_ptr<MacroAction> Create(const std::string &id,
Macro *m);
static QWidget *CreateWidget(const std::string &id, QWidget *parent,

View File

@@ -0,0 +1,92 @@
#include "macro-action-script.hpp"
#include "layout-helpers.hpp"
#include "macro-helpers.hpp"
#include "properties-view.hpp"
#include "sync-helpers.hpp"
namespace advss {
MacroActionScript::MacroActionScript(Macro *m, const std::string &id,
const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &completionSignal)
: MacroAction(m),
MacroSegmentScript(defaultSettings, propertiesSignalName,
triggerSignal, completionSignal),
_id(id)
{
}
MacroActionScript::MacroActionScript(const advss::MacroActionScript &other)
: MacroAction(other.GetMacro()),
MacroSegmentScript(other),
_id(other._id)
{
}
bool MacroActionScript::PerformAction()
{
if (!ScriptHandler::ActionIdIsValid(_id)) {
blog(LOG_WARNING, "skipping unknown script action \"%s\"",
_id.c_str());
return true;
}
(void)SendTriggerSignal();
return true;
}
void MacroActionScript::LogAction() const
{
ablog(LOG_INFO, "performing script action \"%s\"", GetId().c_str());
}
bool MacroActionScript::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
MacroSegmentScript::Save(obj);
return true;
}
bool MacroActionScript::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
MacroSegmentScript::Load(obj);
return true;
}
std::shared_ptr<MacroAction> MacroActionScript::Copy() const
{
return std::make_shared<MacroActionScript>(*this);
}
void MacroActionScript::WaitForCompletion() const
{
using namespace std::chrono_literals;
auto start = std::chrono::high_resolution_clock::now();
auto timePassed = std::chrono::duration_cast<std::chrono::milliseconds>(
start - start);
const auto timeoutMs = GetTimeoutSeconds() * 1000.0;
std::unique_lock<std::mutex> lock(*GetMutex());
while (!TriggerIsCompleted()) {
if (MacroWaitShouldAbort() || MacroIsStopped(GetMacro())) {
break;
}
if ((double)timePassed.count() > timeoutMs) {
blog(LOG_INFO, "script action timeout (%s)",
_id.c_str());
break;
}
GetMacroWaitCV().wait_for(lock, 10ms);
const auto now = std::chrono::high_resolution_clock::now();
timePassed =
std::chrono::duration_cast<std::chrono::milliseconds>(
now - start);
}
}
} // namespace advss

View File

@@ -0,0 +1,29 @@
#pragma once
#include "macro-action-edit.hpp"
#include "macro-script-handler.hpp"
#include "macro-segment-script.hpp"
namespace advss {
class MacroActionScript : public MacroAction, public MacroSegmentScript {
public:
MacroActionScript(Macro *m, const std::string &id,
const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &signalComplete);
MacroActionScript(const advss::MacroActionScript &);
bool PerformAction();
void LogAction() const;
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetId() const { return _id; };
std::shared_ptr<MacroAction> Copy() const;
private:
void WaitForCompletion() const;
std::string _id = "";
};
} // namespace advss

View File

@@ -1,7 +1,24 @@
#include "macro-condition-factory.hpp"
#include <mutex>
namespace advss {
namespace {
class MacroConditionUnknown : public MacroCondition {
public:
MacroConditionUnknown(Macro *m) : MacroCondition(m) {}
bool CheckCondition() { return false; }
bool Save(obs_data_t *obj) const { return MacroCondition::Save(obj); };
bool Load(obs_data_t *obj) { return MacroCondition::Load(obj); };
std::string GetId() const { return "unknown"; }
};
} // namespace
static std::mutex mutex;
std::map<std::string, MacroConditionInfo> &MacroConditionFactory::GetMap()
{
static std::map<std::string, MacroConditionInfo> _methods;
@@ -11,6 +28,7 @@ std::map<std::string, MacroConditionInfo> &MacroConditionFactory::GetMap()
bool MacroConditionFactory::Register(const std::string &id,
MacroConditionInfo info)
{
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it == GetMap().end()) {
GetMap()[id] = info;
return true;
@@ -18,35 +36,60 @@ bool MacroConditionFactory::Register(const std::string &id,
return false;
}
bool MacroConditionFactory::Deregister(const std::string &id)
{
std::lock_guard<std::mutex> lock(mutex);
if (GetMap().count(id) == 0) {
return false;
}
GetMap().erase(id);
return true;
}
static std::shared_ptr<MacroCondition> createUnknownCondition(Macro *m)
{
return std::make_shared<MacroConditionUnknown>(m);
}
std::shared_ptr<MacroCondition>
MacroConditionFactory::Create(const std::string &id, Macro *m)
{
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._create(m);
}
return nullptr;
return createUnknownCondition(m);
}
static QWidget *createUnknownConditionWidget()
{
return new QLabel(
obs_module_text("AdvSceneSwitcher.condition.unknown"));
}
QWidget *
MacroConditionFactory::CreateWidget(const std::string &id, QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._createWidget(parent, cond);
}
return nullptr;
return createUnknownConditionWidget();
}
std::string MacroConditionFactory::GetConditionName(const std::string &id)
{
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._name;
}
return "unknown condition";
return obs_module_text("AdvSceneSwitcher.condition.unknown");
}
std::string MacroConditionFactory::GetIdByName(const QString &name)
{
std::lock_guard<std::mutex> lock(mutex);
for (auto it : GetMap()) {
if (name == obs_module_text(it.second._name.c_str())) {
return it.first;
@@ -57,6 +100,7 @@ std::string MacroConditionFactory::GetIdByName(const QString &name)
bool MacroConditionFactory::UsesDurationModifier(const std::string &id)
{
std::lock_guard<std::mutex> lock(mutex);
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._useDurationModifier;
}

View File

@@ -6,10 +6,11 @@
namespace advss {
struct MacroConditionInfo {
using CreateCondition = std::shared_ptr<MacroCondition> (*)(Macro *m);
using CreateConditionWidget =
QWidget *(*)(QWidget *parent, std::shared_ptr<MacroCondition>);
CreateCondition _create = nullptr;
std::function<std::shared_ptr<MacroCondition>(Macro *m)> _create =
nullptr;
CreateConditionWidget _createWidget = nullptr;
std::string _name;
bool _useDurationModifier = true;
@@ -19,6 +20,7 @@ class MacroConditionFactory {
public:
MacroConditionFactory() = delete;
EXPORT static bool Register(const std::string &, MacroConditionInfo);
static bool Deregister(const std::string &);
static std::shared_ptr<MacroCondition> Create(const std::string &,
Macro *m);
static QWidget *CreateWidget(const std::string &id, QWidget *parent,

View File

@@ -0,0 +1,79 @@
#include "macro-condition-script.hpp"
#include "layout-helpers.hpp"
#include "macro-helpers.hpp"
#include "sync-helpers.hpp"
namespace advss {
MacroConditionScript::MacroConditionScript(
Macro *m, const std::string &id, const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal, const std::string &completionSignal)
: MacroCondition(m),
MacroSegmentScript(defaultSettings, propertiesSignalName,
triggerSignal, completionSignal),
_id(id)
{
}
MacroConditionScript::MacroConditionScript(
const advss::MacroConditionScript &other)
: MacroCondition(other.GetMacro()),
MacroSegmentScript(other),
_id(other._id)
{
}
bool MacroConditionScript::CheckCondition()
{
if (!ScriptHandler::ConditionIdIsValid(_id)) {
blog(LOG_WARNING, "skipping unknown script condition \"%s\"",
_id.c_str());
return false;
}
return SendTriggerSignal();
}
bool MacroConditionScript::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
MacroSegmentScript::Save(obj);
return true;
}
bool MacroConditionScript::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
MacroSegmentScript::Load(obj);
return true;
}
void MacroConditionScript::WaitForCompletion() const
{
using namespace std::chrono_literals;
auto start = std::chrono::high_resolution_clock::now();
auto timePassed = std::chrono::duration_cast<std::chrono::milliseconds>(
start - start);
const auto timeoutMs = GetTimeoutSeconds() * 1000.0;
while (!TriggerIsCompleted()) {
if (MacroWaitShouldAbort() || MacroIsStopped(GetMacro())) {
break;
}
if ((double)timePassed.count() > timeoutMs) {
blog(LOG_INFO, "script condition timeout (%s)",
_id.c_str());
break;
}
std::this_thread::sleep_for(10ms);
const auto now = std::chrono::high_resolution_clock::now();
timePassed =
std::chrono::duration_cast<std::chrono::milliseconds>(
now - start);
}
}
} // namespace advss

View File

@@ -0,0 +1,27 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "macro-script-handler.hpp"
#include "macro-segment-script.hpp"
namespace advss {
class MacroConditionScript : public MacroCondition, public MacroSegmentScript {
public:
MacroConditionScript(Macro *m, const std::string &id,
const OBSData &defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &signalComplete);
MacroConditionScript(const advss::MacroConditionScript &);
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetId() const { return _id; };
private:
void WaitForCompletion() const;
std::string _id = "";
};
} // namespace advss

View File

@@ -0,0 +1,447 @@
#include "macro-script-handler.hpp"
#include "macro-action-script.hpp"
#include "macro-condition-script.hpp"
#include "plugin-state-helpers.hpp"
#include "log-helper.hpp"
#include "variable.hpp"
#include <obs-properties.h>
namespace advss {
std::mutex ScriptHandler::_mutex = {};
std::unordered_map<std::string, ScriptSegmentType> ScriptHandler::_actions = {};
std::unordered_map<std::string, ScriptSegmentType> ScriptHandler::_conditions =
{};
/* Procedure handler helpers */
#define RETURN_STATUS(status) \
{ \
calldata_set_bool(data, "success", status); \
return; \
}
#define RETURN_SUCCESS() RETURN_STATUS(true);
#define RETURN_FAILURE() RETURN_STATUS(false);
static constexpr std::string_view nameParam = "name";
static constexpr std::string_view defaultSettingsParam = "default_settings";
static constexpr std::string_view propertiesSignalParam =
"properties_signal_name";
static constexpr std::string_view triggerSignalParam = "trigger_signal_name";
static std::string getRegisterScriptSegmentDeclString(const char *funcName)
{
return std::string("bool ") + funcName + "(in string " +
nameParam.data() + ", in ptr " + defaultSettingsParam.data() +
", out string " + propertiesSignalParam.data() +
", out string " + triggerSignalParam.data() + ")";
}
static std::string getDeregisterScriptSegmentDeclString(const char *funcName)
{
return std::string("bool ") + funcName + "(in string " +
nameParam.data() + ")";
}
/* Script actions */
static constexpr std::string_view registerActionFuncName =
"advss_register_script_action";
static constexpr std::string_view deregisterActionFuncName =
"advss_deregister_script_action";
static const std::string registerScriptActionDeclString =
getRegisterScriptSegmentDeclString(registerActionFuncName.data());
static const std::string deregisterScriptActionDeclString =
getDeregisterScriptSegmentDeclString(deregisterActionFuncName.data());
/* Script conditions */
static constexpr std::string_view registerConditionFuncName =
"advss_register_script_condition";
static constexpr std::string_view deregisterConditionFuncName =
"advss_deregister_script_condition";
static const std::string registerScriptConditionDeclString =
getRegisterScriptSegmentDeclString(registerConditionFuncName.data());
static const std::string deregisterScriptConditionDeclString =
getDeregisterScriptSegmentDeclString(
deregisterConditionFuncName.data());
/* Script variables */
static constexpr std::string_view valueParam = "value";
static constexpr std::string_view getVariableValueFuncName =
"advss_get_variable_value";
static constexpr std::string_view setVariableValueFuncName =
"advss_set_variable_value";
static const std::string getVariableValueDeclString =
std::string("bool ") + getVariableValueFuncName.data() + "(in string " +
nameParam.data() + ", out string " + valueParam.data() + ")";
static const std::string setVariableValueDeclString =
std::string("bool ") + setVariableValueFuncName.data() + "(in string " +
nameParam.data() + ", in string " + valueParam.data() + ")";
static bool setup();
static bool setupDone = setup();
static bool setup()
{
proc_handler_t *ph = obs_get_proc_handler();
assert(ph != NULL);
proc_handler_add(ph, registerScriptActionDeclString.c_str(),
&ScriptHandler::RegisterScriptAction, nullptr);
proc_handler_add(ph, deregisterScriptActionDeclString.c_str(),
&ScriptHandler::DeregisterScriptAction, nullptr);
proc_handler_add(ph, registerScriptConditionDeclString.c_str(),
&ScriptHandler::RegisterScriptCondition, nullptr);
proc_handler_add(ph, deregisterScriptConditionDeclString.c_str(),
&ScriptHandler::DeregisterScriptCondition, nullptr);
proc_handler_add(ph, getVariableValueDeclString.c_str(),
&ScriptHandler::GetVariableValue, nullptr);
proc_handler_add(ph, setVariableValueDeclString.c_str(),
&ScriptHandler::SetVariableValue, nullptr);
return true;
}
static void replaceWhitespace(std::string &string)
{
std::transform(string.begin(), string.end(), string.begin(), [](char c) {
return std::isspace(static_cast<unsigned char>(c)) ? '_' : c;
});
}
static std::string nameToScriptID(const std::string &name)
{
return std::string("script_") + name;
}
static std::string getTriggerSignal(const std::string &name,
const bool isAction)
{
std::string signal = name;
replaceWhitespace(signal);
signal += "_run";
signal += isAction ? "_action" : "_condition";
return signal;
}
static std::string getCompletionSignal(const std::string &name,
const bool isAction)
{
auto signal = getTriggerSignal(name, isAction);
signal += "_complete";
return signal;
}
static std::string getPropertiesSignal(const std::string &name,
const bool isAction)
{
std::string signal = name;
replaceWhitespace(signal);
signal += isAction ? "_action" : "_condition";
signal += "_get_properties";
return signal;
}
void ScriptHandler::RegisterScriptAction(void *, calldata_t *data)
{
const char *actionName;
if (!calldata_get_string(data, nameParam.data(), &actionName) ||
strlen(actionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerScriptActionDeclString.data(), nameParam.data());
RETURN_FAILURE();
}
obs_data_t *defaultSettingsPtr = nullptr;
if (!calldata_get_ptr(data, defaultSettingsParam.data(),
&defaultSettingsPtr)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerActionFuncName.data(),
defaultSettingsParam.data());
RETURN_FAILURE();
}
std::lock_guard<std::mutex> lock(_mutex);
OBSData defaultSettings(defaultSettingsPtr);
obs_data_release(defaultSettingsPtr);
if (_actions.count(actionName) > 0) {
blog(LOG_WARNING, "[%s] failed! Action \"%s\" already exists!",
registerActionFuncName.data(), actionName);
RETURN_FAILURE();
}
const std::string id = nameToScriptID(actionName);
auto triggerSignalName = getTriggerSignal(actionName, true);
auto completionSignalName = getCompletionSignal(actionName, true);
auto propertiesSignalName = getPropertiesSignal(actionName, true);
const auto createScriptAction =
[id, defaultSettings, propertiesSignalName, triggerSignalName,
completionSignalName](
Macro *m) -> std::shared_ptr<MacroAction> {
return std::make_shared<MacroActionScript>(
m, id, defaultSettings, propertiesSignalName,
triggerSignalName, completionSignalName);
};
if (!MacroActionFactory::Register(id, {createScriptAction,
MacroSegmentScriptEdit::Create,
actionName})) {
blog(LOG_WARNING,
"[%s] failed! Action id \"%s\" already exists!",
registerActionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
blog(LOG_INFO, "[%s] successful for \"%s\"",
registerActionFuncName.data(), actionName);
calldata_set_string(data, triggerSignalParam.data(),
triggerSignalName.c_str());
calldata_set_string(data, propertiesSignalParam.data(),
propertiesSignalName.c_str());
_actions.emplace(id, ScriptSegmentType(id, propertiesSignalName,
triggerSignalName,
completionSignalName));
RETURN_SUCCESS();
}
void ScriptHandler::DeregisterScriptAction(void *, calldata_t *data)
{
const char *actionName;
if (!calldata_get_string(data, nameParam.data(), &actionName) ||
strlen(actionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
deregisterActionFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
const std::string id = nameToScriptID(actionName);
std::lock_guard<std::mutex> lock(_mutex);
if (_actions.count(id) == 0) {
blog(LOG_WARNING,
"[%s] failed! Action \"%s\" was never registered!",
deregisterActionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
if (!MacroActionFactory::Deregister(id)) {
blog(LOG_WARNING,
"[%s] failed! Action id \"%s\" does not exist!",
deregisterActionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
auto it = _actions.find(id);
if (it != _actions.end()) {
_actions.erase(it);
}
RETURN_SUCCESS();
}
void ScriptHandler::RegisterScriptCondition(void *, calldata_t *data)
{
const char *conditionName;
if (!calldata_get_string(data, nameParam.data(), &conditionName) ||
strlen(conditionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerScriptConditionDeclString.data(),
nameParam.data());
RETURN_FAILURE();
}
obs_data_t *defaultSettingsPtr = nullptr;
if (!calldata_get_ptr(data, defaultSettingsParam.data(),
&defaultSettingsPtr)) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
registerScriptConditionDeclString.data(),
defaultSettingsParam.data());
RETURN_FAILURE();
}
std::lock_guard<std::mutex> lock(_mutex);
OBSData defaultSettings(defaultSettingsPtr);
obs_data_release(defaultSettingsPtr);
if (_conditions.count(conditionName) > 0) {
blog(LOG_WARNING,
"[%s] failed! Condition \"%s\" already exists!",
registerConditionFuncName.data(), conditionName);
RETURN_FAILURE();
}
const std::string id = nameToScriptID(conditionName);
auto triggerSignalName = getTriggerSignal(conditionName, false);
auto completionSignalName = getCompletionSignal(conditionName, false);
auto propertiesSignalName = getPropertiesSignal(conditionName, false);
const auto createScriptCondition =
[id, defaultSettings, propertiesSignalName, triggerSignalName,
completionSignalName](
Macro *m) -> std::shared_ptr<MacroCondition> {
return std::make_shared<MacroConditionScript>(
m, id, defaultSettings, propertiesSignalName,
triggerSignalName, completionSignalName);
};
if (!MacroConditionFactory::Register(
id, {createScriptCondition, MacroSegmentScriptEdit::Create,
conditionName})) {
blog(LOG_WARNING,
"[%s] failed! Condition id \"%s\" already exists!",
registerConditionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
blog(LOG_INFO, "[%s] successful for \"%s\"",
registerConditionFuncName.data(), conditionName);
calldata_set_string(data, triggerSignalParam.data(),
triggerSignalName.c_str());
calldata_set_string(data, propertiesSignalParam.data(),
propertiesSignalName.c_str());
_conditions.emplace(id, ScriptSegmentType(id, propertiesSignalName,
triggerSignalName,
completionSignalName));
RETURN_SUCCESS();
}
void ScriptHandler::DeregisterScriptCondition(void *, calldata_t *data)
{
const char *conditionName;
if (!calldata_get_string(data, nameParam.data(), &conditionName) ||
strlen(conditionName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
deregisterConditionFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
const std::string id = nameToScriptID(conditionName);
std::lock_guard<std::mutex> lock(_mutex);
if (_conditions.count(id) == 0) {
blog(LOG_WARNING,
"[%s] failed! Condition \"%s\" was never registered!",
deregisterConditionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
if (!MacroConditionFactory::Deregister(id)) {
blog(LOG_WARNING,
"[%s] failed! Condition id \"%s\" does not exist!",
deregisterConditionFuncName.data(), id.c_str());
RETURN_FAILURE();
}
auto it = _conditions.find(id);
if (it != _conditions.end()) {
_conditions.erase(it);
}
RETURN_SUCCESS();
}
void ScriptHandler::GetVariableValue(void *, calldata_t *data)
{
const char *variableName;
if (!calldata_get_string(data, nameParam.data(), &variableName) ||
strlen(variableName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
getVariableValueFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
auto weakVariable = GetWeakVariableByName(variableName);
auto variable = weakVariable.lock();
if (!variable) {
blog(LOG_WARNING,
"[%s] failed! \"%s\" variable does not exist!",
getVariableValueFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
calldata_set_string(data, valueParam.data(), variable->Value().c_str());
RETURN_SUCCESS();
}
void ScriptHandler::SetVariableValue(void *, calldata_t *data)
{
const char *variableName;
if (!calldata_get_string(data, nameParam.data(), &variableName) ||
strlen(variableName) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
getVariableValueFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
const char *variableValue;
if (!calldata_get_string(data, valueParam.data(), &variableValue) ||
strlen(variableValue) == 0) {
blog(LOG_WARNING, "[%s] failed! \"%s\" parameter missing!",
getVariableValueFuncName.data(), valueParam.data());
RETURN_FAILURE();
}
auto weakVariable = GetWeakVariableByName(variableName);
auto variable = weakVariable.lock();
if (!variable) {
blog(LOG_WARNING,
"[%s] failed! \"%s\" variable does not exist!",
getVariableValueFuncName.data(), nameParam.data());
RETURN_FAILURE();
}
variable->SetValue(variableValue);
RETURN_SUCCESS();
}
bool ScriptHandler::ActionIdIsValid(const std::string &id)
{
std::lock_guard<std::mutex> lock(_mutex);
return _actions.count(id) > 0;
}
bool ScriptHandler::ConditionIdIsValid(const std::string &id)
{
std::lock_guard<std::mutex> lock(_mutex);
return _conditions.count(id) > 0;
}
static std::string signalNameToTriggerSignalDecl(const std::string &name)
{
return std::string("void ") + name + "()";
}
static std::string signalNameToPropertiesSignalDecl(const std::string &name)
{
return std::string("void ") + name + "(in ptr " +
GetPropertiesSignalParamName().data() + ")";
}
static std::string signalNameToCompletionSignalDecl(const std::string &name)
{
return std::string("bool ") + name + "(in int " +
GeCompletionIdParamName().data() + ")";
}
ScriptSegmentType::ScriptSegmentType(const std::string &id,
const std::string &propertiesSignal,
const std::string &triggerSignal,
const std::string &completionSignal)
: _id(id)
{
signal_handler_add(
obs_get_signal_handler(),
signalNameToPropertiesSignalDecl(propertiesSignal).c_str());
signal_handler_add(
obs_get_signal_handler(),
signalNameToTriggerSignalDecl(triggerSignal).c_str());
signal_handler_add(
obs_get_signal_handler(),
signalNameToCompletionSignalDecl(completionSignal).c_str());
}
} // namespace advss

View File

@@ -0,0 +1,59 @@
#pragma once
#include <obs.hpp>
#include <mutex>
#include <string>
#include <unordered_map>
namespace advss {
class ScriptSegmentType {
public:
ScriptSegmentType() = delete;
ScriptSegmentType(const std::string &id,
const std::string &propertiesSignal,
const std::string &triggerSignal,
const std::string &completionSignal);
private:
std::string _id;
};
class ScriptHandler {
public:
ScriptHandler() = delete;
static void RegisterScriptAction(void *ctx, calldata_t *data);
static void DeregisterScriptAction(void *ctx, calldata_t *data);
static void RegisterScriptCondition(void *ctx, calldata_t *data);
static void DeregisterScriptCondition(void *ctx, calldata_t *data);
static void GetVariableValue(void *ctx, calldata_t *data);
static void SetVariableValue(void *ctx, calldata_t *data);
static bool ActionIdIsValid(const std::string &id);
static bool ConditionIdIsValid(const std::string &id);
private:
static std::mutex _mutex;
static std::unordered_map<std::string, ScriptSegmentType> _actions;
static std::unordered_map<std::string, ScriptSegmentType> _conditions;
};
static constexpr std::string_view GetPropertiesSignalParamName()
{
return "properties";
}
static constexpr std::string_view GetActionCompletionSignalParamName()
{
return "completion_signal_name";
}
static constexpr std::string_view GeCompletionIdParamName()
{
return "completion_id";
}
static constexpr std::string_view GeResultSignalParamName()
{
return "result";
}
} // namespace advss

View File

@@ -0,0 +1,219 @@
#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;
MacroSegmentScript::MacroSegmentScript(obs_data_t *defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &completionSignal)
: _settings(obs_data_get_defaults(defaultSettings)),
_propertiesSignal(propertiesSignalName),
_triggerSignal(triggerSignal),
_completionSignal(completionSignal)
{
signal_handler_connect(obs_get_signal_handler(),
completionSignal.c_str(),
&MacroSegmentScript::CompletionSignalReceived,
this);
}
MacroSegmentScript::MacroSegmentScript(const MacroSegmentScript &other)
: _settings(obs_data_create()),
_propertiesSignal(other._propertiesSignal),
_triggerSignal(other._triggerSignal),
_completionSignal(other._completionSignal)
{
signal_handler_connect(obs_get_signal_handler(),
_completionSignal.c_str(),
&MacroSegmentScript::CompletionSignalReceived,
this);
obs_data_apply(_settings.Get(), other._settings.Get());
}
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, GeCompletionIdParamName().data(), _completionId);
calldata_set_string(data, "settings", obs_data_get_json(GetSettings()));
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<MacroSegmentScript *>(param);
long long int id;
if (!calldata_get_int(data, GeCompletionIdParamName().data(), &id)) {
blog(LOG_WARNING,
"received completion signal without \"%s\" parameter",
GeCompletionIdParamName().data());
return;
}
bool result;
if (!calldata_get_bool(data, GeResultSignalParamName().data(),
&result)) {
blog(LOG_WARNING,
"received completion signal without \"%s\" parameter",
GeResultSignalParamName().data());
return;
}
if (id != segment->_completionId) {
return;
}
segment->_triggerIsComplete = true;
segment->_triggerResult = result;
}
obs_properties_t *MacroSegmentScriptEdit::GetProperties(void *obj)
{
auto segmentEdit = reinterpret_cast<MacroSegmentScriptEdit *>(obj);
if (!segmentEdit) {
return nullptr;
}
return segmentEdit->_entryData->GetProperties();
}
void MacroSegmentScriptEdit::UpdateSettings(void *obj, obs_data_t *settings)
{
auto segmentEdit = reinterpret_cast<MacroSegmentScriptEdit *>(obj);
if (!segmentEdit || segmentEdit->_loading || !segmentEdit->_entryData) {
return;
}
auto lock = LockContext();
segmentEdit->_entryData->UpdateSettings(settings);
}
MacroSegmentScriptEdit::MacroSegmentScriptEdit(
QWidget *parent, std::shared_ptr<MacroSegmentScript> 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<MacroAction> segment)
{
return new MacroSegmentScriptEdit(
parent, std::dynamic_pointer_cast<MacroSegmentScript>(segment));
}
QWidget *MacroSegmentScriptEdit::Create(QWidget *parent,
std::shared_ptr<MacroCondition> segment)
{
return new MacroSegmentScriptEdit(
parent, std::dynamic_pointer_cast<MacroSegmentScript>(segment));
}
void MacroSegmentScriptEdit::TimeoutChanged(const Duration &timeout)
{
GUARD_LOADING_AND_LOCK();
_entryData->_timeout = timeout;
}
} // namespace advss

View File

@@ -0,0 +1,78 @@
#pragma once
#include "duration-control.hpp"
#include "macro-script-handler.hpp"
#include <atomic>
#include <obs-data.h>
namespace advss {
class Macro;
class MacroAction;
class MacroCondition;
class MacroSegmentScript {
public:
MacroSegmentScript(obs_data_t *defaultSettings,
const std::string &propertiesSignalName,
const std::string &triggerSignal,
const std::string &completionSignal);
MacroSegmentScript(const advss::MacroSegmentScript &);
protected:
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
obs_properties_t *GetProperties() const;
OBSData GetSettings() const { return _settings.Get(); }
void UpdateSettings(obs_data_t *newSettings) const;
bool SendTriggerSignal();
double GetTimeoutSeconds() const { return _timeout.Seconds(); };
bool TriggerIsCompleted() const { return _triggerIsComplete; }
private:
virtual void WaitForCompletion() const = 0;
static void CompletionSignalReceived(void *param, calldata_t *data);
OBSDataAutoRelease _settings;
std::string _propertiesSignal = "";
std::string _triggerSignal = "";
std::string _completionSignal = "";
std::atomic_bool _triggerIsComplete = {false};
bool _triggerResult = false;
int64_t _completionId = 0;
Duration _timeout = Duration(10.0);
friend class MacroSegmentScriptEdit;
};
class MacroSegmentScriptEdit : public QWidget {
Q_OBJECT
public:
MacroSegmentScriptEdit(
QWidget *parent,
std::shared_ptr<MacroSegmentScript> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> segment);
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> segment);
private slots:
void TimeoutChanged(const Duration &);
private:
static obs_properties_t *GetProperties(void *obj);
static void UpdateSettings(void *obj, obs_data_t *settings);
DurationSelection *_timeout;
std::shared_ptr<MacroSegmentScript> _entryData;
bool _loading = true;
};
} // namespace advss

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,264 @@
#pragma once
#include <obs-data.h>
#include <obs.hpp>
#include <qtimer.h>
#include <QPointer>
#include <vector>
#include <memory>
#include <QScrollArea>
#include <QPlainTextEdit>
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(30, 0, 0)
class QFormLayout;
class QLabel;
class QResizeEvent;
typedef obs_properties_t *(*PropertiesReloadCallback)(void *obj);
typedef void (*PropertiesUpdateCallback)(void *obj, obs_data_t *old_settings,
obs_data_t *new_settings);
typedef void (*PropertiesVisualUpdateCb)(void *obj, obs_data_t *settings);
namespace advss {
class OBSPropertiesView;
/* ------------------------------------------------------------------------- */
class VScrollArea : public QScrollArea {
Q_OBJECT
public:
VScrollArea(QWidget *parent = nullptr);
protected:
virtual void resizeEvent(QResizeEvent *event) override;
};
/* ------------------------------------------------------------------------- */
class SpinBoxIgnoreScroll : public QSpinBox {
Q_OBJECT
public:
SpinBoxIgnoreScroll(QWidget *parent = nullptr);
protected:
virtual void wheelEvent(QWheelEvent *event) override;
};
/* ------------------------------------------------------------------------- */
class OBSPlainTextEdit : public QPlainTextEdit {
Q_OBJECT
public:
explicit OBSPlainTextEdit(QWidget *parent = nullptr,
bool monospace = true);
};
/* ------------------------------------------------------------------------- */
class WidgetInfo : public QObject {
Q_OBJECT
friend class OBSPropertiesView;
private:
OBSPropertiesView *view;
obs_property_t *property;
QWidget *widget;
QPointer<QTimer> update_timer;
bool recently_updated = false;
OBSData old_settings_cache;
void BoolChanged(const char *setting);
void IntChanged(const char *setting);
void FloatChanged(const char *setting);
void TextChanged(const char *setting);
bool PathChanged(const char *setting);
void ListChanged(const char *setting);
bool ColorChangedInternal(const char *setting, bool supportAlpha);
bool ColorChanged(const char *setting);
bool ColorAlphaChanged(const char *setting);
bool FontChanged(const char *setting);
void GroupChanged(const char *setting);
void EditableListChanged();
void ButtonClicked();
void TogglePasswordText(bool checked);
public:
inline WidgetInfo(OBSPropertiesView *view_, obs_property_t *prop,
QWidget *widget_)
: view(view_),
property(prop),
widget(widget_)
{
}
~WidgetInfo()
{
if (update_timer) {
update_timer->stop();
QMetaObject::invokeMethod(update_timer, "timeout");
update_timer->deleteLater();
}
}
public slots:
void ControlChanged();
/* editable list */
void EditListAdd();
void EditListAddText();
void EditListAddFiles();
void EditListAddDir();
void EditListRemove();
void EditListEdit();
void EditListUp();
void EditListDown();
};
/* ------------------------------------------------------------------------- */
class OBSPropertiesView : public VScrollArea {
Q_OBJECT
friend class WidgetInfo;
using properties_delete_t = decltype(&obs_properties_destroy);
using properties_t =
std::unique_ptr<obs_properties_t, properties_delete_t>;
private:
QWidget *widget = nullptr;
properties_t properties;
OBSData settings;
OBSWeakObjectAutoRelease weakObj;
void *rawObj = nullptr;
std::string type;
PropertiesReloadCallback reloadCallback;
PropertiesUpdateCallback callback = nullptr;
PropertiesVisualUpdateCb visUpdateCb = nullptr;
int minSize;
std::vector<std::unique_ptr<WidgetInfo>> children;
std::string lastFocused;
QWidget *lastWidget = nullptr;
bool deferUpdate;
bool enableDefer = true;
template<typename Sender, typename SenderParent, typename... Args>
QWidget *NewWidget(obs_property_t *prop, Sender *widget,
void (SenderParent::*signal)(Args...));
QWidget *AddCheckbox(obs_property_t *prop);
QWidget *AddText(obs_property_t *prop, QFormLayout *layout,
QLabel *&label);
void AddPath(obs_property_t *prop, QFormLayout *layout, QLabel **label);
void AddInt(obs_property_t *prop, QFormLayout *layout, QLabel **label);
void AddFloat(obs_property_t *prop, QFormLayout *layout,
QLabel **label);
QWidget *AddList(obs_property_t *prop, bool &warning);
void AddEditableList(obs_property_t *prop, QFormLayout *layout,
QLabel *&label);
QWidget *AddButton(obs_property_t *prop);
void AddColorInternal(obs_property_t *prop, QFormLayout *layout,
QLabel *&label, bool supportAlpha);
void AddColor(obs_property_t *prop, QFormLayout *layout,
QLabel *&label);
void AddColorAlpha(obs_property_t *prop, QFormLayout *layout,
QLabel *&label);
void AddFont(obs_property_t *prop, QFormLayout *layout, QLabel *&label);
void AddFrameRate(obs_property_t *prop, bool &warning,
QFormLayout *layout, QLabel *&label);
void AddGroup(obs_property_t *prop, QFormLayout *layout);
void AddProperty(obs_property_t *property, QFormLayout *layout);
void resizeEvent(QResizeEvent *event) override;
void GetScrollPos(int &h, int &v, int &hend, int &vend);
void SetScrollPos(int h, int v, int old_hend, int old_vend);
private slots:
void RefreshProperties();
public slots:
void ReloadProperties();
void SignalChanged();
signals:
void PropertiesResized();
void Changed();
void PropertiesRefreshed();
public:
OBSPropertiesView(OBSData settings, obs_object_t *obj,
PropertiesReloadCallback reloadCallback,
PropertiesUpdateCallback callback,
PropertiesVisualUpdateCb cb = nullptr,
int minSize = 0);
OBSPropertiesView(OBSData settings, void *obj,
PropertiesReloadCallback reloadCallback,
PropertiesUpdateCallback callback,
PropertiesVisualUpdateCb cb = nullptr,
int minSize = 0);
OBSPropertiesView(OBSData settings, const char *type,
PropertiesReloadCallback reloadCallback,
int minSize = 0);
#define obj_constructor(type) \
inline OBSPropertiesView(OBSData settings, obs_##type##_t *type, \
PropertiesReloadCallback reloadCallback, \
PropertiesUpdateCallback callback, \
PropertiesVisualUpdateCb cb = nullptr, \
int minSize = 0) \
: OBSPropertiesView(settings, (obs_object_t *)type, \
reloadCallback, callback, cb, minSize) \
{ \
}
obj_constructor(source);
obj_constructor(output);
obj_constructor(encoder);
obj_constructor(service);
#undef obj_constructor
inline obs_data_t *GetSettings() const { return settings; }
inline void UpdateSettings()
{
if (callback)
callback(OBSGetStrongRef(weakObj), nullptr, settings);
else if (visUpdateCb)
visUpdateCb(OBSGetStrongRef(weakObj), settings);
}
inline bool DeferUpdate() const { return deferUpdate; }
inline void SetDeferrable(bool deferrable) { enableDefer = deferrable; }
inline OBSObject GetObject() const { return OBSGetStrongRef(weakObj); }
#define Def_IsObject(type) \
inline bool IsObject(obs_##type##_t *type) const \
{ \
OBSObject obj = OBSGetStrongRef(weakObj); \
return obj.Get() == (obs_object_t *)type; \
}
/* clang-format off */
Def_IsObject(source)
Def_IsObject(output)
Def_IsObject(encoder)
Def_IsObject(service)
/* clang-format on */
#undef Def_IsObject
};
} // namespace advss
#endif

View File

@@ -0,0 +1,63 @@
#pragma once
#include <QComboBox>
#include <QLabel>
#include <QSpinBox>
#include <QStackedWidget>
#include <QWidget>
#include <obs.h>
#include <media-io/frame-rate.h>
#include <vector>
#ifdef _MSC_VER
#pragma warning(disable : 4505)
#endif
static bool operator!=(const media_frames_per_second &a,
const media_frames_per_second &b)
{
return a.numerator != b.numerator || a.denominator != b.denominator;
}
static bool operator==(const media_frames_per_second &a,
const media_frames_per_second &b)
{
return !(a != b);
}
using frame_rate_range_t =
std::pair<media_frames_per_second, media_frames_per_second>;
using frame_rate_ranges_t = std::vector<frame_rate_range_t>;
class OBSFrameRatePropertyWidget : public QWidget {
Q_OBJECT
public:
frame_rate_ranges_t fps_ranges;
QComboBox *modeSelect = nullptr;
QStackedWidget *modeDisplay = nullptr;
QWidget *labels = nullptr;
QLabel *currentFPS = nullptr;
QLabel *timePerFrame = nullptr;
QLabel *minLabel = nullptr;
QLabel *maxLabel = nullptr;
QComboBox *simpleFPS = nullptr;
QComboBox *fpsRange = nullptr;
QSpinBox *numEdit = nullptr;
QSpinBox *denEdit = nullptr;
bool updating = false;
const char *name = nullptr;
obs_data_t *settings = nullptr;
QLabel *warningLabel = nullptr;
OBSFrameRatePropertyWidget() = default;
};