mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-23 02:14:55 -05:00
886 lines
20 KiB
C++
886 lines
20 KiB
C++
#include "macro.hpp"
|
|
#include "macro-action-edit.hpp"
|
|
#include "macro-condition-edit.hpp"
|
|
#include "macro-dock.hpp"
|
|
#include "macro-action-scene-switch.hpp"
|
|
#include "advanced-scene-switcher.hpp"
|
|
#include "hotkey.hpp"
|
|
|
|
#include <limits>
|
|
#undef max
|
|
#include <chrono>
|
|
#include <unordered_map>
|
|
#include <QMainWindow>
|
|
|
|
constexpr int perfLogThreshold = 300;
|
|
|
|
Macro::Macro(const std::string &name, const bool addHotkey)
|
|
{
|
|
SetName(name);
|
|
if (addHotkey) {
|
|
SetupHotkeys();
|
|
}
|
|
_registerHotkeys = addHotkey;
|
|
}
|
|
|
|
Macro::~Macro()
|
|
{
|
|
_die = true;
|
|
Stop();
|
|
ClearHotkeys();
|
|
RemoveDock();
|
|
}
|
|
|
|
std::shared_ptr<Macro>
|
|
Macro::CreateGroup(const std::string &name,
|
|
std::vector<std::shared_ptr<Macro>> &children)
|
|
{
|
|
auto group = std::make_shared<Macro>(name, false);
|
|
for (auto &c : children) {
|
|
c->SetParent(group);
|
|
}
|
|
group->_isGroup = true;
|
|
group->_groupSize = children.size();
|
|
return group;
|
|
}
|
|
|
|
void Macro::RemoveGroup(std::shared_ptr<Macro> group)
|
|
{
|
|
auto it = std::find(switcher->macros.begin(), switcher->macros.end(),
|
|
group);
|
|
if (it == switcher->macros.end()) {
|
|
return;
|
|
}
|
|
|
|
auto size = group->GroupSize();
|
|
for (uint32_t i = 1; i <= size; i++) {
|
|
auto m = std::next(it, i);
|
|
(*m)->SetParent(nullptr);
|
|
}
|
|
|
|
switcher->macros.erase(it);
|
|
}
|
|
|
|
void Macro::PrepareMoveToGroup(Macro *group, std::shared_ptr<Macro> item)
|
|
{
|
|
for (auto &m : switcher->macros) {
|
|
if (m.get() == group) {
|
|
PrepareMoveToGroup(m, item);
|
|
return;
|
|
}
|
|
}
|
|
PrepareMoveToGroup(std::shared_ptr<Macro>(), item);
|
|
}
|
|
|
|
void Macro::PrepareMoveToGroup(std::shared_ptr<Macro> group,
|
|
std::shared_ptr<Macro> item)
|
|
{
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
// Potentially remove from old group
|
|
auto oldGroup = item->Parent();
|
|
if (oldGroup) {
|
|
oldGroup->_groupSize--;
|
|
}
|
|
|
|
item->SetParent(group);
|
|
if (group) {
|
|
group->_groupSize++;
|
|
}
|
|
}
|
|
|
|
bool Macro::CeckMatch()
|
|
{
|
|
if (_isGroup) {
|
|
return false;
|
|
}
|
|
|
|
_matched = false;
|
|
for (auto &c : _conditions) {
|
|
if (_paused) {
|
|
vblog(LOG_INFO, "Macro %s is paused", _name.c_str());
|
|
return false;
|
|
}
|
|
|
|
auto startTime = std::chrono::high_resolution_clock::now();
|
|
bool cond = c->CheckCondition();
|
|
auto endTime = std::chrono::high_resolution_clock::now();
|
|
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
endTime - startTime);
|
|
if (ms.count() >= perfLogThreshold) {
|
|
blog(LOG_WARNING,
|
|
"spent %ld ms in %s condition check of macro '%s'!",
|
|
ms.count(), c->GetId().c_str(), Name().c_str());
|
|
}
|
|
|
|
c->CheckDurationModifier(cond);
|
|
|
|
switch (c->GetLogicType()) {
|
|
case LogicType::NONE:
|
|
vblog(LOG_INFO,
|
|
"ignoring condition check 'none' for '%s'",
|
|
_name.c_str());
|
|
continue;
|
|
case LogicType::AND:
|
|
_matched = _matched && cond;
|
|
if (cond) {
|
|
c->SetHighlight();
|
|
}
|
|
break;
|
|
case LogicType::OR:
|
|
_matched = _matched || cond;
|
|
if (cond) {
|
|
c->SetHighlight();
|
|
}
|
|
break;
|
|
case LogicType::AND_NOT:
|
|
_matched = _matched && !cond;
|
|
if (!cond) {
|
|
c->SetHighlight();
|
|
}
|
|
break;
|
|
case LogicType::OR_NOT:
|
|
if (!cond) {
|
|
c->SetHighlight();
|
|
}
|
|
break;
|
|
case LogicType::ROOT_NONE:
|
|
_matched = cond;
|
|
if (cond) {
|
|
c->SetHighlight();
|
|
}
|
|
break;
|
|
case LogicType::ROOT_NOT:
|
|
_matched = !cond;
|
|
if (!cond) {
|
|
c->SetHighlight();
|
|
}
|
|
break;
|
|
default:
|
|
blog(LOG_WARNING,
|
|
"ignoring unknown condition check for '%s'",
|
|
_name.c_str());
|
|
break;
|
|
}
|
|
vblog(LOG_INFO, "condition %s returned %d", c->GetId().c_str(),
|
|
cond);
|
|
}
|
|
vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched);
|
|
|
|
bool newLastMatched = _matched;
|
|
if (_matched && _matchOnChange && _lastMatched == _matched) {
|
|
vblog(LOG_INFO, "ignore match for Macro %s (on change)",
|
|
_name.c_str());
|
|
_matched = false;
|
|
SetOnChangeHighlight();
|
|
}
|
|
_lastMatched = newLastMatched;
|
|
|
|
// TODO: Move back to PerformAction() once new scene collection frontend
|
|
// events are available - see:
|
|
// https://github.com/obsproject/obs-studio/commit/feda1aaa283e8a99f6ba1159cfe6b9c1f2934a61
|
|
if (_matched && _runCount != std::numeric_limits<int>::max()) {
|
|
_runCount++;
|
|
}
|
|
_lastCheckTime = std::chrono::high_resolution_clock::now();
|
|
return _matched;
|
|
}
|
|
|
|
bool Macro::PerformActions(bool forceParallel, bool ignorePause)
|
|
{
|
|
if (!_done) {
|
|
vblog(LOG_INFO, "macro %s already running", _name.c_str());
|
|
return !forceParallel;
|
|
}
|
|
_stop = false;
|
|
_done = false;
|
|
bool ret = true;
|
|
if (_runInParallel || forceParallel) {
|
|
if (_backgroundThread.joinable()) {
|
|
_backgroundThread.join();
|
|
}
|
|
_backgroundThread = std::thread(
|
|
[this, ignorePause] { RunActions(ignorePause); });
|
|
} else {
|
|
RunActions(ret, ignorePause);
|
|
}
|
|
_wasExecutedRecently = true;
|
|
auto group = _parent.lock();
|
|
if (group) {
|
|
group->_wasExecutedRecently = true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int64_t Macro::MsSinceLastCheck()
|
|
{
|
|
if (_lastCheckTime.time_since_epoch().count() == 0) {
|
|
return 0;
|
|
}
|
|
const auto timePassed =
|
|
std::chrono::high_resolution_clock::now() - _lastCheckTime;
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(timePassed)
|
|
.count() +
|
|
1;
|
|
}
|
|
|
|
void Macro::SetName(const std::string &name)
|
|
{
|
|
_name = name;
|
|
SetHotkeysDesc();
|
|
SetDockWidgetName();
|
|
}
|
|
|
|
void Macro::ResetTimers()
|
|
{
|
|
for (auto &c : _conditions) {
|
|
c->ResetDuration();
|
|
}
|
|
_lastCheckTime = {};
|
|
}
|
|
|
|
void Macro::RunActions(bool &retVal, bool ignorePause)
|
|
{
|
|
bool ret = true;
|
|
for (auto &a : _actions) {
|
|
a->LogAction();
|
|
ret = ret && a->PerformAction();
|
|
if (!ret || (_paused && !ignorePause) || _stop || _die) {
|
|
retVal = ret;
|
|
break;
|
|
}
|
|
a->SetHighlight();
|
|
}
|
|
_done = true;
|
|
}
|
|
|
|
void Macro::RunActions(bool ignorePause)
|
|
{
|
|
bool unused;
|
|
RunActions(unused, ignorePause);
|
|
}
|
|
|
|
void Macro::SetOnChangeHighlight()
|
|
{
|
|
_onChangeTriggered = true;
|
|
}
|
|
|
|
void Macro::SetPaused(bool pause)
|
|
{
|
|
if (_paused && !pause) {
|
|
ResetTimers();
|
|
}
|
|
_paused = pause;
|
|
}
|
|
|
|
void Macro::AddHelperThread(std::thread &&newThread)
|
|
{
|
|
for (unsigned int i = 0; i < _helperThreads.size(); i++) {
|
|
if (!_helperThreads[i].joinable()) {
|
|
_helperThreads[i] = std::move(newThread);
|
|
return;
|
|
}
|
|
}
|
|
_helperThreads.push_back(std::move(newThread));
|
|
}
|
|
|
|
void Macro::Stop()
|
|
{
|
|
_stop = true;
|
|
switcher->macroWaitCv.notify_all();
|
|
for (auto &t : _helperThreads) {
|
|
if (t.joinable()) {
|
|
t.join();
|
|
}
|
|
}
|
|
if (_backgroundThread.joinable()) {
|
|
_backgroundThread.join();
|
|
}
|
|
}
|
|
|
|
void Macro::UpdateActionIndices()
|
|
{
|
|
int idx = 0;
|
|
for (auto a : _actions) {
|
|
a->SetIndex(idx);
|
|
idx++;
|
|
}
|
|
}
|
|
|
|
void Macro::UpdateConditionIndices()
|
|
{
|
|
int idx = 0;
|
|
for (auto c : _conditions) {
|
|
c->SetIndex(idx);
|
|
idx++;
|
|
}
|
|
}
|
|
|
|
Macro *Macro::Parent()
|
|
{
|
|
auto p = _parent.lock();
|
|
return p.get();
|
|
}
|
|
|
|
bool Macro::Save(obs_data_t *obj) const
|
|
{
|
|
obs_data_set_string(obj, "name", _name.c_str());
|
|
obs_data_set_bool(obj, "pause", _paused);
|
|
obs_data_set_bool(obj, "parallel", _runInParallel);
|
|
obs_data_set_bool(obj, "onChange", _matchOnChange);
|
|
|
|
obs_data_set_bool(obj, "group", _isGroup);
|
|
if (_isGroup) {
|
|
auto groupData = obs_data_create();
|
|
obs_data_set_bool(groupData, "collapsed", _isCollapsed);
|
|
obs_data_set_int(groupData, "size", _groupSize);
|
|
obs_data_set_obj(obj, "groupData", groupData);
|
|
obs_data_release(groupData);
|
|
return true;
|
|
}
|
|
|
|
obs_data_set_bool(obj, "registerDock", _registerDock);
|
|
// The object name is used to restore the position of the dock
|
|
if (_registerDock) {
|
|
SetDockWidgetName();
|
|
}
|
|
obs_data_set_bool(obj, "dockHasRunButton", _dockHasRunButton);
|
|
obs_data_set_bool(obj, "dockHasPauseButton", _dockHasPauseButton);
|
|
|
|
obs_data_set_bool(obj, "registerHotkeys", _registerHotkeys);
|
|
obs_data_array_t *pauseHotkey = obs_hotkey_save(_pauseHotkey);
|
|
obs_data_set_array(obj, "pauseHotkey", pauseHotkey);
|
|
obs_data_array_release(pauseHotkey);
|
|
obs_data_array_t *unpauseHotkey = obs_hotkey_save(_unpauseHotkey);
|
|
obs_data_set_array(obj, "unpauseHotkey", unpauseHotkey);
|
|
obs_data_array_release(unpauseHotkey);
|
|
obs_data_array_t *togglePauseHotkey =
|
|
obs_hotkey_save(_togglePauseHotkey);
|
|
obs_data_set_array(obj, "togglePauseHotkey", togglePauseHotkey);
|
|
obs_data_array_release(togglePauseHotkey);
|
|
|
|
obs_data_array_t *conditions = obs_data_array_create();
|
|
for (auto &c : _conditions) {
|
|
obs_data_t *array_obj = obs_data_create();
|
|
|
|
c->Save(array_obj);
|
|
obs_data_array_push_back(conditions, array_obj);
|
|
|
|
obs_data_release(array_obj);
|
|
}
|
|
obs_data_set_array(obj, "conditions", conditions);
|
|
obs_data_array_release(conditions);
|
|
|
|
obs_data_array_t *actions = obs_data_array_create();
|
|
for (auto &a : _actions) {
|
|
obs_data_t *array_obj = obs_data_create();
|
|
|
|
a->Save(array_obj);
|
|
obs_data_array_push_back(actions, array_obj);
|
|
|
|
obs_data_release(array_obj);
|
|
}
|
|
obs_data_set_array(obj, "actions", actions);
|
|
obs_data_array_release(actions);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool isValidLogic(LogicType t, bool root)
|
|
{
|
|
bool isRoot = isRootLogicType(t);
|
|
if (!isRoot == root) {
|
|
return false;
|
|
}
|
|
if (isRoot) {
|
|
if (t >= LogicType::ROOT_LAST) {
|
|
return false;
|
|
}
|
|
} else if (t >= LogicType::LAST) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void setValidLogic(MacroCondition *c, bool root, std::string name)
|
|
{
|
|
if (isValidLogic(c->GetLogicType(), root)) {
|
|
return;
|
|
}
|
|
if (root) {
|
|
c->SetLogicType(LogicType::ROOT_NONE);
|
|
blog(LOG_WARNING,
|
|
"setting invalid logic selection to 'if' for macro %s",
|
|
name.c_str());
|
|
} else {
|
|
c->SetLogicType(LogicType::NONE);
|
|
blog(LOG_WARNING,
|
|
"setting invalid logic selection to 'ignore' for macro %s",
|
|
name.c_str());
|
|
}
|
|
}
|
|
|
|
bool Macro::Load(obs_data_t *obj)
|
|
{
|
|
_name = obs_data_get_string(obj, "name");
|
|
_paused = obs_data_get_bool(obj, "pause");
|
|
_runInParallel = obs_data_get_bool(obj, "parallel");
|
|
_matchOnChange = obs_data_get_bool(obj, "onChange");
|
|
|
|
_isGroup = obs_data_get_bool(obj, "group");
|
|
if (_isGroup) {
|
|
auto groupData = obs_data_get_obj(obj, "groupData");
|
|
_isCollapsed = obs_data_get_bool(groupData, "collapsed");
|
|
_groupSize = obs_data_get_int(groupData, "size");
|
|
obs_data_release(groupData);
|
|
return true;
|
|
}
|
|
|
|
_dockHasRunButton = obs_data_get_bool(obj, "dockHasRunButton");
|
|
_dockHasPauseButton = obs_data_get_bool(obj, "dockHasPauseButton");
|
|
EnableDock(obs_data_get_bool(obj, "registerDock"));
|
|
|
|
obs_data_set_default_bool(obj, "registerHotkeys", true);
|
|
_registerHotkeys = obs_data_get_bool(obj, "registerHotkeys");
|
|
if (_registerHotkeys) {
|
|
SetupHotkeys();
|
|
}
|
|
obs_data_array_t *pauseHotkey = obs_data_get_array(obj, "pauseHotkey");
|
|
obs_hotkey_load(_pauseHotkey, pauseHotkey);
|
|
obs_data_array_release(pauseHotkey);
|
|
obs_data_array_t *unpauseHotkey =
|
|
obs_data_get_array(obj, "unpauseHotkey");
|
|
obs_hotkey_load(_unpauseHotkey, unpauseHotkey);
|
|
obs_data_array_release(unpauseHotkey);
|
|
obs_data_array_t *togglePauseHotkey =
|
|
obs_data_get_array(obj, "togglePauseHotkey");
|
|
obs_hotkey_load(_togglePauseHotkey, togglePauseHotkey);
|
|
obs_data_array_release(togglePauseHotkey);
|
|
SetHotkeysDesc();
|
|
|
|
bool root = true;
|
|
obs_data_array_t *conditions = obs_data_get_array(obj, "conditions");
|
|
size_t count = obs_data_array_count(conditions);
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
obs_data_t *array_obj = obs_data_array_item(conditions, i);
|
|
|
|
std::string id = obs_data_get_string(array_obj, "id");
|
|
|
|
auto newEntry = MacroConditionFactory::Create(id, this);
|
|
if (newEntry) {
|
|
_conditions.emplace_back(newEntry);
|
|
auto c = _conditions.back().get();
|
|
c->Load(array_obj);
|
|
setValidLogic(c, root, _name);
|
|
} else {
|
|
blog(LOG_WARNING,
|
|
"discarding condition entry with unknown id (%s) for macro %s",
|
|
id.c_str(), _name.c_str());
|
|
}
|
|
|
|
obs_data_release(array_obj);
|
|
root = false;
|
|
}
|
|
obs_data_array_release(conditions);
|
|
UpdateConditionIndices();
|
|
|
|
obs_data_array_t *actions = obs_data_get_array(obj, "actions");
|
|
count = obs_data_array_count(actions);
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
obs_data_t *array_obj = obs_data_array_item(actions, i);
|
|
|
|
std::string id = obs_data_get_string(array_obj, "id");
|
|
|
|
auto newEntry = MacroActionFactory::Create(id, this);
|
|
if (newEntry) {
|
|
_actions.emplace_back(newEntry);
|
|
_actions.back()->Load(array_obj);
|
|
} else {
|
|
blog(LOG_WARNING,
|
|
"discarding action entry with unknown id (%s) for macro %s",
|
|
id.c_str(), _name.c_str());
|
|
}
|
|
|
|
obs_data_release(array_obj);
|
|
}
|
|
obs_data_array_release(actions);
|
|
UpdateActionIndices();
|
|
return true;
|
|
}
|
|
|
|
bool Macro::PostLoad()
|
|
{
|
|
for (auto &c : _conditions) {
|
|
c->PostLoad();
|
|
}
|
|
for (auto &a : _actions) {
|
|
a->PostLoad();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Macro::SwitchesScene()
|
|
{
|
|
MacroActionSwitchScene temp(nullptr);
|
|
auto sceneSwitchId = temp.GetId();
|
|
for (auto &a : _actions) {
|
|
if (a->GetId() == sceneSwitchId) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Macro::WasExecutedRecently()
|
|
{
|
|
if (_wasExecutedRecently) {
|
|
_wasExecutedRecently = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Macro::OnChangePreventedActionsRecently()
|
|
{
|
|
if (_onChangeTriggered) {
|
|
_onChangeTriggered = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Macro::ResetUIHelpers()
|
|
{
|
|
_onChangeTriggered = false;
|
|
for (auto c : _conditions) {
|
|
c->Highlight();
|
|
}
|
|
for (auto a : _actions) {
|
|
a->Highlight();
|
|
}
|
|
}
|
|
|
|
void Macro::EnablePauseHotkeys(bool value)
|
|
{
|
|
if (_registerHotkeys == value) {
|
|
return;
|
|
}
|
|
|
|
if (_registerHotkeys) {
|
|
ClearHotkeys();
|
|
} else {
|
|
SetupHotkeys();
|
|
}
|
|
_registerHotkeys = value;
|
|
}
|
|
|
|
bool Macro::PauseHotkeysEnabled()
|
|
{
|
|
return _registerHotkeys;
|
|
}
|
|
|
|
void Macro::EnableDock(bool value)
|
|
{
|
|
if (_registerDock == value) {
|
|
return;
|
|
}
|
|
|
|
RemoveDock();
|
|
if (!_registerDock) {
|
|
_dock = new MacroDock(this,
|
|
static_cast<QMainWindow *>(
|
|
obs_frontend_get_main_window()));
|
|
_dockAction =
|
|
static_cast<QAction *>(obs_frontend_add_dock(_dock));
|
|
SetDockWidgetName();
|
|
}
|
|
|
|
_registerDock = value;
|
|
}
|
|
|
|
void Macro::SetDockHasRunButton(bool value)
|
|
{
|
|
_dockHasRunButton = value;
|
|
if (!_dock) {
|
|
return;
|
|
}
|
|
_dock->ShowRunButton(value);
|
|
}
|
|
|
|
void Macro::SetDockHasPauseButton(bool value)
|
|
{
|
|
_dockHasPauseButton = value;
|
|
if (!_dock) {
|
|
return;
|
|
}
|
|
_dock->ShowPauseButton(value);
|
|
}
|
|
|
|
void Macro::RemoveDock()
|
|
{
|
|
if (_dock) {
|
|
_dock->close();
|
|
_dock->deleteLater();
|
|
_dockAction->deleteLater();
|
|
_dock = nullptr;
|
|
_dockAction = nullptr;
|
|
}
|
|
}
|
|
|
|
void Macro::SetDockWidgetName() const
|
|
{
|
|
if (!_dock) {
|
|
return;
|
|
}
|
|
// Set prefix to avoid dock name conflict
|
|
_dock->setObjectName("ADVSS-" + QString::fromStdString(_name));
|
|
_dock->SetName(QString::fromStdString(_name));
|
|
|
|
if (!_dockAction) {
|
|
return;
|
|
}
|
|
_dockAction->setText(QString::fromStdString(_name));
|
|
}
|
|
|
|
static void pauseCB(void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed)
|
|
{
|
|
if (pressed) {
|
|
auto m = static_cast<Macro *>(data);
|
|
m->SetPaused(true);
|
|
}
|
|
}
|
|
|
|
static void unpauseCB(void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed)
|
|
{
|
|
if (pressed) {
|
|
auto m = static_cast<Macro *>(data);
|
|
m->SetPaused(false);
|
|
}
|
|
}
|
|
|
|
static void togglePauseCB(void *data, obs_hotkey_id, obs_hotkey_t *,
|
|
bool pressed)
|
|
{
|
|
if (pressed) {
|
|
auto m = static_cast<Macro *>(data);
|
|
m->SetPaused(!m->Paused());
|
|
}
|
|
}
|
|
|
|
static int macroHotkeyID = 0;
|
|
|
|
obs_hotkey_id registerHotkeyHelper(const std::string prefix,
|
|
const char *formatModuleText, Macro *macro,
|
|
obs_hotkey_func func)
|
|
{
|
|
macroHotkeyID++;
|
|
|
|
std::string hotkeyName = prefix + std::to_string(macroHotkeyID);
|
|
QString format{obs_module_text(formatModuleText)};
|
|
QString hotkeyDesc = format.arg(QString::fromStdString(macro->Name()));
|
|
return obs_hotkey_register_frontend(hotkeyName.c_str(),
|
|
hotkeyDesc.toStdString().c_str(),
|
|
func, macro);
|
|
}
|
|
|
|
void Macro::SetupHotkeys()
|
|
{
|
|
if (_pauseHotkey != OBS_INVALID_HOTKEY_ID ||
|
|
_unpauseHotkey != OBS_INVALID_HOTKEY_ID ||
|
|
_togglePauseHotkey != OBS_INVALID_HOTKEY_ID) {
|
|
ClearHotkeys();
|
|
}
|
|
|
|
_pauseHotkey = registerHotkeyHelper(
|
|
"macro_pause_hotkey_", "AdvSceneSwitcher.hotkey.macro.pause",
|
|
this, pauseCB);
|
|
_unpauseHotkey = registerHotkeyHelper(
|
|
"macro_unpause_hotkey_",
|
|
"AdvSceneSwitcher.hotkey.macro.unpause", this, unpauseCB);
|
|
_togglePauseHotkey = registerHotkeyHelper(
|
|
"macro_toggle_pause_hotkey_",
|
|
"AdvSceneSwitcher.hotkey.macro.togglePause", this,
|
|
togglePauseCB);
|
|
}
|
|
|
|
void Macro::ClearHotkeys()
|
|
{
|
|
obs_hotkey_unregister(_pauseHotkey);
|
|
obs_hotkey_unregister(_unpauseHotkey);
|
|
obs_hotkey_unregister(_togglePauseHotkey);
|
|
}
|
|
|
|
void setHotkeyDescriptionHelper(const char *formatModuleText,
|
|
const std::string name, const obs_hotkey_id id)
|
|
{
|
|
QString format{obs_module_text(formatModuleText)};
|
|
QString hotkeyDesc = format.arg(QString::fromStdString(name));
|
|
obs_hotkey_set_description(id, hotkeyDesc.toStdString().c_str());
|
|
}
|
|
|
|
void Macro::SetHotkeysDesc()
|
|
{
|
|
setHotkeyDescriptionHelper("AdvSceneSwitcher.hotkey.macro.pause", _name,
|
|
_pauseHotkey);
|
|
setHotkeyDescriptionHelper("AdvSceneSwitcher.hotkey.macro.unpause",
|
|
_name, _unpauseHotkey);
|
|
setHotkeyDescriptionHelper("AdvSceneSwitcher.hotkey.macro.togglePause",
|
|
_name, _togglePauseHotkey);
|
|
}
|
|
|
|
void SwitcherData::saveMacros(obs_data_t *obj)
|
|
{
|
|
switcher->macroProperties.Save(obj);
|
|
|
|
obs_data_array_t *macroArray = obs_data_array_create();
|
|
for (auto &m : macros) {
|
|
obs_data_t *array_obj = obs_data_create();
|
|
|
|
m->Save(array_obj);
|
|
obs_data_array_push_back(macroArray, array_obj);
|
|
|
|
obs_data_release(array_obj);
|
|
}
|
|
obs_data_set_array(obj, "macros", macroArray);
|
|
obs_data_array_release(macroArray);
|
|
}
|
|
|
|
void SwitcherData::loadMacros(obs_data_t *obj)
|
|
{
|
|
Hotkey::ClearAllHotkeys();
|
|
switcher->macroProperties.Load(obj);
|
|
|
|
macros.clear();
|
|
obs_data_array_t *macroArray = obs_data_get_array(obj, "macros");
|
|
size_t count = obs_data_array_count(macroArray);
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
obs_data_t *array_obj = obs_data_array_item(macroArray, i);
|
|
macros.emplace_back(std::make_shared<Macro>());
|
|
macros.back()->Load(array_obj);
|
|
obs_data_release(array_obj);
|
|
}
|
|
obs_data_array_release(macroArray);
|
|
|
|
int groupCount = 0;
|
|
std::shared_ptr<Macro> group;
|
|
std::vector<std::shared_ptr<Macro>> invalidGroups;
|
|
for (auto &m : macros) {
|
|
if (groupCount && m->IsGroup()) {
|
|
blog(LOG_ERROR,
|
|
"nested group detected - will delete \"%s\"",
|
|
m->Name().c_str());
|
|
invalidGroups.emplace_back(m);
|
|
continue;
|
|
}
|
|
if (groupCount) {
|
|
m->SetParent(group);
|
|
groupCount--;
|
|
}
|
|
if (m->IsGroup()) {
|
|
groupCount = m->GroupSize();
|
|
group = m;
|
|
}
|
|
m->PostLoad();
|
|
}
|
|
|
|
if (groupCount) {
|
|
blog(LOG_ERROR,
|
|
"invalid group size detected - will delete \"%s\"",
|
|
group->Name().c_str());
|
|
invalidGroups.emplace_back(group);
|
|
}
|
|
|
|
for (auto &m : invalidGroups) {
|
|
auto it = std::find(macros.begin(), macros.end(), m);
|
|
if (it == macros.end()) {
|
|
continue;
|
|
}
|
|
macros.erase(it);
|
|
}
|
|
}
|
|
|
|
bool SwitcherData::checkMacros()
|
|
{
|
|
bool ret = false;
|
|
for (auto &m : macros) {
|
|
if (m->CeckMatch()) {
|
|
ret = true;
|
|
// This has to be performed here for now as actions are
|
|
// not performed immediately after checking conditions.
|
|
if (m->SwitchesScene()) {
|
|
switcher->macroSceneSwitched = true;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool SwitcherData::runMacros()
|
|
{
|
|
// Create copy of macor list as elements might be removed, inserted, or
|
|
// reordered while macros are currently being executed.
|
|
// For example, this can happen if a macro is performing a wait action,
|
|
// as the main lock will be unlocked during this time.
|
|
auto runPhaseMacros = macros;
|
|
|
|
// Avoid deadlocks when opening settings window and calling frontend
|
|
// API functions at the same time.
|
|
//
|
|
// If the timing is just right, the frontend API call will call
|
|
// QMetaObject::invokeMethod(...) with Qt::BlockingQueuedConnection
|
|
// while holding the main switcher mutex.
|
|
// But this invokeMethod call itself will be blocked as it is waiting
|
|
// the constructor of AdvSceneSwitcher() to complete.
|
|
// The constructor of AdvSceneSwitcher() cannot continue however as it
|
|
// cannot lock the main switcher mutex.
|
|
if (GetLock()) {
|
|
GetLock()->unlock();
|
|
}
|
|
|
|
for (auto &m : runPhaseMacros) {
|
|
if (m && m->Matched()) {
|
|
vblog(LOG_INFO, "running macro: %s", m->Name().c_str());
|
|
if (!m->PerformActions()) {
|
|
blog(LOG_WARNING, "abort macro: %s",
|
|
m->Name().c_str());
|
|
}
|
|
}
|
|
}
|
|
if (GetLock()) {
|
|
GetLock()->lock();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Macro *GetMacroByName(const char *name)
|
|
{
|
|
for (auto &m : switcher->macros) {
|
|
if (m->Name() == name) {
|
|
return m.get();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
Macro *GetMacroByQString(const QString &name)
|
|
{
|
|
return GetMacroByName(name.toUtf8().constData());
|
|
}
|
|
|
|
std::weak_ptr<Macro> GetWeakMacroByName(const char *name)
|
|
{
|
|
for (auto &m : switcher->macros) {
|
|
if (m->Name() == name) {
|
|
return m;
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|