Compare commits

...

8 Commits

Author SHA1 Message Date
WarmUpTill
7cc44b4470 Fix folder watch condition filter handling 2024-05-19 03:46:53 +02:00
WarmUpTill
54356d9410 Add tooltip to macro property selection in variable action 2024-05-19 01:44:10 +02:00
WarmUpTill
defbdf8b7a Add folder condition
It allows you to watch for chagnes in a given folder
2024-05-19 01:44:10 +02:00
WarmUpTill
a01af6cfc3 Adapt code to be compatible with older MSVC redistributable versions 2024-05-18 23:31:04 +02:00
WarmUpTill
4d9c7f1054 Fix typo 2024-05-18 23:31:04 +02:00
WarmUpTill
ced36e2b5b Add nodiscard to sync-helpers 2024-05-18 23:31:04 +02:00
WarmUpTill
d2749f29b0 Add note about limitations of the window focus action 2024-05-14 20:23:36 +02:00
WarmUpTill
b78c8bc1de Assume minimum volume when no volume update was received within 250ms
Without this timeout the peak volume update, which was received last,
would be used permanently until the next update arrives.
This might only take place when the source produces audio output again.
2024-05-14 19:12:49 +02:00
14 changed files with 652 additions and 29 deletions

View File

@@ -683,6 +683,17 @@ AdvSceneSwitcher.condition.clipboard.condition.isImage="Clipboard contains an im
AdvSceneSwitcher.condition.clipboard.condition.isURL="Clipboard contains an URL"
AdvSceneSwitcher.condition.clipboard.condition.matches="Clipboard content matches"
AdvSceneSwitcher.condition.clipboard.condition.entry="{{conditions}}{{regex}}{{urlInfo}}"
AdvSceneSwitcher.condition.folder="Folder watch"
AdvSceneSwitcher.condition.folder.tooltip="This condition type will allow you to monitor the contents of a folder.\nNote that the monitoring will *not* recursively scan for changes in sub directories within directories of the selected folder!\nNote that if there are several changes during a short period of time, some of the changes might not emit this signal.\nHowever, the last change in the sequence of changes always will."
AdvSceneSwitcher.condition.folder.condition.any="Any change happened"
AdvSceneSwitcher.condition.folder.condition.fileAdd="A file was added"
AdvSceneSwitcher.condition.folder.condition.fileChange="A file was modified"
AdvSceneSwitcher.condition.folder.condition.fileRemove="A file was removed"
AdvSceneSwitcher.condition.folder.condition.folderAdd="A directory was added"
AdvSceneSwitcher.condition.folder.condition.folderRemove="A directory was removed"
AdvSceneSwitcher.condition.folder.entry="{{conditions}}in{{folder}}{{tooltip}}"
AdvSceneSwitcher.condition.folder.enableFilter="Only evaluate to true, if the changed path matches a patern"
AdvSceneSwitcher.condition.folder.entry.filter="{{filter}}{{regex}}"
; Macro Actions
AdvSceneSwitcher.action.scene="Switch scene"
@@ -972,6 +983,7 @@ AdvSceneSwitcher.action.variable.type.sceneItemCount="Set to scene item count of
AdvSceneSwitcher.action.variable.type.stringLength="Set to length of string"
AdvSceneSwitcher.action.variable.type.extractJson="Extract json field with name"
AdvSceneSwitcher.action.variable.type.setToTempvar="Set to macro property"
AdvSceneSwitcher.action.variable.type.setToTempvar.help="This action type will allow you to extract values out of segments of the current macro and assign those values to the selected variable.\nFor example, you can get the current scene name from a scene condition and assign this name to a variable."
AdvSceneSwitcher.action.variable.type.sceneItemName="Set to scene item name at index"
AdvSceneSwitcher.action.variable.type.padValue="Pad current value"
AdvSceneSwitcher.action.variable.type.truncateValue="Truncate current value"
@@ -988,7 +1000,7 @@ AdvSceneSwitcher.action.variable.invalidSelection="Invalid selection!"
AdvSceneSwitcher.action.variable.actionNoVariableSupport="Getting variable values from %1 actions is not supported!"
AdvSceneSwitcher.action.variable.conditionNoVariableSupport="Getting variable values from %1 conditions is not supported!"
AdvSceneSwitcher.action.variable.currentSegmentValue="Current value:"
AdvSceneSwitcher.action.variable.entry.other="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}{{sceneItemIndex}}{{direction}}{{stringLength}}{{paddingCharSelection}}"
AdvSceneSwitcher.action.variable.entry.other="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}{{envVariableName}}{{scenes}}{{tempVars}}{{tempVarsHelp}}{{sceneItemIndex}}{{direction}}{{stringLength}}{{paddingCharSelection}}"
AdvSceneSwitcher.action.variable.entry.pad="{{actions}}of{{variables}}to length{{stringLength}}by adding{{paddingCharSelection}}to the{{direction}}"
AdvSceneSwitcher.action.variable.entry.truncate="{{actions}}of{{variables}}to length{{stringLength}}by removing characters from the{{direction}}"
AdvSceneSwitcher.action.variable.entry.substringIndex="Substring start:{{subStringStart}}Substring size:{{subStringSize}}"
@@ -1054,6 +1066,8 @@ AdvSceneSwitcher.action.queue.entry.add="{{actions}}actions of macro{{macros}}to
AdvSceneSwitcher.action.queue.entry.other="{{actions}}queue{{queues}}"
AdvSceneSwitcher.action.window="Window"
AdvSceneSwitcher.action.window.type.setFocusWindow="Focus window"
AdvSceneSwitcher.action.window.type.setFocusWindow.limitation="The focus action can only be performed under certain circumstances"
AdvSceneSwitcher.action.window.type.setFocusWindow.limitation.details="At least one of the following conditions must be true:\n OBS is the foreground process.\n There is currently no foreground window, and thus no foreground process.\n OBS received the last input event."
AdvSceneSwitcher.action.window.type.maximizeWindow="Maximize window"
AdvSceneSwitcher.action.window.type.minimizeWindow="Minimize window"
AdvSceneSwitcher.action.window.type.closeWindow="Close window"
@@ -1732,6 +1746,12 @@ AdvSceneSwitcher.tempVar.clipboard.text.description="The text contained in the c
AdvSceneSwitcher.tempVar.file.content="File content"
AdvSceneSwitcher.tempVar.file.date="File modification date"
AdvSceneSwitcher.tempVar.folder.newFiles="New files"
AdvSceneSwitcher.tempVar.folder.changedFiles="Changed files"
AdvSceneSwitcher.tempVar.folder.removedFiles="Removed files"
AdvSceneSwitcher.tempVar.folder.newDirs="New directories"
AdvSceneSwitcher.tempVar.folder.removedDirs="Removed directories"
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene"

