mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-22 18:04:59 -05:00
520 lines
13 KiB
C++
520 lines
13 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 {};
|
|
}
|
|
|
|
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);
|
|
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: {
|
|
auto type = obs_data_item_numtype(item);
|
|
switch (type) {
|
|
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
|