Files
SceneSwitcher/plugins/base/utils/source-setting.cpp
WarmUpTill 3ffe3c2fca Fix setting source settings without default value
Example text source's text field didn't have default value.
So, if never set before, can't be set using the "Source" action type.

Needed now that it is possible to create new sources via macro.
2026-10-24 21:32:32 +02:00

577 lines
15 KiB
C++

#include "source-setting.hpp"
#include "json-helpers.hpp"
#include "log-helper.hpp"
#include "obs-module-helper.hpp"
#include "math-helpers.hpp"
#include <nlohmann/json.hpp>
#include <QLayout>
Q_DECLARE_METATYPE(advss::SourceSetting);
using properties_delete_t = decltype(&obs_properties_destroy);
using properties_t = std::unique_ptr<obs_properties_t, properties_delete_t>;
namespace advss {
SourceSetting::SourceSetting(const std::string &id, obs_property_type type,
const std::string &description,
const std::string &longDescription)
: _id(id),
_type(type),
_description(description),
_longDescription(longDescription)
{
}
bool SourceSetting::Save(obs_data_t *obj) const
{
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "id", _id.c_str());
obs_data_set_int(data, "type", _type);
obs_data_set_string(data, "description", _description.c_str());
obs_data_set_obj(obj, "sourceSetting", data);
return true;
}
bool SourceSetting::Load(obs_data_t *obj)
{
OBSDataAutoRelease data = obs_data_get_obj(obj, "sourceSetting");
_id = obs_data_get_string(data, "id");
_type = static_cast<obs_property_type>(obs_data_get_int(data, "type"));
_description = obs_data_get_string(data, "description");
return true;
}
static bool isListType(obs_property_type type)
{
return type == OBS_PROPERTY_LIST || type == OBS_PROPERTY_EDITABLE_LIST;
}
bool SourceSetting::IsList() const
{
return isListType(_type);
}
bool SourceSetting::operator==(const SourceSetting &other) const
{
return _id == other._id;
}
static const char *getPropertySuffix(obs_property_type type)
{
switch (type) {
case OBS_PROPERTY_INVALID:
return "AdvSceneSwitcher.settings.suffix.type.invalid";
case OBS_PROPERTY_BOOL:
return "AdvSceneSwitcher.settings.suffix.type.bool";
case OBS_PROPERTY_INT:
return "AdvSceneSwitcher.settings.suffix.type.int";
case OBS_PROPERTY_FLOAT:
return "AdvSceneSwitcher.settings.suffix.type.float";
case OBS_PROPERTY_TEXT:
return "AdvSceneSwitcher.settings.suffix.type.text";
case OBS_PROPERTY_PATH:
return "AdvSceneSwitcher.settings.suffix.type.path";
case OBS_PROPERTY_LIST:
return "AdvSceneSwitcher.settings.suffix.type.list";
case OBS_PROPERTY_COLOR:
return "AdvSceneSwitcher.settings.suffix.type.color";
case OBS_PROPERTY_BUTTON:
return "AdvSceneSwitcher.settings.suffix.type.button";
case OBS_PROPERTY_FONT:
return "AdvSceneSwitcher.settings.suffix.type.font";
case OBS_PROPERTY_EDITABLE_LIST:
return "AdvSceneSwitcher.settings.suffix.type.editableList";
case OBS_PROPERTY_FRAME_RATE:
return "AdvSceneSwitcher.settings.suffix.type.frameRate";
case OBS_PROPERTY_GROUP:
return "AdvSceneSwitcher.settings.suffix.type.group";
case OBS_PROPERTY_COLOR_ALPHA:
return "AdvSceneSwitcher.settings.suffix.type.colorAlpha";
}
return "";
}
static void addSettingsHelper(obs_property_t *property,
std::vector<SourceSetting> &settings,
const std::string &prefix = "")
{
do {
if (!property) {
continue;
}
const auto type = obs_property_get_type(property);
if (type == OBS_PROPERTY_GROUP) {
auto group = obs_property_group_content(property);
auto description = obs_property_description(property);
std::string prefix =
description
? std::string("[") + description + "] "
: "";
addSettingsHelper(obs_properties_first(group), settings,
prefix);
continue;
}
auto name = obs_property_name(property);
if (!name) {
continue;
}
auto description = obs_property_description(property);
if (!description) {
continue;
}
std::string descriptionWithPrefixAndSuffix =
prefix + description +
obs_module_text(getPropertySuffix(type));
auto longDescription = obs_property_long_description(property);
SourceSetting setting(name, type,
descriptionWithPrefixAndSuffix,
longDescription ? longDescription : "");
settings.emplace_back(setting);
} while (obs_property_next(&property));
}
static std::optional<std::string>
getDataJsonWithDefaults(const OBSWeakSource &ws)
{
OBSSourceAutoRelease source = obs_weak_source_get_source(ws);
OBSDataAutoRelease data = obs_source_get_settings(source);
if (!data) {
return {};
}
OBSDataAutoRelease dataWithDefaults = obs_data_get_defaults(data);
obs_data_apply(dataWithDefaults, data);
auto json = obs_data_get_json(dataWithDefaults);
if (!json) {
return {};
}
return json;
}
std::optional<std::string> GetSourceSettingValue(const OBSWeakSource &source,
const SourceSetting &setting)
{
const auto json = getDataJsonWithDefaults(source);
if (!json) {
return {};
}
auto value = GetJsonField(*json, setting.GetID());
return value;
}
static obs_property_t *findListPropertyByIdHelper(obs_property_t *property,
const std::string &id)
{
do {
if (!property) {
continue;
}
const auto type = obs_property_get_type(property);
if (type == OBS_PROPERTY_GROUP) {
auto group = obs_property_group_content(property);
findListPropertyByIdHelper(obs_properties_first(group),
id);
continue;
}
if (!isListType(type)) {
continue;
}
auto name = obs_property_name(property);
if (name != id) {
continue;
}
return property;
} while (obs_property_next(&property));
return nullptr;
}
static std::pair<obs_property_t *, properties_t>
findListPropertyById(const obs_source_t *source, const std::string &id)
{
// Note:
// Using obs_source_properties() here might cause flickering for some
// sources, as this will call obs_properties_apply_settings()
// implicitly.
// So, we use obs_get_source_properties() instead.
//
// 20250617: This might cause issues with the move plugin as it doesn't
// handle obs_get_source_properties() properly in the move filter
auto properties = obs_get_source_properties(obs_source_get_id(source));
if (!properties) {
return {nullptr, properties_t(nullptr, obs_properties_destroy)};
}
auto property = obs_properties_first(properties);
return {findListPropertyByIdHelper(property, id),
properties_t(properties, obs_properties_destroy)};
}
static std::pair<obs_property_t *, properties_t>
findListPropertyById(const OBSWeakSource &ws, const std::string &id)
{
OBSSourceAutoRelease source = obs_weak_source_get_source(ws);
return findListPropertyById(source.Get(), id);
}
std::optional<std::string>
GetSourceSettingListEntryName(const OBSWeakSource &source,
const SourceSetting &setting)
{
if (!setting.IsList()) {
return {};
}
const auto json = getDataJsonWithDefaults(source);
if (!json) {
return {};
}
const auto &[property, properties] =
findListPropertyById(source, setting.GetID());
if (!property) {
return {};
}
const auto type = obs_property_list_format(property);
if (type == OBS_COMBO_FORMAT_INVALID) {
return {};
}
auto currentValue = GetJsonField(*json, setting.GetID());
if (!currentValue) {
return {};
}
const auto count = obs_property_list_item_count(property);
for (size_t i = 0; i < count; i++) {
std::string value;
switch (type) {
case OBS_COMBO_FORMAT_INT:
value = std::to_string(
obs_property_list_item_int(property, i));
break;
case OBS_COMBO_FORMAT_FLOAT:
value = std::to_string(
obs_property_list_item_float(property, i));
break;
case OBS_COMBO_FORMAT_STRING:
value = obs_property_list_item_string(property, i);
break;
case OBS_COMBO_FORMAT_BOOL:
value = std::to_string(
obs_property_list_item_bool(property, i));
break;
default:
break;
}
if (value == currentValue) {
return obs_property_list_item_name(property, i);
}
}
return {};
}
static std::pair<obs_data_type, obs_data_number_type>
getListDataType(obs_source_t *source, const SourceSetting &setting)
{
const auto &[property, properties] =
findListPropertyById(source, setting.GetID());
if (!property) {
return {OBS_DATA_NULL, OBS_DATA_NUM_INVALID};
}
switch (obs_property_list_format(property)) {
case OBS_COMBO_FORMAT_INT:
return {OBS_DATA_NUMBER, OBS_DATA_NUM_INT};
case OBS_COMBO_FORMAT_FLOAT:
return {OBS_DATA_NUMBER, OBS_DATA_NUM_DOUBLE};
case OBS_COMBO_FORMAT_STRING:
return {OBS_DATA_STRING, OBS_DATA_NUM_INVALID};
case OBS_COMBO_FORMAT_BOOL:
return {OBS_DATA_BOOLEAN, OBS_DATA_NUM_INVALID};
default:
break;
}
return {OBS_DATA_NULL, OBS_DATA_NUM_INVALID};
}
// Used for settings which have neither a value nor a default value yet, e.g.
// the "text" setting of a text source which was not created via the OBS UI
static std::pair<obs_data_type, obs_data_number_type>
getDataTypeFromProperty(obs_source_t *source, const SourceSetting &setting)
{
switch (setting.GetType()) {
case OBS_PROPERTY_BOOL:
return {OBS_DATA_BOOLEAN, OBS_DATA_NUM_INVALID};
case OBS_PROPERTY_INT:
case OBS_PROPERTY_COLOR:
case OBS_PROPERTY_COLOR_ALPHA:
return {OBS_DATA_NUMBER, OBS_DATA_NUM_INT};
case OBS_PROPERTY_FLOAT:
return {OBS_DATA_NUMBER, OBS_DATA_NUM_DOUBLE};
case OBS_PROPERTY_TEXT:
case OBS_PROPERTY_PATH:
return {OBS_DATA_STRING, OBS_DATA_NUM_INVALID};
case OBS_PROPERTY_FONT:
return {OBS_DATA_OBJECT, OBS_DATA_NUM_INVALID};
case OBS_PROPERTY_EDITABLE_LIST:
return {OBS_DATA_ARRAY, OBS_DATA_NUM_INVALID};
case OBS_PROPERTY_LIST:
return getListDataType(source, setting);
default:
break;
}
return {OBS_DATA_NULL, OBS_DATA_NUM_INVALID};
}
void SetSourceSetting(obs_source_t *source, const SourceSetting &setting,
const std::string &value)
{
auto id = setting.GetID();
OBSDataAutoRelease data = obs_source_get_settings(source);
OBSDataAutoRelease dataWithDefaults = obs_data_get_defaults(data);
obs_data_apply(dataWithDefaults, data);
auto item = obs_data_item_byname(dataWithDefaults, id.c_str());
auto type = obs_data_item_gettype(item);
auto numType = obs_data_item_numtype(item);
if (type == OBS_DATA_NULL) {
std::tie(type, numType) =
getDataTypeFromProperty(source, setting);
}
switch (type) {
case OBS_DATA_NULL:
vblog(LOG_WARNING,
"could not set settings value, as the value does not (yet) exist!");
break;
case OBS_DATA_STRING:
obs_data_set_string(data, id.c_str(), value.c_str());
break;
case OBS_DATA_NUMBER: {
switch (numType) {
case OBS_DATA_NUM_INVALID:
break;
case OBS_DATA_NUM_INT: {
auto intValue = GetInt(value);
if (intValue.has_value()) {
obs_data_set_int(data, id.c_str(), *intValue);
break;
}
auto doubleValue = GetDouble(value);
if (doubleValue.has_value()) {
obs_data_set_int(data, id.c_str(),
*doubleValue);
}
break;
}
case OBS_DATA_NUM_DOUBLE: {
auto numValue = GetDouble(value);
if (!numValue.has_value()) {
break;
}
obs_data_set_double(data, id.c_str(), *numValue);
break;
}
}
break;
}
case OBS_DATA_BOOLEAN:
obs_data_set_bool(data, id.c_str(), value == "true");
break;
case OBS_DATA_OBJECT: {
OBSDataAutoRelease json =
obs_data_create_from_json(value.c_str());
if (!json) {
blog(LOG_WARNING,
"not setting invalid data object settings value! (%s)",
value.c_str());
break;
}
obs_data_set_obj(data, id.c_str(), json);
break;
}
case OBS_DATA_ARRAY: {
auto jsonStr = obs_data_get_json(data);
if (!jsonStr) {
break;
}
std::string resultJsonStr;
try {
auto json = nlohmann::json::parse(jsonStr);
auto jsonArray = nlohmann::json::parse(value);
json[id] = jsonArray;
resultJsonStr = json.dump();
} catch (const nlohmann::json::exception &) {
break;
}
OBSDataAutoRelease newData =
obs_data_create_from_json(resultJsonStr.c_str());
if (!newData) {
break;
}
obs_data_clear(data);
obs_data_apply(data, newData);
}
}
obs_data_item_release(&item);
obs_source_update(source, data);
}
void SetSourceSettingListEntryValueByName(obs_source_t *source,
const SourceSetting &setting,
const std::string &name)
{
if (!setting.IsList()) {
return;
}
const auto &[property, properties] =
findListPropertyById(source, setting.GetID());
if (!property) {
return;
}
const auto type = obs_property_list_format(property);
if (type == OBS_COMBO_FORMAT_INVALID) {
return;
}
const auto count = obs_property_list_item_count(property);
bool found = false;
size_t idx = 0;
for (; idx < count; idx++) {
if (obs_property_list_item_name(property, idx) == name) {
found = true;
break;
}
}
if (!found) {
return;
}
std::string value;
switch (type) {
case OBS_COMBO_FORMAT_INT:
value = std::to_string(
obs_property_list_item_int(property, idx));
break;
case OBS_COMBO_FORMAT_FLOAT:
value = std::to_string(
obs_property_list_item_float(property, idx));
break;
case OBS_COMBO_FORMAT_STRING:
value = obs_property_list_item_string(property, idx);
break;
case OBS_COMBO_FORMAT_BOOL:
value = std::to_string(
obs_property_list_item_bool(property, idx));
break;
default:
break;
}
SetSourceSetting(source, setting, value);
}
SourceSettingSelection::SourceSettingSelection(QWidget *parent)
: QWidget(parent),
_settings(new FilterComboBox(
this, obs_module_text("AdvSceneSwitcher.setting.select"))),
_tooltip(new HelpIcon())
{
_tooltip->hide();
_settings->setSizeAdjustPolicy(QComboBox::AdjustToContents);
_settings->setMaximumWidth(350);
QWidget::connect(_settings, SIGNAL(currentIndexChanged(int)), this,
SLOT(SelectionIdxChanged(int)));
auto layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(_settings);
layout->addWidget(_tooltip);
setLayout(layout);
}
void SourceSettingSelection::SetSource(const OBSWeakSource &source,
bool restorePreviousSelection)
{
const auto previousSelection = _settings->currentData();
Populate(source);
if (restorePreviousSelection) {
_settings->setCurrentIndex(
_settings->findData(previousSelection));
}
}
void SourceSettingSelection::SetSelection(const OBSWeakSource &source,
const SourceSetting &setting)
{
SetSource(source, false);
QVariant variant;
variant.setValue(setting);
_settings->setCurrentIndex(_settings->findData(variant));
}
void SourceSettingSelection::SelectionIdxChanged(int idx)
{
if (idx == -1) {
return;
}
auto setting = _settings->itemData(idx).value<SourceSetting>();
if (setting._longDescription.empty()) {
_tooltip->setToolTip("");
_tooltip->hide();
} else {
_tooltip->setToolTip(
QString::fromStdString(setting._longDescription));
_tooltip->show();
}
emit SelectionChanged(setting);
}
static std::vector<SourceSetting> getSourceSettings(obs_source_t *source)
{
properties_t properties(obs_source_properties(source),
obs_properties_destroy);
if (!properties) {
return {};
}
std::vector<SourceSetting> settings;
auto it = obs_properties_first(properties.get());
addSettingsHelper(it, settings);
return settings;
}
void SourceSettingSelection::Populate(const OBSWeakSource &source)
{
_settings->clear();
OBSSourceAutoRelease s = obs_weak_source_get_source(source);
const auto settings = getSourceSettings(s);
for (const auto &setting : settings) {
QVariant variant;
variant.setValue(setting);
_settings->addItem(QString::fromStdString(setting._description),
variant);
}
adjustSize();
updateGeometry();
}
} // namespace advss