View File

@@ -6,6 +6,7 @@
#include "macro.hpp"
#include "non-modal-dialog.hpp"
#include "source-helpers.hpp"
#include "ui-helpers.hpp"
#include "utility.hpp"
namespace advss {
@@ -630,6 +631,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_scenes(new SceneSelectionWidget(this, true, false, true, true,
true)),
_tempVars(new TempVariableSelection(this)),
_tempVarsHelp(new QLabel()),
_sceneItemIndex(new VariableSpinBox()),
_direction(new QComboBox()),
_stringLength(new VariableSpinBox()),
@@ -657,6 +659,15 @@ MacroActionVariableEdit::MacroActionVariableEdit(
populateTypeSelection(_actions);
populateDirectionSelection(_direction);
QString path = GetThemeTypeName() == "Light"
? ":/res/images/help.svg"
: ":/res/images/help_light.svg";
QIcon icon(path);
QPixmap pixmap = icon.pixmap(QSize(16, 16));
_tempVarsHelp->setPixmap(pixmap);
_tempVarsHelp->setToolTip(obs_module_text(
"AdvSceneSwitcher.action.variable.type.setToTempvar.help"));
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
this, SLOT(VariableChanged(const QString &)));
QWidget::connect(_variables2, SIGNAL(SelectionChanged(const QString &)),
@@ -742,6 +753,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
{"{{envVariableName}}", _envVariable},
{"{{scenes}}", _scenes},
{"{{tempVars}}", _tempVars},
{"{{tempVarsHelp}}", _tempVarsHelp},
{"{{sceneItemIndex}}", _sceneItemIndex},
{"{{direction}}", _direction},
{"{{stringLength}}", _stringLength},
@@ -1202,6 +1214,7 @@ void MacroActionVariableEdit::SelectionChanged(const TempVariableRef &var)
auto lock = LockContext();
_entryData->_tempVar = var;
SetWidgetVisibility();
}
void MacroActionVariableEdit::SceneItemIndexChanged(
@@ -1268,6 +1281,7 @@ void MacroActionVariableEdit::SetWidgetVisibility()
{"{{envVariableName}}", _envVariable},
{"{{scenes}}", _scenes},
{"{{tempVars}}", _tempVars},
{"{{tempVarsHelp}}", _tempVarsHelp},
{"{{sceneItemIndex}}", _sceneItemIndex},
{"{{direction}}", _direction},
{"{{stringLength}}", _stringLength},
@@ -1381,6 +1395,10 @@ void MacroActionVariableEdit::SetWidgetVisibility()
MacroActionVariable::Type::SCENE_ITEM_NAME);
_tempVars->setVisible(_entryData->_type ==
MacroActionVariable::Type::SET_TO_TEMPVAR);
_tempVarsHelp->setVisible(
_entryData->_type ==
MacroActionVariable::Type::SET_TO_TEMPVAR &&
!_entryData->_tempVar.HasValidID());
_sceneItemIndex->setVisible(_entryData->_type ==
MacroActionVariable::Type::SCENE_ITEM_NAME);
_direction->setVisible(

View File

@@ -185,6 +185,7 @@ private:
VariableLineEdit *_envVariable;
SceneSelectionWidget *_scenes;
TempVariableSelection *_tempVars;
QLabel *_tempVarsHelp;
VariableSpinBox *_sceneItemIndex;
QComboBox *_direction;
VariableSpinBox *_stringLength;

View File

@@ -11,19 +11,16 @@
namespace advss {
static bool registerTab();
static void setupTab(QTabWidget *);
static bool registerTabDone = registerTab();
static ActionQueueTable *tabWidget = nullptr;
static bool registerTab()
void RegisterActionQueueTab()
{
AddPluginInitStep([]() {
AddSetupTabCallback("actionQueueTab", ActionQueueTable::Create,
setupTab);
});
return true;
}
static void setTabVisible(QTabWidget *tabWidget, bool visible)
@@ -70,11 +67,11 @@ static QStringList getCellLabels(ActionQueue *queue, bool addName = true)
<< QString::fromStdString(obs_module_text(
queue->IsRunning()
? "AdvSceneSwitcher.actionQueueTab.yes"
: "AdvSceneSwitcher.actionQueueTab.yes"))
: "AdvSceneSwitcher.actionQueueTab.no"))
<< QString::fromStdString(obs_module_text(
queue->RunsOnStartup()
? "AdvSceneSwitcher.actionQueueTab.yes"
: "AdvSceneSwitcher.actionQueueTab.yes"));
: "AdvSceneSwitcher.actionQueueTab.no"));
return result;
}

