mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 20:55:28 -05:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df358de5fb | ||
|
|
6d7e23c88f | ||
|
|
9fff05c57c | ||
|
|
6da151f7d7 | ||
|
|
61ee58ba70 | ||
|
|
312fe1d648 | ||
|
|
5d3c83c292 | ||
|
|
964b6c6c71 | ||
|
|
27116c0cd3 | ||
|
|
06c29bced5 | ||
|
|
4f3d9e3a00 | ||
|
|
c02896ea17 | ||
|
|
4bfd40219b | ||
|
|
6a3de18069 | ||
|
|
8c51b44fbb | ||
|
|
10daa38921 | ||
|
|
6e0f7ab38f | ||
|
|
86061b3cf1 | ||
|
|
c3669cae3e | ||
|
|
a116360f87 |
@@ -99,7 +99,6 @@ AdvSceneSwitcher.macroList.deleted="gelöscht"
|
||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" ist bereits ausgewählt!"
|
||||
|
||||
; Macro Logic
|
||||
AdvSceneSwitcher.logic.none="Eintrag ignorieren"
|
||||
AdvSceneSwitcher.logic.and="Und"
|
||||
AdvSceneSwitcher.logic.or="Oder"
|
||||
AdvSceneSwitcher.logic.andNot="Und nicht"
|
||||
|
||||
@@ -306,7 +306,6 @@ AdvSceneSwitcher.macroList.deleted="deleted"
|
||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" is alreay selected!"
|
||||
|
||||
# Macro Logic
|
||||
AdvSceneSwitcher.logic.none="Ignore entry"
|
||||
AdvSceneSwitcher.logic.and="And"
|
||||
AdvSceneSwitcher.logic.or="Or"
|
||||
AdvSceneSwitcher.logic.andNot="And not"
|
||||
@@ -1076,8 +1075,8 @@ AdvSceneSwitcher.action.macro.type.runActions="Run macro actions"
|
||||
AdvSceneSwitcher.action.macro.type.run.conditions.ignore="Do not consider condition state"
|
||||
AdvSceneSwitcher.action.macro.type.run.conditions.true="Only if conditions evaluate to true"
|
||||
AdvSceneSwitcher.action.macro.type.run.conditions.false="Only if conditions evaluate to false"
|
||||
AdvSceneSwitcher.action.macro.type.run.updateConditionMatchState="Reevaluate the condition state before executing this action"
|
||||
AdvSceneSwitcher.action.macro.type.run.updateConditionMatchState.help="The plugin operates in phases:\n * The phase evaluating the macro conditions\n * The phase running macro actions\nMacros executed before this particular action might have side effects on the condition state of macros.\nCheck this option if you want those side effects to be taken into account when evaluating the condition state."
|
||||
AdvSceneSwitcher.action.macro.type.run.updateConditionMatchState="Check conditions now, even if macro is paused"
|
||||
AdvSceneSwitcher.action.macro.type.run.updateConditionMatchState.help="The plugin operates in phases:\n * The phase evaluating the macro conditions\n * The phase running macro actions\nMacros executed before this particular action might have side effects on the condition state of macros.\nAdditionally, paused macros are assumed to not match their conditions.\nCheck this option to force a fresh condition evaluation before running this action, regardless of whether the macro is paused."
|
||||
AdvSceneSwitcher.action.macro.type.run.actionType.regular="actions"
|
||||
AdvSceneSwitcher.action.macro.type.run.actionType.else="else-actions"
|
||||
AdvSceneSwitcher.action.macro.type.run.skipWhenPaused="Skip execution when macro is paused"
|
||||
|
||||
@@ -83,7 +83,6 @@ AdvSceneSwitcher.macroTab.highlightTrueConditions="Resaltar condiciones de la ma
|
||||
AdvSceneSwitcher.macroTab.highlightPerformedActions="Resaltar acciones realizadas recientemente de la macro seleccionada actualmente"
|
||||
|
||||
; Lógica de macros
|
||||
AdvSceneSwitcher.logic.none="Omitir entrada"
|
||||
AdvSceneSwitcher.logic.and="Y"
|
||||
AdvSceneSwitcher.logic.or="O"
|
||||
AdvSceneSwitcher.logic.andNot="Y no"
|
||||
|
||||
@@ -131,7 +131,6 @@ AdvSceneSwitcher.macroList.deleted="supprimé"
|
||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" est déjà sélectionné !"
|
||||
|
||||
; Macro Logic
|
||||
AdvSceneSwitcher.logic.none="Ignorer l'entrée"
|
||||
AdvSceneSwitcher.logic.and="Et"
|
||||
AdvSceneSwitcher.logic.or="Ou"
|
||||
AdvSceneSwitcher.logic.andNot="Et pas"
|
||||
|
||||
@@ -275,7 +275,6 @@ AdvSceneSwitcher.macroList.deleted="削除"
|
||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" はすでに選択されています!"
|
||||
|
||||
# Macro Logic
|
||||
AdvSceneSwitcher.logic.none="入力無視"
|
||||
; AdvSceneSwitcher.logic.and="And"
|
||||
; AdvSceneSwitcher.logic.or="Or"
|
||||
; AdvSceneSwitcher.logic.andNot="And not"
|
||||
|
||||
@@ -239,7 +239,6 @@ AdvSceneSwitcher.macroList.deleted="excluída"
|
||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" já está selecionada!"
|
||||
|
||||
; Macro Logic
|
||||
AdvSceneSwitcher.logic.none="Ignorar entrada"
|
||||
AdvSceneSwitcher.logic.and="E"
|
||||
AdvSceneSwitcher.logic.or="Ou"
|
||||
AdvSceneSwitcher.logic.andNot="E não"
|
||||
|
||||
@@ -70,7 +70,6 @@ AdvSceneSwitcher.macroTab.name="Имя:"
|
||||
AdvSceneSwitcher.macroTab.defaultname="Макрос %1"
|
||||
AdvSceneSwitcher.macroTab.copy="Создать копию"
|
||||
; Macro Logic
|
||||
AdvSceneSwitcher.logic.none="Игнорировать вход"
|
||||
AdvSceneSwitcher.logic.and="И"
|
||||
AdvSceneSwitcher.logic.or="Или"
|
||||
AdvSceneSwitcher.logic.andNot="И не"
|
||||
|
||||
@@ -78,7 +78,6 @@ AdvSceneSwitcher.macroTab.expandAll="Hepsini Genişlet"
|
||||
AdvSceneSwitcher.macroTab.collapseAll="Hepsini Küçült"
|
||||
|
||||
; Macro Logic
|
||||
AdvSceneSwitcher.logic.none="Girişi yoksay"
|
||||
AdvSceneSwitcher.logic.and="Ve"
|
||||
AdvSceneSwitcher.logic.or="Ya da"
|
||||
AdvSceneSwitcher.logic.andNot="ve değil"
|
||||
|
||||
@@ -259,7 +259,6 @@ AdvSceneSwitcher.macroList.deleted="删除"
|
||||
AdvSceneSwitcher.macroList.duplicate="\"%1\" 已选择!"
|
||||
|
||||
; Macro Logic
|
||||
AdvSceneSwitcher.logic.none="忽略条件"
|
||||
AdvSceneSwitcher.logic.and="且"
|
||||
AdvSceneSwitcher.logic.or="或"
|
||||
AdvSceneSwitcher.logic.andNot="且不"
|
||||
|
||||
@@ -506,6 +506,8 @@ bool SwitcherData::AnySceneTransitionStarted()
|
||||
******************************************************************************/
|
||||
extern "C" EXPORT void FreeSceneSwitcher()
|
||||
{
|
||||
switcher->Stop();
|
||||
|
||||
PlatformCleanup();
|
||||
RunPluginCleanupSteps();
|
||||
|
||||
@@ -627,13 +629,20 @@ static void handleSceneCollectionCleanup()
|
||||
return;
|
||||
}
|
||||
|
||||
// OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP is also called on
|
||||
// shutdown.
|
||||
// Here we also don't want to clear the settings.
|
||||
if (switcher->obsIsShuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
SaveSceneSwitcher(nullptr, false, nullptr);
|
||||
}
|
||||
|
||||
// Note to future self:
|
||||
// be careful using switcher->m here as there is potential for deadlocks when using
|
||||
// frontend functions such as obs_frontend_set_current_scene()
|
||||
static void OBSEvent(enum obs_frontend_event event, void *switcher)
|
||||
static void OBSEvent(enum obs_frontend_event event, void *)
|
||||
{
|
||||
if (!switcher) {
|
||||
return;
|
||||
@@ -767,8 +776,6 @@ void HighlightMacroSettingsButton(bool enable)
|
||||
enable);
|
||||
}
|
||||
|
||||
void SetupActionQueues();
|
||||
|
||||
extern "C" EXPORT void InitSceneSwitcher(obs_module_t *module,
|
||||
translateFunc translate)
|
||||
{
|
||||
@@ -780,12 +787,11 @@ extern "C" EXPORT void InitSceneSwitcher(obs_module_t *module,
|
||||
PlatformInit();
|
||||
LoadPlugins();
|
||||
SetupDock();
|
||||
SetupActionQueues();
|
||||
|
||||
RunPluginInitSteps();
|
||||
|
||||
obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr);
|
||||
obs_frontend_add_event_callback(OBSEvent, switcher);
|
||||
obs_frontend_add_event_callback(OBSEvent, nullptr);
|
||||
|
||||
QAction *action = (QAction *)obs_frontend_add_tools_menu_qaction(
|
||||
obs_module_text("AdvSceneSwitcher.pluginName"));
|
||||
|
||||
@@ -468,7 +468,6 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
|
||||
// Needs to be loaded before any entries which might rely on scene group
|
||||
// selections to be available.
|
||||
loadSceneGroups(obj);
|
||||
LoadVariables(obj);
|
||||
|
||||
RunLoadSteps(obj);
|
||||
|
||||
@@ -507,7 +506,6 @@ void SwitcherData::SaveSettings(obs_data_t *obj)
|
||||
saveSceneGroups(obj);
|
||||
SaveMacros(obj);
|
||||
SaveGlobalMacroSettings(obj);
|
||||
SaveVariables(obj);
|
||||
saveWindowTitleSwitches(obj);
|
||||
saveScreenRegionSwitches(obj);
|
||||
savePauseSwitches(obj);
|
||||
|
||||
@@ -871,6 +871,8 @@ void MacroActionMacroEdit::SetWidgetVisibility()
|
||||
} else {
|
||||
_noConditionsWarning->setVisible(false);
|
||||
}
|
||||
_runMacroHelp->setVisible(action ==
|
||||
MacroActionMacro::Action::RUN_MACRO);
|
||||
|
||||
_nestedMacro->setVisible(action ==
|
||||
MacroActionMacro::Action::NESTED_MACRO);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "path-helpers.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "section.hpp"
|
||||
#include "switch-button.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
@@ -102,12 +103,15 @@ MacroConditionEdit::MacroConditionEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroCondition> *entryData,
|
||||
bool isRootCondition)
|
||||
: MacroSegmentEdit(parent),
|
||||
_enable(new SwitchButton()),
|
||||
_logicSelection(new QComboBox()),
|
||||
_conditionSelection(new FilterComboBox()),
|
||||
_dur(new DurationModifierEdit()),
|
||||
_entryData(entryData),
|
||||
_isRoot(isRootCondition)
|
||||
{
|
||||
QWidget::connect(_enable, SIGNAL(checked(bool)), this,
|
||||
SLOT(ConditionEnableChanged(bool)));
|
||||
QWidget::connect(_logicSelection, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(LogicSelectionChanged(int)));
|
||||
QWidget::connect(_conditionSelection,
|
||||
@@ -122,6 +126,7 @@ MacroConditionEdit::MacroConditionEdit(
|
||||
Logic::PopulateLogicTypeSelection(_logicSelection, isRootCondition);
|
||||
populateConditionSelection(_conditionSelection);
|
||||
|
||||
_section->AddHeaderWidget(_enable);
|
||||
_section->AddHeaderWidget(_logicSelection);
|
||||
_section->AddHeaderWidget(_conditionSelection);
|
||||
_section->AddHeaderWidget(_headerInfo);
|
||||
@@ -154,8 +159,17 @@ void MacroConditionEdit::LogicSelectionChanged(int idx)
|
||||
const auto logic = static_cast<Logic::Type>(
|
||||
_logicSelection->itemData(idx).toInt());
|
||||
(*_entryData)->SetLogicType(logic);
|
||||
}
|
||||
|
||||
SetEnableAppearance(logic != Logic::Type::NONE);
|
||||
void MacroConditionEdit::ConditionEnableChanged(bool value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
(*_entryData)->SetEnabled(value);
|
||||
SetDisableEffect(!value);
|
||||
}
|
||||
|
||||
bool MacroConditionEdit::IsRootNode() const
|
||||
@@ -168,7 +182,9 @@ void MacroConditionEdit::SetLogicSelection()
|
||||
const auto logic = (*_entryData)->GetLogicType();
|
||||
_logicSelection->setCurrentIndex(
|
||||
_logicSelection->findData(static_cast<int>(logic)));
|
||||
SetEnableAppearance(logic != Logic::Type::NONE);
|
||||
const bool enabled = (*_entryData)->Enabled();
|
||||
_enable->setChecked(enabled);
|
||||
SetEnableAppearance(enabled);
|
||||
}
|
||||
|
||||
void MacroConditionEdit::SetRootNode(bool root)
|
||||
@@ -239,10 +255,12 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
|
||||
{
|
||||
auto lock = LockContext();
|
||||
auto logic = (*_entryData)->GetLogicType();
|
||||
const bool enabled = (*_entryData)->Enabled();
|
||||
_entryData->reset();
|
||||
*_entryData = MacroConditionFactory::Create(id, macro);
|
||||
(*_entryData)->SetIndex(idx);
|
||||
(*_entryData)->SetLogicType(logic);
|
||||
(*_entryData)->SetEnabled(enabled);
|
||||
(*_entryData)->PostLoad();
|
||||
RunAndClearPostLoadSteps();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
class SwitchButton;
|
||||
|
||||
class DurationModifierEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -47,11 +49,13 @@ private slots:
|
||||
void ConditionSelectionChanged(const QString &text);
|
||||
void DurationChanged(const Duration &value);
|
||||
void DurationModifierChanged(DurationModifier::Type m);
|
||||
void ConditionEnableChanged(bool);
|
||||
|
||||
private:
|
||||
void SetLogicSelection();
|
||||
std::shared_ptr<MacroSegment> Data() const;
|
||||
|
||||
SwitchButton *_enable;
|
||||
QComboBox *_logicSelection;
|
||||
FilterComboBox *_conditionSelection;
|
||||
DurationModifierEdit *_dur;
|
||||
|
||||
@@ -31,6 +31,10 @@ bool MacroCondition::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroSegment::Load(obj);
|
||||
_logic.Load(obj, "logic");
|
||||
if (_logic.GetType() == Logic::Type::NONE) {
|
||||
SetEnabled(false);
|
||||
_logic.SetType(Logic::Type::AND);
|
||||
}
|
||||
_durationModifier.Load(obj);
|
||||
return true;
|
||||
}
|
||||
@@ -50,7 +54,8 @@ void MacroCondition::ValidateLogicSelection(bool isRootCondition,
|
||||
return;
|
||||
}
|
||||
|
||||
_logic.SetType(Logic::Type::NONE);
|
||||
_logic.SetType(Logic::Type::AND);
|
||||
SetEnabled(false);
|
||||
blog(LOG_WARNING,
|
||||
"setting invalid logic selection to 'ignore' for macro %s",
|
||||
context);
|
||||
|
||||
@@ -80,7 +80,7 @@ bool MacroSegment::Save(obs_data_t *obj) const
|
||||
obs_data_set_bool(data, "useCustomLabel", _useCustomLabel);
|
||||
obs_data_set_string(data, "customLabel", _customLabel.c_str());
|
||||
obs_data_set_bool(data, "enabled", _enabled);
|
||||
obs_data_set_int(data, "version", 1);
|
||||
obs_data_set_int(data, "version", 2);
|
||||
|
||||
OBSDataArrayAutoRelease mappingsArray = obs_data_array_create();
|
||||
for (const auto &mapping : _varMappings) {
|
||||
@@ -112,6 +112,12 @@ bool MacroSegment::Load(obs_data_t *obj)
|
||||
_enabled = obs_data_get_bool(obj, "enabled");
|
||||
}
|
||||
|
||||
// Reset the previously unused "enabled" value for conditions to "true"
|
||||
if (obs_data_get_int(data, "version") < 2 &&
|
||||
obs_data_has_user_value(obj, "logic")) {
|
||||
_enabled = true;
|
||||
}
|
||||
|
||||
_varMappings.clear();
|
||||
OBSDataArrayAutoRelease mappingsArray =
|
||||
obs_data_get_array(data, "varMappings");
|
||||
|
||||
@@ -557,6 +557,10 @@ void AdvSceneSwitcher::HighlightOnChange() const
|
||||
return;
|
||||
}
|
||||
|
||||
if (macro->Paused()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (macro->ActionTriggerModePreventedActionsSince(
|
||||
lastOnChangeHighlightCheckTime)) {
|
||||
HighlightWidget(ui->actionTriggerMode, Qt::yellow,
|
||||
|
||||
@@ -17,6 +17,17 @@
|
||||
|
||||
namespace advss {
|
||||
|
||||
static bool setup()
|
||||
{
|
||||
AddPluginCleanupStep([]() {
|
||||
GetTopLevelMacros().clear();
|
||||
GetTemporaryMacros().clear();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setupDone = setup();
|
||||
|
||||
Macro::Macro(const std::string &name) : _dockSettings(this)
|
||||
{
|
||||
SetName(name);
|
||||
@@ -146,6 +157,15 @@ bool Macro::CheckConditionHelper(
|
||||
return conditionMatched;
|
||||
};
|
||||
|
||||
if (!condition->Enabled()) {
|
||||
vblog(LOG_INFO, "ignoring condition '%s' for '%s'",
|
||||
condition->GetId().c_str(), _name.c_str());
|
||||
if (!_useShortCircuitEvaluation) {
|
||||
(void)evaluateCondition();
|
||||
}
|
||||
return _matched;
|
||||
}
|
||||
|
||||
const auto logicType = condition->GetLogicType();
|
||||
if (logicType == Logic::Type::NONE) {
|
||||
vblog(LOG_INFO, "ignoring condition '%s' for '%s'",
|
||||
@@ -270,7 +290,7 @@ bool Macro::CheckConditions(bool ignorePause)
|
||||
|
||||
const bool hasActionsToExecute = _matched ? (_actions.size() > 0)
|
||||
: (_elseActions.size() > 0);
|
||||
if (!_actionModeMatch && hasActionsToExecute) {
|
||||
if (!_actionModeMatch && hasActionsToExecute && !_paused) {
|
||||
_lastActionRunModePreventTime =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
@@ -369,7 +389,7 @@ bool Macro::ShouldRunActions() const
|
||||
!_paused && (_matched || _elseActions.size() > 0) &&
|
||||
_actionModeMatch;
|
||||
|
||||
if (VerboseLoggingEnabled() && !_actionModeMatch) {
|
||||
if (VerboseLoggingEnabled() && !_actionModeMatch && !_paused) {
|
||||
if (_matched && _actions.size() > 0) {
|
||||
blog(LOG_INFO, "skip actions for Macro %s (on change)",
|
||||
_name.c_str());
|
||||
|
||||
@@ -16,14 +16,17 @@ static void setupTab(QTabWidget *);
|
||||
|
||||
static ActionQueueTable *tabWidget = nullptr;
|
||||
|
||||
void RegisterActionQueueTab()
|
||||
static bool setup()
|
||||
{
|
||||
AddPluginInitStep([]() {
|
||||
AddPluginInitStep([] {
|
||||
AddSetupTabCallback("actionQueueTab", ActionQueueTable::Create,
|
||||
setupTab);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setupDone = setup();
|
||||
|
||||
static void setTabVisible(QTabWidget *tabWidget, bool visible)
|
||||
{
|
||||
SetTabVisibleByName(
|
||||
|
||||
@@ -12,20 +12,16 @@ std::deque<std::shared_ptr<Item>> &GetActionQueues()
|
||||
return queues;
|
||||
}
|
||||
|
||||
void RegisterActionQueueTab();
|
||||
|
||||
void SetupActionQueues()
|
||||
static bool setup()
|
||||
{
|
||||
static bool done = false;
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
AddSaveStep(SaveActionQueues);
|
||||
AddLoadStep(LoadActionQueues);
|
||||
RegisterActionQueueTab();
|
||||
done = true;
|
||||
AddPluginCleanupStep([]() { queues.clear(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setupDone = setup();
|
||||
|
||||
ActionQueue::ActionQueue() : Item()
|
||||
{
|
||||
_lastEmpty = std::chrono::high_resolution_clock::now();
|
||||
|
||||
@@ -101,7 +101,6 @@ signals:
|
||||
};
|
||||
|
||||
std::deque<std::shared_ptr<Item>> &GetActionQueues();
|
||||
void SetupActionQueues();
|
||||
void SaveActionQueues(obs_data_t *);
|
||||
void LoadActionQueues(obs_data_t *);
|
||||
void ImportQueues(obs_data_t *);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
namespace advss {
|
||||
|
||||
const std::map<Logic::Type, const char *> Logic::localeMap = {
|
||||
{Logic::Type::NONE, {"AdvSceneSwitcher.logic.none"}},
|
||||
{Logic::Type::AND, {"AdvSceneSwitcher.logic.and"}},
|
||||
{Logic::Type::OR, {"AdvSceneSwitcher.logic.or"}},
|
||||
{Logic::Type::AND_NOT, {"AdvSceneSwitcher.logic.andNot"}},
|
||||
@@ -67,7 +66,7 @@ void Logic::PopulateLogicTypeSelection(QComboBox *list, bool isRootCondition)
|
||||
return typeValue < rootOffset;
|
||||
}}
|
||||
: std::function<bool(int)>{[](int typeValue) {
|
||||
return typeValue >= rootOffset;
|
||||
return typeValue > rootOffset;
|
||||
}};
|
||||
for (const auto &[type, name] : localeMap) {
|
||||
const int typeValue = static_cast<int>(type);
|
||||
|
||||
@@ -28,6 +28,8 @@ static constexpr bool handleUncleanShutdown = true;
|
||||
static bool wasCleanShutdown = false;
|
||||
static bool suppressCrashDialog = false;
|
||||
|
||||
static char *sentinelFile = nullptr;
|
||||
|
||||
bool GetSuppressCrashDialog()
|
||||
{
|
||||
return suppressCrashDialog;
|
||||
@@ -58,7 +60,6 @@ static void handleShutdown(enum obs_frontend_event event, void *)
|
||||
return;
|
||||
}
|
||||
|
||||
char *sentinelFile = obs_module_config_path(sentinel.data());
|
||||
if (!sentinelFile) {
|
||||
return;
|
||||
}
|
||||
@@ -78,7 +79,8 @@ static void handleShutdown(enum obs_frontend_event event, void *)
|
||||
|
||||
static void setup()
|
||||
{
|
||||
char *sentinelFile = obs_module_config_path(sentinel.data());
|
||||
// Freed in handleShutdown()
|
||||
sentinelFile = obs_module_config_path(sentinel.data());
|
||||
if (!sentinelFile) {
|
||||
return;
|
||||
}
|
||||
@@ -106,7 +108,6 @@ static void setup()
|
||||
|
||||
file.write("running");
|
||||
file.close();
|
||||
bfree(sentinelFile);
|
||||
|
||||
obs_frontend_add_event_callback(handleShutdown, nullptr);
|
||||
return;
|
||||
|
||||
@@ -75,6 +75,18 @@ static std::vector<std::function<void()>> &getStopSteps()
|
||||
return steps;
|
||||
}
|
||||
|
||||
static std::vector<std::function<void(obs_data_t *)>> &getEarlySaveSteps()
|
||||
{
|
||||
static std::vector<std::function<void(obs_data_t *)>> steps;
|
||||
return steps;
|
||||
}
|
||||
|
||||
static std::vector<std::function<void(obs_data_t *)>> &getEarlyLoadSteps()
|
||||
{
|
||||
static std::vector<std::function<void(obs_data_t *)>> steps;
|
||||
return steps;
|
||||
}
|
||||
|
||||
static std::vector<std::function<void(obs_data_t *)>> &getSaveSteps()
|
||||
{
|
||||
static std::vector<std::function<void(obs_data_t *)>> steps;
|
||||
@@ -109,6 +121,18 @@ void LoadPluginSettings(obs_data_t *obj)
|
||||
GetSwitcher()->LoadSettings(obj);
|
||||
}
|
||||
|
||||
void AddEarlySaveStep(std::function<void(obs_data_t *)> step)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
getEarlySaveSteps().emplace_back(step);
|
||||
}
|
||||
|
||||
void AddEarlyLoadStep(std::function<void(obs_data_t *)> step)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
getEarlyLoadSteps().emplace_back(step);
|
||||
}
|
||||
|
||||
void AddSaveStep(std::function<void(obs_data_t *)> step)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
@@ -136,6 +160,9 @@ void AddIntervalResetStep(std::function<void()> step)
|
||||
void RunSaveSteps(obs_data_t *obj)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
for (const auto &func : getEarlySaveSteps()) {
|
||||
func(obj);
|
||||
}
|
||||
for (const auto &func : getSaveSteps()) {
|
||||
func(obj);
|
||||
}
|
||||
@@ -144,6 +171,9 @@ void RunSaveSteps(obs_data_t *obj)
|
||||
void RunLoadSteps(obs_data_t *obj)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
for (const auto &func : getEarlyLoadSteps()) {
|
||||
func(obj);
|
||||
}
|
||||
for (const auto &func : getLoadSteps()) {
|
||||
func(obj);
|
||||
}
|
||||
@@ -310,7 +340,7 @@ bool HighlightUIElementsEnabled()
|
||||
|
||||
bool OBSIsShuttingDown()
|
||||
{
|
||||
return GetSwitcher() && GetSwitcher()->obsIsShuttingDown;
|
||||
return !GetSwitcher() || GetSwitcher()->obsIsShuttingDown;
|
||||
}
|
||||
|
||||
bool InitialLoadIsComplete()
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace advss {
|
||||
void SavePluginSettings(obs_data_t *);
|
||||
EXPORT void LoadPluginSettings(obs_data_t *);
|
||||
|
||||
void AddEarlySaveStep(std::function<void(obs_data_t *)>);
|
||||
void AddEarlyLoadStep(std::function<void(obs_data_t *)>);
|
||||
EXPORT void AddSaveStep(std::function<void(obs_data_t *)>);
|
||||
EXPORT void AddLoadStep(std::function<void(obs_data_t *)>);
|
||||
EXPORT void AddPostLoadStep(std::function<void()>);
|
||||
|
||||
@@ -63,7 +63,6 @@ static void load(obs_data_t *data)
|
||||
{
|
||||
tabSettings.Load(data, "tabSettings");
|
||||
dockSettings.Load(data, "dockSettings");
|
||||
|
||||
enableDock(obs_data_get_bool(data, "addVariablesDock"));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#include "variable.hpp"
|
||||
#include "math-helpers.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <obs.hpp>
|
||||
|
||||
#include <QGridLayout>
|
||||
|
||||
namespace advss {
|
||||
@@ -15,6 +18,15 @@ static std::deque<std::shared_ptr<Item>> variables;
|
||||
static std::mutex lastVariableChangeMutex;
|
||||
static std::chrono::high_resolution_clock::time_point lastVariableChange{};
|
||||
|
||||
static bool setup()
|
||||
{
|
||||
AddEarlySaveStep(SaveVariables);
|
||||
AddEarlyLoadStep(LoadVariables);
|
||||
AddPluginCleanupStep([]() { variables.clear(); });
|
||||
return true;
|
||||
}
|
||||
static bool setupDone = setup();
|
||||
|
||||
static void setLastVariableChangeTime()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lastVariableChangeMutex);
|
||||
@@ -418,34 +430,31 @@ static bool variableWithNameExists(const std::string &name)
|
||||
|
||||
void SaveVariables(obs_data_t *obj)
|
||||
{
|
||||
obs_data_array_t *variablesArray = obs_data_array_create();
|
||||
OBSDataArrayAutoRelease variablesArray = obs_data_array_create();
|
||||
for (const auto &v : variables) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
OBSDataAutoRelease array_obj = obs_data_create();
|
||||
v->Save(array_obj);
|
||||
obs_data_array_push_back(variablesArray, array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
|
||||
obs_data_set_array(obj, "variables", variablesArray);
|
||||
obs_data_array_release(variablesArray);
|
||||
}
|
||||
|
||||
void LoadVariables(obs_data_t *obj)
|
||||
{
|
||||
variables.clear();
|
||||
|
||||
obs_data_array_t *variablesArray = obs_data_get_array(obj, "variables");
|
||||
OBSDataArrayAutoRelease variablesArray =
|
||||
obs_data_get_array(obj, "variables");
|
||||
size_t count = obs_data_array_count(variablesArray);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *array_obj = obs_data_array_item(variablesArray, i);
|
||||
OBSDataAutoRelease array_obj =
|
||||
obs_data_array_item(variablesArray, i);
|
||||
auto var = Variable::Create();
|
||||
variables.emplace_back(var);
|
||||
variables.back()->Load(array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
|
||||
obs_data_array_release(variablesArray);
|
||||
}
|
||||
|
||||
static void signalImportedVariables(void *varsPtr)
|
||||
@@ -460,16 +469,15 @@ static void signalImportedVariables(void *varsPtr)
|
||||
|
||||
void ImportVariables(obs_data_t *data)
|
||||
{
|
||||
obs_data_array_t *array = obs_data_get_array(data, "variables");
|
||||
OBSDataArrayAutoRelease array = obs_data_get_array(data, "variables");
|
||||
size_t count = obs_data_array_count(array);
|
||||
|
||||
auto importedVars = new std::vector<std::shared_ptr<Item>>;
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *arrayElement = obs_data_array_item(array, i);
|
||||
OBSDataAutoRelease arrayElement = obs_data_array_item(array, i);
|
||||
auto var = Variable::Create();
|
||||
var->Load(arrayElement);
|
||||
obs_data_release(arrayElement);
|
||||
|
||||
if (variableWithNameExists(var->Name())) {
|
||||
continue;
|
||||
@@ -479,8 +487,6 @@ void ImportVariables(obs_data_t *data)
|
||||
importedVars->emplace_back(var);
|
||||
}
|
||||
|
||||
obs_data_array_release(array);
|
||||
|
||||
QueueUITask(signalImportedVariables, importedVars);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "platform-funcs.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
|
||||
#include <windows.h>
|
||||
#include <UIAutomation.h>
|
||||
#include <util/platform.h>
|
||||
#include <TlHelp32.h>
|
||||
#include <Psapi.h>
|
||||
#include <memory>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#include <string>
|
||||
@@ -16,6 +18,9 @@
|
||||
#include <QWidget>
|
||||
#include <mutex>
|
||||
|
||||
#define ADVSS_WIDEN_(x) L##x
|
||||
#define ADVSS_WIDEN(x) ADVSS_WIDEN_(x)
|
||||
|
||||
namespace advss {
|
||||
|
||||
#define MAX_SEARCH 1000
|
||||
@@ -500,9 +505,86 @@ int SecondsSinceLastInput()
|
||||
return (getTime() - getLastInputTime()) / 1000;
|
||||
}
|
||||
|
||||
static void addPluginFolderToSymbolPath()
|
||||
{
|
||||
// This runs after OBS_FRONTEND_EVENT_FINISHED_LOADING, which fires after
|
||||
// obs_load_all_modules() completes. By that point OBS has already called
|
||||
// reset_win32_symbol_paths() -> SymInitializeW(), so DbgHelp is
|
||||
// initialized and we can append our plugins subfolder (where the PDB
|
||||
// files live) to the existing search path.
|
||||
HMODULE dbghelp = LoadLibraryW(L"DbgHelp");
|
||||
if (!dbghelp) {
|
||||
return;
|
||||
}
|
||||
|
||||
typedef BOOL(WINAPI * SymGetSearchPathW_t)(HANDLE, PWSTR, DWORD);
|
||||
typedef BOOL(WINAPI * SymSetSearchPathW_t)(HANDLE, PCWSTR);
|
||||
typedef BOOL(WINAPI * SymRefreshModuleList_t)(HANDLE);
|
||||
|
||||
auto symGetSearchPathW = reinterpret_cast<SymGetSearchPathW_t>(
|
||||
GetProcAddress(dbghelp, "SymGetSearchPathW"));
|
||||
auto symSetSearchPathW = reinterpret_cast<SymSetSearchPathW_t>(
|
||||
GetProcAddress(dbghelp, "SymSetSearchPathW"));
|
||||
auto symRefreshModuleList = reinterpret_cast<SymRefreshModuleList_t>(
|
||||
GetProcAddress(dbghelp, "SymRefreshModuleList"));
|
||||
|
||||
if (!symGetSearchPathW || !symSetSearchPathW || !symRefreshModuleList) {
|
||||
FreeLibrary(dbghelp);
|
||||
return;
|
||||
}
|
||||
|
||||
HMODULE hModule = NULL;
|
||||
if (!GetModuleHandleExW(
|
||||
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
|
||||
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||
reinterpret_cast<LPCWSTR>(addPluginFolderToSymbolPath),
|
||||
&hModule)) {
|
||||
FreeLibrary(dbghelp);
|
||||
return;
|
||||
}
|
||||
|
||||
wchar_t dllDir[MAX_PATH];
|
||||
if (!GetModuleFileNameW(hModule, dllDir, MAX_PATH)) {
|
||||
FreeLibrary(dbghelp);
|
||||
return;
|
||||
}
|
||||
|
||||
wchar_t *lastSep = wcsrchr(dllDir, L'\\');
|
||||
if (!lastSep) {
|
||||
FreeLibrary(dbghelp);
|
||||
return;
|
||||
}
|
||||
*lastSep = L'\0';
|
||||
|
||||
wchar_t pluginsPath[MAX_PATH];
|
||||
wcsncpy_s(pluginsPath, MAX_PATH, dllDir, _TRUNCATE);
|
||||
wcsncat_s(pluginsPath, MAX_PATH, L"\\" ADVSS_WIDEN(ADVSS_PLUGIN_FOLDER),
|
||||
_TRUNCATE);
|
||||
|
||||
constexpr DWORD currentPathLen = 4096;
|
||||
constexpr DWORD newPathLen = 8192;
|
||||
auto currentPath = std::make_unique<wchar_t[]>(currentPathLen);
|
||||
auto newPath = std::make_unique<wchar_t[]>(newPathLen);
|
||||
|
||||
symGetSearchPathW(GetCurrentProcess(), currentPath.get(),
|
||||
currentPathLen);
|
||||
|
||||
if (currentPath[0] != L'\0') {
|
||||
_snwprintf_s(newPath.get(), newPathLen, _TRUNCATE, L"%s;%s",
|
||||
currentPath.get(), pluginsPath);
|
||||
} else {
|
||||
wcsncpy_s(newPath.get(), newPathLen, pluginsPath, _TRUNCATE);
|
||||
}
|
||||
|
||||
symSetSearchPathW(GetCurrentProcess(), newPath.get());
|
||||
symRefreshModuleList(GetCurrentProcess());
|
||||
FreeLibrary(dbghelp);
|
||||
}
|
||||
|
||||
void PlatformInit()
|
||||
{
|
||||
CoInitialize(NULL);
|
||||
AddFinishedLoadingStep(addPluginFolderToSymbolPath);
|
||||
}
|
||||
|
||||
void PlatformCleanup()
|
||||
|
||||
@@ -23,6 +23,7 @@ bool setup()
|
||||
{
|
||||
AddSaveStep(saveConnections);
|
||||
AddLoadStep(loadConnections);
|
||||
AddPluginCleanupStep([]() { connections.clear(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -718,6 +718,7 @@ static bool setup()
|
||||
{
|
||||
AddSaveStep(SaveMqttConnections);
|
||||
AddLoadStep(LoadMqttConnections);
|
||||
AddPluginCleanupStep([]() { GetMqttConnections().clear(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
#include "log-helper.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <obs-module.h>
|
||||
#include <obs.hpp>
|
||||
|
||||
#include <mutex>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
@@ -28,6 +32,17 @@ const std::string_view InlineScript::_defaultLUAScript =
|
||||
"end";
|
||||
;
|
||||
|
||||
static std::mutex instancesMutex;
|
||||
static std::unordered_set<InlineScript *> instances;
|
||||
|
||||
static void handleScriptingShutdown(enum obs_frontend_event event, void *)
|
||||
{
|
||||
if (event != OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN) {
|
||||
return;
|
||||
}
|
||||
InlineScript::DeregisterAll();
|
||||
}
|
||||
|
||||
static bool setup()
|
||||
{
|
||||
auto sh = obs_get_signal_handler();
|
||||
@@ -35,6 +50,8 @@ static bool setup()
|
||||
std::string("void ") + signalName.data() + "(string id)";
|
||||
signal_handler_add(sh, signalDecl.c_str());
|
||||
|
||||
obs_frontend_add_event_callback(handleScriptingShutdown, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
static bool setupDone = setup();
|
||||
@@ -98,6 +115,10 @@ static bool createScriptFile(const char *settingsFile, const char *content)
|
||||
|
||||
InlineScript::InlineScript() : _instanceId(_instanceIdCounter++)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(instancesMutex);
|
||||
instances.insert(this);
|
||||
}
|
||||
Setup();
|
||||
}
|
||||
|
||||
@@ -107,9 +128,27 @@ InlineScript::InlineScript(const InlineScript &other)
|
||||
_textLUA(other._textLUA),
|
||||
_instanceId(_instanceIdCounter++)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(instancesMutex);
|
||||
instances.insert(this);
|
||||
}
|
||||
Setup();
|
||||
}
|
||||
|
||||
InlineScript::~InlineScript()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(instancesMutex);
|
||||
instances.erase(this);
|
||||
}
|
||||
|
||||
void InlineScript::DeregisterAll()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(instancesMutex);
|
||||
for (auto *instance : instances) {
|
||||
instance->_script.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void InlineScript::Save(obs_data_t *data) const
|
||||
{
|
||||
OBSDataAutoRelease obj = obs_data_create();
|
||||
|
||||
@@ -15,6 +15,7 @@ class InlineScript {
|
||||
public:
|
||||
InlineScript();
|
||||
InlineScript(const InlineScript &);
|
||||
~InlineScript();
|
||||
|
||||
enum Type { INLINE, FILE };
|
||||
|
||||
@@ -34,6 +35,8 @@ public:
|
||||
|
||||
void ResolveVariablesToFixedValues();
|
||||
|
||||
static void DeregisterAll();
|
||||
|
||||
private:
|
||||
void Setup();
|
||||
void SetupFile();
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "date/tz.h"
|
||||
#endif
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace advss {
|
||||
|
||||
using websocketpp::lib::placeholders::_1;
|
||||
@@ -28,7 +30,7 @@ static constexpr std::string_view registerSubscriptionURL =
|
||||
static constexpr std::string_view registerSubscriptionPath =
|
||||
"/helix/eventsub/subscriptions";
|
||||
#endif
|
||||
static const int reconnectDelay = 15;
|
||||
static const auto reconnectDelay = 15s;
|
||||
|
||||
#undef DispatchMessage
|
||||
|
||||
@@ -81,6 +83,10 @@ void EventSub::ConnectThread()
|
||||
} else {
|
||||
_client->connect(con);
|
||||
_connection = connection_hdl(con);
|
||||
if (_disconnect) {
|
||||
_client->close(con, websocketpp::close::status::normal,
|
||||
"Twitch EventSub stopping", ec);
|
||||
}
|
||||
_client->run();
|
||||
}
|
||||
|
||||
@@ -98,9 +104,9 @@ void EventSub::WaitAndReconnect()
|
||||
auto thread = std::thread([this]() {
|
||||
std::unique_lock<std::mutex> lock(_waitMtx);
|
||||
blog(LOG_INFO,
|
||||
"Twitch EventSub trying to reconnect to in %d seconds.",
|
||||
reconnectDelay);
|
||||
_cv.wait_for(lock, std::chrono::seconds(reconnectDelay));
|
||||
"Twitch EventSub trying to reconnect to in %lld seconds.",
|
||||
(long long)reconnectDelay.count());
|
||||
_cv.wait_for(lock, reconnectDelay);
|
||||
_reconnecting = false;
|
||||
|
||||
if (_disconnect) {
|
||||
@@ -155,12 +161,6 @@ void EventSub::Disconnect()
|
||||
_cv.notify_all();
|
||||
}
|
||||
|
||||
while (_connected) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
_client->close(_connection, websocketpp::close::status::normal,
|
||||
"Twitch EventSub stopping", ec);
|
||||
}
|
||||
|
||||
if (_thread.joinable()) {
|
||||
_thread.join();
|
||||
}
|
||||
@@ -342,8 +342,7 @@ static bool isValidTimestamp(const std::string ×tamp)
|
||||
|
||||
auto duration = now - parsedTime;
|
||||
// Clocks might be off by a bit, so allow negative values also
|
||||
return duration <= std::chrono::minutes(10) &&
|
||||
duration >= std::chrono::minutes(-1);
|
||||
return duration <= 10min && duration >= -1min;
|
||||
} catch (const std::exception &e) {
|
||||
blog(LOG_WARNING, "%s: %s", __func__, e.what());
|
||||
return false;
|
||||
|
||||
@@ -87,6 +87,7 @@ static bool setupTwitchTokenSupport()
|
||||
{
|
||||
AddSaveStep(saveConnections);
|
||||
AddLoadStep(loadConnections);
|
||||
AddPluginCleanupStep([]() { twitchTokens.clear(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace advss {
|
||||
|
||||
void SavePluginSettings(obs_data_t *) {}
|
||||
void LoadPluginSettings(obs_data_t *) {}
|
||||
void AddEarlySaveStep(std::function<void(obs_data_t *)>) {}
|
||||
void AddEarlyLoadStep(std::function<void(obs_data_t *)>) {}
|
||||
void AddSaveStep(std::function<void(obs_data_t *)>) {}
|
||||
void AddLoadStep(std::function<void(obs_data_t *)>) {}
|
||||
void AddPostLoadStep(std::function<void()>) {}
|
||||
|
||||
Reference in New Issue
Block a user