mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 04:36:21 -05:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cc44b4470 | ||
|
|
54356d9410 | ||
|
|
defbdf8b7a | ||
|
|
a01af6cfc3 | ||
|
|
4d9c7f1054 | ||
|
|
ced36e2b5b | ||
|
|
d2749f29b0 | ||
|
|
b78c8bc1de | ||
|
|
cc4a3560d8 | ||
|
|
3ab2ea9e66 |
@@ -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"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -185,6 +185,7 @@ private:
|
||||
VariableLineEdit *_envVariable;
|
||||
SceneSelectionWidget *_scenes;
|
||||
TempVariableSelection *_tempVars;
|
||||
QLabel *_tempVarsHelp;
|
||||
VariableSpinBox *_sceneItemIndex;
|
||||
QComboBox *_direction;
|
||||
VariableSpinBox *_stringLength;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ®ex)
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
414
plugins/base/macro-condition-folder.cpp
Normal file
414
plugins/base/macro-condition-folder.cpp
Normal 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 ®ex,
|
||||
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 ®ex)
|
||||
{
|
||||
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
|
||||
109
plugins/base/macro-condition-folder.hpp
Normal file
109
plugins/base/macro-condition-folder.hpp
Normal 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
|
||||
@@ -181,12 +181,6 @@ static bool windowIsValid(HWND window)
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD id;
|
||||
GetWindowThreadProcessId(window, &id);
|
||||
if (id == GetCurrentProcessId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only accept for top level windows and not controls within them
|
||||
if (window != GetAncestor(window, GA_ROOT)) {
|
||||
return false;
|
||||
|
||||
@@ -29,6 +29,12 @@ static void setTabVisible(QTabWidget *tabWidget, bool visible)
|
||||
obs_module_text("AdvSceneSwitcher.twitchConnectionTab.title"));
|
||||
}
|
||||
|
||||
static bool twitchTabIsSelected(QTabWidget *tabWidget)
|
||||
{
|
||||
return tabWidget->tabText(tabWidget->currentIndex()) ==
|
||||
obs_module_text("AdvSceneSwitcher.twitchConnectionTab.title");
|
||||
}
|
||||
|
||||
TwitchConnectionsTable *TwitchConnectionsTable::Create()
|
||||
{
|
||||
tabWidget = new TwitchConnectionsTable();
|
||||
@@ -211,11 +217,11 @@ static void setupTab(QTabWidget *tab)
|
||||
}
|
||||
});
|
||||
|
||||
auto timer = new QTimer(tabWidget);
|
||||
timer->setInterval(1000);
|
||||
QWidget::connect(timer, &QTimer::timeout,
|
||||
[]() { updateConnectionStatus(tabWidget->Table()); });
|
||||
timer->start();
|
||||
QWidget::connect(tab, &QTabWidget::currentChanged, [tab]() {
|
||||
if (twitchTabIsSelected(tab)) {
|
||||
updateConnectionStatus(tabWidget->Table());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
Reference in New Issue
Block a user