View File

@@ -11,6 +11,8 @@ std::deque<std::shared_ptr<Item>> &GetActionQueues()
return queues;
}
void RegisterActionQueueTab();
void SetupActionQueues()
{
static bool done = false;
@@ -19,6 +21,7 @@ void SetupActionQueues()
}
AddSaveStep(SaveActionQueues);
AddLoadStep(LoadActionQueues);
RegisterActionQueueTab();
done = true;
}

View File

@@ -5,8 +5,8 @@
namespace advss {
EXPORT std::mutex *GetMutex();
EXPORT std::lock_guard<std::mutex> LockContext();
EXPORT std::unique_lock<std::mutex> *GetLoopLock();
[[nodiscard]] EXPORT std::mutex *GetMutex();
[[nodiscard]] EXPORT std::lock_guard<std::mutex> LockContext();
[[nodiscard]] EXPORT std::unique_lock<std::mutex> *GetLoopLock();
} // namespace advss

View File

@@ -65,6 +65,7 @@ public:
EXPORT void Load(obs_data_t *, Macro *, const char *name = "tempVar");
EXPORT std::optional<const TempVariable> GetTempVariable(Macro *) const;
EXPORT bool operator==(const TempVariableRef &other) const;
bool HasValidID() const { return !_id.empty(); }
private:
enum class SegmentType { NONE, CONDITION, ACTION, ELSEACTION };

View File

@@ -87,6 +87,8 @@ target_sources(
macro-condition-file.hpp
macro-condition-filter.cpp
macro-condition-filter.hpp
macro-condition-folder.cpp
macro-condition-folder.hpp
macro-condition-hotkey.cpp
macro-condition-hotkey.hpp
macro-condition-idle.cpp

View File

@@ -2,6 +2,7 @@
#include "layout-helpers.hpp"
#include "platform-funcs.hpp"
#include "selection-helpers.hpp"
#include "ui-helpers.hpp"
namespace advss {
@@ -163,7 +164,8 @@ MacroActionWindowEdit::MacroActionWindowEdit(
: QWidget(parent),
_actions(new QComboBox()),
_windows(new QComboBox()),
_regex(new RegexConfigWidget(this))
_regex(new RegexConfigWidget(this)),
_infoLayout(new QHBoxLayout())
{
populateActionSelection(_actions);
@@ -171,6 +173,22 @@ MacroActionWindowEdit::MacroActionWindowEdit(
_windows->setMaxVisibleItems(20);
PopulateWindowSelection(_windows);
auto focusLimitation = new QLabel(obs_module_text(
"AdvSceneSwitcher.action.window.type.setFocusWindow.limitation"));
_infoLayout->addWidget(focusLimitation);
const QString path = GetThemeTypeName() == "Light"
? ":/res/images/help.svg"
: ":/res/images/help_light.svg";
const QIcon icon(path);
const QPixmap pixmap = icon.pixmap(QSize(16, 16));
auto focusLimitationDetails = new QLabel();
focusLimitationDetails->setPixmap(pixmap);
focusLimitationDetails->setToolTip(obs_module_text(
"AdvSceneSwitcher.action.window.type.setFocusWindow.limitation.details"));
_infoLayout->addWidget(focusLimitationDetails);
_infoLayout->addStretch();
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_windows, SIGNAL(currentTextChanged(const QString &)),
@@ -179,12 +197,15 @@ MacroActionWindowEdit::MacroActionWindowEdit(
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(RegexChanged(const RegexConfig &)));
auto layout = new QHBoxLayout;
auto entryLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.window.entry"),
layout,
entryLayout,
{{"{{actions}}", _actions},
{"{{windows}}", _windows},
{"{{regex}}", _regex}});
auto layout = new QVBoxLayout();
layout->addLayout(entryLayout);
layout->addLayout(_infoLayout);
setLayout(layout);
_entryData = entryData;
@@ -200,8 +221,7 @@ void MacroActionWindowEdit::UpdateEntryData()
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_windows->setCurrentText(QString::fromStdString(_entryData->_window));
_regex->SetRegexConfig(_entryData->_regex);
adjustSize();
updateGeometry();
SetWidgetVisibility();
}
void MacroActionWindowEdit::WindowChanged(const QString &text)
@@ -228,6 +248,15 @@ void MacroActionWindowEdit::RegexChanged(const RegexConfig &regex)
updateGeometry();
}
void MacroActionWindowEdit::SetWidgetVisibility()
{
SetLayoutVisible(_infoLayout,
_entryData->_action ==
MacroActionWindow::Action::SET_FOCUS_WINDOW);
adjustSize();
updateGeometry();
}
void MacroActionWindowEdit::ActionChanged(int value)
{
if (_loading || !_entryData) {
@@ -236,6 +265,7 @@ void MacroActionWindowEdit::ActionChanged(int value)
auto lock = LockContext();
_entryData->_action = static_cast<MacroActionWindow::Action>(value);
SetWidgetVisibility();
}
} // namespace advss

View File

@@ -59,9 +59,12 @@ signals:
void HeaderInfoChanged(const QString &);
private:
void SetWidgetVisibility();
QComboBox *_actions;
QComboBox *_windows;
RegexConfigWidget *_regex;
QHBoxLayout *_infoLayout;
std::shared_ptr<MacroActionWindow> _entryData;
bool _loading = true;

View File

@@ -61,26 +61,48 @@ MacroConditionAudio::~MacroConditionAudio()
obs_volmeter_destroy(_volmeter);
}
float MacroConditionAudio::GetVolumePeak()
{
using namespace std::chrono_literals;
using namespace std::chrono;
static constexpr std::chrono::milliseconds timeout = 250ms;
// OBS might rarely not update _peak quickly enough when very low
// intervals are configured on the General tab.
// In that case _peak might be set to negative infinity still, which
// will result in unexpected behavior, so we use the previously valid
// peak value instead.
//
// If no volume update was received within a timeout window, however, it
// is assumed, that the source no longer produces any audio output and
// thus a peak volume value of negative infinity is used.
float peak;
std::lock_guard<std::mutex> lock(_peakMutex);
auto msPassedSinceLastUpdate = duration_cast<milliseconds>(
high_resolution_clock::now() - _lastPeakUpdate);
if (_lastPeakUpdate.time_since_epoch().count() != 0 &&
msPassedSinceLastUpdate > timeout) {
peak = -std::numeric_limits<float>::infinity();
} else {
peak = _peakUpdated ? _peak : _previousPeak;
}
_previousPeak = peak;
_peak = -std::numeric_limits<float>::infinity();
_peakUpdated = false;
return peak;
}
bool MacroConditionAudio::CheckOutputCondition()
{
bool ret = false;
OBSSourceAutoRelease source =
obs_weak_source_get_source(_audioSource.GetSource());
// OBS might very rarely not update _peak quickly enough when very low
// intervals are configured on the General tab.
// In that case _peak might be set to negative infinity still, which
// will result in unexpected behavior, so we use the previously valid
// peak value instead.
float peak;
{
std::lock_guard<std::mutex> lock(_peakMutex);
peak = _peakUpdated ? _peak : _previousPeak;
_previousPeak = peak;
_peak = -std::numeric_limits<float>::infinity();
_peakUpdated = false;
}
float peak = GetVolumePeak();
double curVolume = _useDb ? peak : DecibelToPercent(peak) * 100;
switch (_outputCondition) {
@@ -349,6 +371,7 @@ void MacroConditionAudio::SetVolumeLevel(void *data, const float *,
}
}
c->_peakUpdated = true;
c->_lastPeakUpdate = std::chrono::high_resolution_clock::now();
}
void MacroConditionAudio::ResetVolmeter()

View File

@@ -71,12 +71,14 @@ private:
bool CheckMonitor();
bool CheckBalance();
void SetupTempVars();
float GetVolumePeak();
Type _checkType = Type::OUTPUT_VOLUME;
std::mutex _peakMutex;
float _peak = -std::numeric_limits<float>::infinity();
float _previousPeak = -std::numeric_limits<float>::infinity();
bool _peakUpdated = false;
std::chrono::high_resolution_clock::time_point _lastPeakUpdate = {};
static bool _registered;
static const std::string id;
};

View File

@@ -0,0 +1,414 @@
#include "macro-condition-folder.hpp"
#include "macro-helpers.hpp"
#include "layout-helpers.hpp"
#include "ui-helpers.hpp"
#include <QDir>
#include <QFileInfo>
namespace advss {
const std::string MacroConditionFolder::id = "folder";
bool MacroConditionFolder::_registered = MacroConditionFactory::Register(
MacroConditionFolder::id,
{MacroConditionFolder::Create, MacroConditionFolderEdit::Create,
"AdvSceneSwitcher.condition.folder"});
static const std::map<MacroConditionFolder::Condition, std::string> conditions =
{
{MacroConditionFolder::Condition::ANY,
"AdvSceneSwitcher.condition.folder.condition.any"},
{MacroConditionFolder::Condition::FILE_ADD,
"AdvSceneSwitcher.condition.folder.condition.fileAdd"},
{MacroConditionFolder::Condition::FILE_CHANGE,
"AdvSceneSwitcher.condition.folder.condition.fileChange"},
{MacroConditionFolder::Condition::FILE_REMOVE,
"AdvSceneSwitcher.condition.folder.condition.fileRemove"},
{MacroConditionFolder::Condition::FOLDER_ADD,
"AdvSceneSwitcher.condition.folder.condition.folderAdd"},
{MacroConditionFolder::Condition::FOLDER_REMOVE,
"AdvSceneSwitcher.condition.folder.condition.folderRemove"},
};
MacroConditionFolder::MacroConditionFolder(Macro *m) : MacroCondition(m, true)
{
}
bool MacroConditionFolder::CheckCondition()
{
std::lock_guard<std::mutex> lock(_mutex);
bool ret = _matched;
if (_lastWatchedValue != _folder.UnresolvedValue()) {
SetupWatcher();
}
SetTempVarValues();
_newFiles.clear();
_changedFiles.clear();
_removedFiles.clear();
_newDirs.clear();
_removedDirs.clear();
_matched = false;
return ret;
}
bool MacroConditionFolder::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
_folder.Save(obj, "file");
obs_data_set_bool(obj, "enableFilter", _enableFilter);
_regex.Save(obj);
_filter.Save(obj, "filter");
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
return true;
}
bool MacroConditionFolder::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_folder.Load(obj, "file");
_enableFilter = obs_data_get_bool(obj, "enableFilter");
_regex.Load(obj);
_regex.SetEnabled(true); // Already controlled via _enableFilter
_filter.Load(obj, "filter");
_condition = static_cast<Condition>(obs_data_get_int(obj, "condition"));
SetupWatcher();
return true;
}
std::string MacroConditionFolder::GetShortDesc() const
{
return _folder.UnresolvedValue();
}
void MacroConditionFolder::SetFolder(const std::string &folder)
{
_folder = folder;
SetupWatcher();
}
static QSet<QString> getFilesInDir(const QString &path)
{
QSet<QString> result;
for (const auto &file : QDir(path).entryList(QDir::Files)) {
result << file;
}
return result;
}
static QSet<QString> getDirsInDir(const QString &path)
{
QSet<QString> result;
for (const auto &dir : QDir(path).entryList(QDir::AllDirs)) {
result << dir;
}
return result;
}
static void reduceSetToPatternMatch(QSet<QString> &set,
const RegexConfig &regex,
const std::string &pattern)
{
QSet<QString> copy = set;
for (const auto &value : copy) {
if (!regex.Matches(value.toStdString(), pattern)) {
set.remove(value);
}
}
}
void MacroConditionFolder::DirectoryChanged(const QString &path)
{
std::lock_guard<std::mutex> lock(_mutex);
if (MacroIsPaused(GetMacro())) {
return;
}
auto currentFiles = getFilesInDir(path);
auto currentDirs = getDirsInDir(path);
if (currentFiles.count() > _currentFiles.count()) {
_newFiles += currentFiles - _currentFiles;
} else {
_removedFiles += _currentFiles - currentFiles;
}
if (currentDirs.count() > _currentDirs.count()) {
_newDirs += currentDirs - _currentDirs;
} else {
_removedDirs += _currentDirs - currentDirs;
}
if (_enableFilter) {
reduceSetToPatternMatch(_newFiles, _regex, _filter);
reduceSetToPatternMatch(_removedFiles, _regex, _filter);
reduceSetToPatternMatch(_newDirs, _regex, _filter);
reduceSetToPatternMatch(_removedDirs, _regex, _filter);
}
switch (_condition) {
case Condition::ANY:
_matched = _matched || _newFiles.count() > 0 ||
_removedFiles.count() > 0 || _newDirs.count() > 0 ||
_removedDirs.count() > 0;
break;
case Condition::FILE_ADD:
_matched = _newFiles.count() > 0;
break;
case Condition::FILE_REMOVE:
_matched = _removedFiles.count() > 0;
break;
case Condition::FOLDER_ADD:
_matched = _newDirs.count() > 0;
break;
case Condition::FOLDER_REMOVE:
_matched = _removedDirs.count() > 0;
break;
default:
break;
}
for (const auto &newFile : _newFiles) {
_watcher->addPath(path + "/" + newFile);
}
_currentFiles = currentFiles;
_currentDirs = currentDirs;
}
void MacroConditionFolder::FileChanged(const QString &path)
{
std::lock_guard<std::mutex> lock(_mutex);
QFileInfo fileInfo(path);
if (!fileInfo.exists()) {
return;
}
const auto fileName = fileInfo.fileName();
if (_enableFilter && !_regex.Matches(fileName.toStdString(), _filter)) {
return;
}
_changedFiles << fileName;
if (_condition == Condition::FILE_CHANGE ||
_condition == Condition::ANY) {
_matched = true;
}
}
static QStringList getFileWatcherList(const QSet<QString> &files,
const QString &dirName)
{
QStringList list;
for (const auto &value : files) {
list << (dirName + "/" + value);
}
return list;
}
void MacroConditionFolder::SetupWatcher()
{
_watcher = std::make_unique<QFileSystemWatcher>();
const auto path = QString::fromStdString(_folder);
_currentFiles = getFilesInDir(path);
_currentDirs = getDirsInDir(path);
_lastWatchedValue = _folder.UnresolvedValue();
connect(_watcher.get(), SIGNAL(directoryChanged(const QString &)), this,
SLOT(DirectoryChanged(const QString &)));
connect(_watcher.get(), SIGNAL(fileChanged(const QString &)), this,
SLOT(FileChanged(const QString &)));
_watcher.get()->addPaths(getFileWatcherList(_currentFiles, path));
_watcher->addPath(path);
}
void MacroConditionFolder::SetTempVarValues()
{
auto setVarHelper = [this](const QSet<QString> &set,
const std::string &id) {
std::string result;
for (const auto &value : set) {
result += value.toStdString() + "\n";
}
if (result.size() > 0) {
result.pop_back();
}
SetTempVarValue(id, result);
};
setVarHelper(_newFiles, "newFiles");
setVarHelper(_changedFiles, "changedFiles");
setVarHelper(_removedFiles, "removedFiles");
setVarHelper(_newDirs, "newDirs");
setVarHelper(_removedDirs, "removedDirs");
}
void MacroConditionFolder::SetupTempVars()
{
MacroCondition::SetupTempVars();
AddTempvar("newFiles",
obs_module_text("AdvSceneSwitcher.tempVar.folder.newFiles"));
AddTempvar("changedFiles",
obs_module_text(
"AdvSceneSwitcher.tempVar.folder.changedFiles"));
AddTempvar("removedFiles",
obs_module_text(
"AdvSceneSwitcher.tempVar.folder.removedFiles"));
AddTempvar("newDirs",
obs_module_text("AdvSceneSwitcher.tempVar.folder.newDirs"));
AddTempvar(
"removedDirs",
obs_module_text("AdvSceneSwitcher.tempVar.folder.removedDirs"));
}
static void populateConditions(QComboBox *list)
{
for (const auto &[value, name] : conditions) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
}
MacroConditionFolderEdit::MacroConditionFolderEdit(
QWidget *parent, std::shared_ptr<MacroConditionFolder> entryData)
: QWidget(parent),
_conditions(new QComboBox()),
_folder(new FileSelection(FileSelection::Type::FOLDER)),
_enableFilter(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.folder.enableFilter"))),
_filterLayout(new QHBoxLayout()),
_regex(new RegexConfigWidget(this, false)),
_filter(new VariableLineEdit(this))
{
QString path = GetThemeTypeName() == "Light"
? ":/res/images/help.svg"
: ":/res/images/help_light.svg";
QIcon icon(path);
QPixmap pixmap = icon.pixmap(QSize(16, 16));
auto tooltipLabel = new QLabel(this);
tooltipLabel->setPixmap(pixmap);
tooltipLabel->setToolTip(
obs_module_text("AdvSceneSwitcher.condition.folder.tooltip"));
populateConditions(_conditions);
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_folder, SIGNAL(PathChanged(const QString &)), this,
SLOT(PathChanged(const QString &)));
QWidget::connect(_enableFilter, SIGNAL(stateChanged(int)), this,
SLOT(EnableFilterChanged(int)));
QWidget::connect(_regex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(RegexChanged(const RegexConfig &)));
QWidget::connect(_filter, SIGNAL(editingFinished()), this,
SLOT(FilterChanged()));
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{conditions}}", _conditions}, {"{{folder}}", _folder},
{"{{tooltip}}", tooltipLabel}, {"{{regex}}", _regex},
{"{{filter}}", _filter},
};
auto entryLayout = new QHBoxLayout();
entryLayout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.folder.entry"),
entryLayout, widgetPlaceholders, false);
_filterLayout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.folder.entry.filter"),
_filterLayout, widgetPlaceholders, false);
auto layout = new QVBoxLayout();
layout->addLayout(entryLayout);
layout->addWidget(_enableFilter);
layout->addLayout(_filterLayout);
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionFolderEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_conditions->setCurrentIndex(_conditions->findData(
static_cast<int>(_entryData->_condition)));
_folder->SetPath(_entryData->GetFolder());
_enableFilter->setChecked(_entryData->_enableFilter);
_regex->SetRegexConfig(_entryData->_regex);
_filter->setText(_entryData->_filter);
SetWidgetVisibility();
}
void MacroConditionFolderEdit::ConditionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_condition = static_cast<MacroConditionFolder::Condition>(
_conditions->itemData(index).toInt());
}
void MacroConditionFolderEdit::PathChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->SetFolder(text.toStdString());
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroConditionFolderEdit::RegexChanged(const RegexConfig &regex)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_regex = regex;
_entryData->_regex.SetEnabled(true);
}
void MacroConditionFolderEdit::FilterChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_filter = _filter->text().toStdString();
}
void MacroConditionFolderEdit::EnableFilterChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_enableFilter = value;
SetWidgetVisibility();
}
void MacroConditionFolderEdit::SetWidgetVisibility()
{
SetLayoutVisible(_filterLayout, _entryData->_enableFilter);
adjustSize();
updateGeometry();
}
} // namespace advss

View File

@@ -0,0 +1,109 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "file-selection.hpp"
#include "regex-config.hpp"
#include "variable-line-edit.hpp"
#include <QFileSystemWatcher>
namespace advss {
class MacroConditionFolder : public QObject, public MacroCondition {
Q_OBJECT
public:
MacroConditionFolder(Macro *m);
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetShortDesc() const;
std::string GetId() const { return id; };
static std::shared_ptr<MacroCondition> Create(Macro *m)
{
return std::make_shared<MacroConditionFolder>(m);
}
void SetFolder(const std::string &);
StringVariable GetFolder() const { return _folder; }
enum class Condition {
ANY,
FILE_ADD,
FILE_CHANGE,
FILE_REMOVE,
FOLDER_ADD,
FOLDER_REMOVE,
};
Condition _condition = Condition::ANY;
bool _enableFilter = false;
RegexConfig _regex = RegexConfig(true);
StringVariable _filter = ".*";
private slots:
void DirectoryChanged(const QString &);
void FileChanged(const QString &);
private:
void SetupWatcher();
void SetTempVarValues();
void SetupTempVars();
StringVariable _folder = obs_module_text("AdvSceneSwitcher.enterPath");
std::unique_ptr<QFileSystemWatcher> _watcher;
std::string _lastWatchedValue = "";
std::mutex _mutex;
bool _matched = false;
QSet<QString> _newFiles;
QSet<QString> _changedFiles;
QSet<QString> _removedFiles;
QSet<QString> _newDirs;
QSet<QString> _removedDirs;
QSet<QString> _currentFiles;
QSet<QString> _currentDirs;
static bool _registered;
static const std::string id;
};
class MacroConditionFolderEdit : public QWidget {
Q_OBJECT
public:
MacroConditionFolderEdit(
QWidget *parent,
std::shared_ptr<MacroConditionFolder> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionFolderEdit(
parent,
std::dynamic_pointer_cast<MacroConditionFolder>(cond));
}
private slots:
void ConditionChanged(int index);
void PathChanged(const QString &text);
void EnableFilterChanged(int value);
void RegexChanged(const RegexConfig &);
void FilterChanged();
signals:
void HeaderInfoChanged(const QString &);
private:
void SetWidgetVisibility();
QComboBox *_conditions;
FileSelection *_folder;
QCheckBox *_enableFilter;
QHBoxLayout *_filterLayout;
RegexConfigWidget *_regex;
VariableLineEdit *_filter;
std::shared_ptr<MacroConditionFolder> _entryData;
bool _loading = true;
};
} // namespace advss