mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-04-26 08:10:08 -05:00
Add canvas and position variable support to scene condition
This commit is contained in:
parent
16136b8741
commit
8f54e71e61
|
|
@ -325,6 +325,7 @@ AdvSceneSwitcher.condition.scene.type.previousPattern="Previous scene matches"
|
||||||
AdvSceneSwitcher.condition.scene.type.previewPattern="Preview scene matches"
|
AdvSceneSwitcher.condition.scene.type.previewPattern="Preview scene matches"
|
||||||
AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour="During transition check for transition target scene"
|
AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour="During transition check for transition target scene"
|
||||||
AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="During transition check for transition source scene"
|
AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="During transition check for transition source scene"
|
||||||
|
AdvSceneSwitcher.condition.scene.canvasNotSupported="This check is not supported for the currently selected canvas \"%1\""
|
||||||
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}{{regex}}"
|
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}{{regex}}"
|
||||||
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
||||||
AdvSceneSwitcher.condition.window="Window"
|
AdvSceneSwitcher.condition.window="Window"
|
||||||
|
|
@ -2103,6 +2104,7 @@ AdvSceneSwitcher.tempVar.clipboard.mimeType.all.description="All MIME types of t
|
||||||
AdvSceneSwitcher.tempVar.scene.current="Current scene"
|
AdvSceneSwitcher.tempVar.scene.current="Current scene"
|
||||||
AdvSceneSwitcher.tempVar.scene.previous="Previous scene"
|
AdvSceneSwitcher.tempVar.scene.previous="Previous scene"
|
||||||
AdvSceneSwitcher.tempVar.scene.preview="Preview scene"
|
AdvSceneSwitcher.tempVar.scene.preview="Preview scene"
|
||||||
|
AdvSceneSwitcher.tempVar.scene.position="Position in scenes list"
|
||||||
|
|
||||||
AdvSceneSwitcher.tempVar.window.window="Window title"
|
AdvSceneSwitcher.tempVar.window.window="Window title"
|
||||||
AdvSceneSwitcher.tempVar.window.window.description="The window title of the matching window."
|
AdvSceneSwitcher.tempVar.window.window.description="The window title of the matching window."
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,17 @@ static bool sceneNameMatchesRegex(const OBSWeakSource &scene,
|
||||||
return regex.Matches(GetWeakSourceName(scene), pattern);
|
return regex.Matches(GetWeakSourceName(scene), pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
static OBSWeakSource getCurrentSceneHelper(bool useTransitionTargetScene)
|
static OBSWeakSource getCurrentSceneHelper(bool useTransitionTargetScene,
|
||||||
|
obs_weak_canvas_t *canvas)
|
||||||
{
|
{
|
||||||
|
if (!IsMainCanvas(canvas)) {
|
||||||
|
// An "active" scene doesn't necessarily have to be the current
|
||||||
|
// scene, but this is still the best guess for now without
|
||||||
|
// having more information about each canvas' specific frontend
|
||||||
|
// implementation
|
||||||
|
return GetActiveCanvasScene(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
if (useTransitionTargetScene) {
|
if (useTransitionTargetScene) {
|
||||||
auto current = obs_frontend_get_current_scene();
|
auto current = obs_frontend_get_current_scene();
|
||||||
auto weak = obs_source_get_weak_source(current);
|
auto weak = obs_source_get_weak_source(current);
|
||||||
|
|
@ -68,62 +77,119 @@ bool MacroConditionScene::CheckCondition()
|
||||||
_lastSceneChangeTime = GetLastSceneChangeTime();
|
_lastSceneChangeTime = GetLastSceneChangeTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto canvas = _scene.GetCanvas();
|
||||||
|
|
||||||
switch (_type) {
|
switch (_type) {
|
||||||
case Type::CURRENT: {
|
case Type::CURRENT: {
|
||||||
auto scene = getCurrentSceneHelper(_useTransitionTargetScene);
|
auto scene = getCurrentSceneHelper(_useTransitionTargetScene,
|
||||||
|
canvas);
|
||||||
SetVariableValue(GetWeakSourceName(scene));
|
SetVariableValue(GetWeakSourceName(scene));
|
||||||
SetTempVarValue("current", GetWeakSourceName(scene));
|
SetTempVarValue("current", GetWeakSourceName(scene));
|
||||||
return scene == _scene.GetScene(false);
|
SetTempVarValue("position",
|
||||||
|
std::to_string(GetIndexOfScene(canvas, scene)));
|
||||||
|
return scene && scene == _scene.GetScene(false);
|
||||||
}
|
}
|
||||||
case Type::PREVIOUS: {
|
case Type::PREVIOUS: {
|
||||||
|
if (!IsMainCanvas(canvas)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
auto scene = getPreviousSceneHelper(_useTransitionTargetScene);
|
auto scene = getPreviousSceneHelper(_useTransitionTargetScene);
|
||||||
|
if (!scene) {
|
||||||
|
// Scene was never switched
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
SetVariableValue(GetWeakSourceName(scene));
|
SetVariableValue(GetWeakSourceName(scene));
|
||||||
|
SetTempVarValue("position",
|
||||||
|
std::to_string(GetIndexOfScene(canvas, scene)));
|
||||||
SetTempVarValue("previous", GetWeakSourceName(scene));
|
SetTempVarValue("previous", GetWeakSourceName(scene));
|
||||||
return scene == _scene.GetScene(false);
|
return scene == _scene.GetScene(false);
|
||||||
}
|
}
|
||||||
case Type::PREVIEW: {
|
case Type::PREVIEW: {
|
||||||
|
if (!IsMainCanvas(canvas)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
OBSSourceAutoRelease source =
|
OBSSourceAutoRelease source =
|
||||||
obs_frontend_get_current_preview_scene();
|
obs_frontend_get_current_preview_scene();
|
||||||
OBSWeakSourceAutoRelease scene =
|
OBSWeakSourceAutoRelease scene =
|
||||||
obs_source_get_weak_source(source);
|
obs_source_get_weak_source(source);
|
||||||
SetVariableValue(GetWeakSourceName(scene));
|
SetVariableValue(GetWeakSourceName(scene));
|
||||||
SetTempVarValue("preview", GetWeakSourceName(scene));
|
SetTempVarValue("preview", GetWeakSourceName(scene));
|
||||||
return scene == _scene.GetScene(false);
|
SetTempVarValue("position", std::to_string(GetIndexOfScene(
|
||||||
|
canvas, scene.Get())));
|
||||||
|
return scene && scene == _scene.GetScene(false);
|
||||||
}
|
}
|
||||||
case Type::CHANGED:
|
case Type::CHANGED:
|
||||||
|
if (!IsMainCanvas(canvas)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
SetVariableValue(GetWeakSourceName(GetCurrentScene()));
|
SetVariableValue(GetWeakSourceName(GetCurrentScene()));
|
||||||
SetTempVarValue("current",
|
SetTempVarValue("current",
|
||||||
GetWeakSourceName(GetCurrentScene()));
|
GetWeakSourceName(GetCurrentScene()));
|
||||||
SetTempVarValue("previous",
|
SetTempVarValue("previous",
|
||||||
GetWeakSourceName(GetPreviousScene()));
|
GetWeakSourceName(GetPreviousScene()));
|
||||||
|
SetTempVarValue("position",
|
||||||
|
std::to_string(GetIndexOfScene(
|
||||||
|
canvas, GetCurrentScene())));
|
||||||
return sceneChanged;
|
return sceneChanged;
|
||||||
case Type::NOT_CHANGED:
|
case Type::NOT_CHANGED:
|
||||||
|
if (!IsMainCanvas(canvas)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
SetVariableValue(GetWeakSourceName(GetCurrentScene()));
|
SetVariableValue(GetWeakSourceName(GetCurrentScene()));
|
||||||
SetTempVarValue("current",
|
SetTempVarValue("current",
|
||||||
GetWeakSourceName(GetCurrentScene()));
|
GetWeakSourceName(GetCurrentScene()));
|
||||||
SetTempVarValue("previous",
|
SetTempVarValue("previous",
|
||||||
GetWeakSourceName(GetPreviousScene()));
|
GetWeakSourceName(GetPreviousScene()));
|
||||||
|
SetTempVarValue("position",
|
||||||
|
std::to_string(GetIndexOfScene(
|
||||||
|
canvas, GetCurrentScene())));
|
||||||
return !sceneChanged;
|
return !sceneChanged;
|
||||||
case Type::CURRENT_PATTERN: {
|
case Type::CURRENT_PATTERN: {
|
||||||
auto scene = getCurrentSceneHelper(_useTransitionTargetScene);
|
auto scene = getCurrentSceneHelper(_useTransitionTargetScene,
|
||||||
|
canvas);
|
||||||
SetVariableValue(GetWeakSourceName(scene));
|
SetVariableValue(GetWeakSourceName(scene));
|
||||||
SetTempVarValue("current", GetWeakSourceName(scene));
|
SetTempVarValue("current", GetWeakSourceName(scene));
|
||||||
return sceneNameMatchesRegex(scene, _regex, _pattern);
|
SetTempVarValue("position",
|
||||||
|
std::to_string(GetIndexOfScene(canvas, scene)));
|
||||||
|
return scene && sceneNameMatchesRegex(scene, _regex, _pattern);
|
||||||
}
|
}
|
||||||
case Type::PREVIOUS_PATTERN: {
|
case Type::PREVIOUS_PATTERN: {
|
||||||
|
if (!IsMainCanvas(canvas)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
auto scene = getPreviousSceneHelper(_useTransitionTargetScene);
|
auto scene = getPreviousSceneHelper(_useTransitionTargetScene);
|
||||||
|
if (!scene) {
|
||||||
|
// Scene was never switched
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
SetVariableValue(GetWeakSourceName(scene));
|
SetVariableValue(GetWeakSourceName(scene));
|
||||||
SetTempVarValue("previous", GetWeakSourceName(scene));
|
SetTempVarValue("previous", GetWeakSourceName(scene));
|
||||||
|
SetTempVarValue("position",
|
||||||
|
std::to_string(GetIndexOfScene(canvas, scene)));
|
||||||
return sceneNameMatchesRegex(scene, _regex, _pattern);
|
return sceneNameMatchesRegex(scene, _regex, _pattern);
|
||||||
}
|
}
|
||||||
case Type::PREVIEW_PATTERN: {
|
case Type::PREVIEW_PATTERN: {
|
||||||
|
if (!IsMainCanvas(canvas)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
OBSSourceAutoRelease source =
|
OBSSourceAutoRelease source =
|
||||||
obs_frontend_get_current_preview_scene();
|
obs_frontend_get_current_preview_scene();
|
||||||
OBSWeakSourceAutoRelease scene =
|
OBSWeakSourceAutoRelease scene =
|
||||||
obs_source_get_weak_source(source);
|
obs_source_get_weak_source(source);
|
||||||
SetVariableValue(GetWeakSourceName(scene));
|
SetVariableValue(GetWeakSourceName(scene));
|
||||||
SetTempVarValue("preview", GetWeakSourceName(scene));
|
SetTempVarValue("preview", GetWeakSourceName(scene));
|
||||||
return sceneNameMatchesRegex(scene.Get(), _regex, _pattern);
|
SetTempVarValue("position", std::to_string(GetIndexOfScene(
|
||||||
|
canvas, scene.Get())));
|
||||||
|
return scene &&
|
||||||
|
sceneNameMatchesRegex(scene.Get(), _regex, _pattern);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -214,6 +280,7 @@ std::string MacroConditionScene::GetShortDesc() const
|
||||||
void MacroConditionScene::SetupTempVars()
|
void MacroConditionScene::SetupTempVars()
|
||||||
{
|
{
|
||||||
MacroCondition::SetupTempVars();
|
MacroCondition::SetupTempVars();
|
||||||
|
|
||||||
switch (_type) {
|
switch (_type) {
|
||||||
case Type::CURRENT:
|
case Type::CURRENT:
|
||||||
case Type::CURRENT_PATTERN:
|
case Type::CURRENT_PATTERN:
|
||||||
|
|
@ -245,6 +312,9 @@ void MacroConditionScene::SetupTempVars()
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AddTempvar("position",
|
||||||
|
obs_module_text("AdvSceneSwitcher.tempVar.scene.position"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroConditionScene::SetType(const Type &type)
|
void MacroConditionScene::SetType(const Type &type)
|
||||||
|
|
@ -269,10 +339,13 @@ MacroConditionSceneEdit::MacroConditionSceneEdit(
|
||||||
_pattern(new QLineEdit()),
|
_pattern(new QLineEdit()),
|
||||||
_useTransitionTargetScene(new QCheckBox(obs_module_text(
|
_useTransitionTargetScene(new QCheckBox(obs_module_text(
|
||||||
"AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour"))),
|
"AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour"))),
|
||||||
_regex(new RegexConfigWidget(this, false))
|
_regex(new RegexConfigWidget(this, false)),
|
||||||
|
_canvasWarning(new QLabel(this))
|
||||||
{
|
{
|
||||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||||
|
QWidget::connect(_scenes, SIGNAL(CanvasChanged(const OBSWeakCanvas &)),
|
||||||
|
this, SLOT(CanvasChanged(const OBSWeakCanvas &)));
|
||||||
QWidget::connect(_sceneType, SIGNAL(currentIndexChanged(int)), this,
|
QWidget::connect(_sceneType, SIGNAL(currentIndexChanged(int)), this,
|
||||||
SLOT(TypeChanged(int)));
|
SLOT(TypeChanged(int)));
|
||||||
QWidget::connect(_pattern, SIGNAL(editingFinished()), this,
|
QWidget::connect(_pattern, SIGNAL(editingFinished()), this,
|
||||||
|
|
@ -285,7 +358,7 @@ MacroConditionSceneEdit::MacroConditionSceneEdit(
|
||||||
|
|
||||||
populateTypeSelection(_sceneType);
|
populateTypeSelection(_sceneType);
|
||||||
|
|
||||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||||
{"{{scenes}}", _scenes},
|
{"{{scenes}}", _scenes},
|
||||||
{"{{sceneType}}", _sceneType},
|
{"{{sceneType}}", _sceneType},
|
||||||
{"{{pattern}}", _pattern},
|
{"{{pattern}}", _pattern},
|
||||||
|
|
@ -303,6 +376,7 @@ MacroConditionSceneEdit::MacroConditionSceneEdit(
|
||||||
auto mainLayout = new QVBoxLayout;
|
auto mainLayout = new QVBoxLayout;
|
||||||
mainLayout->addLayout(line1Layout);
|
mainLayout->addLayout(line1Layout);
|
||||||
mainLayout->addLayout(line2Layout);
|
mainLayout->addLayout(line2Layout);
|
||||||
|
mainLayout->addWidget(_canvasWarning);
|
||||||
setLayout(mainLayout);
|
setLayout(mainLayout);
|
||||||
|
|
||||||
_entryData = entryData;
|
_entryData = entryData;
|
||||||
|
|
@ -344,41 +418,58 @@ void MacroConditionSceneEdit::RegexChanged(const RegexConfig ®ex)
|
||||||
_entryData->_regex = regex;
|
_entryData->_regex = regex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroConditionSceneEdit::CanvasChanged(const OBSWeakCanvas &)
|
||||||
|
{
|
||||||
|
SetWidgetVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
void MacroConditionSceneEdit::SetWidgetVisibility()
|
void MacroConditionSceneEdit::SetWidgetVisibility()
|
||||||
{
|
{
|
||||||
_scenes->setVisible(
|
const auto type = _entryData->GetType();
|
||||||
_entryData->GetType() == MacroConditionScene::Type::CURRENT ||
|
|
||||||
_entryData->GetType() == MacroConditionScene::Type::PREVIOUS ||
|
_scenes->setVisible(type == MacroConditionScene::Type::CURRENT ||
|
||||||
_entryData->GetType() == MacroConditionScene::Type::PREVIEW);
|
type == MacroConditionScene::Type::PREVIOUS ||
|
||||||
|
type == MacroConditionScene::Type::PREVIEW);
|
||||||
_useTransitionTargetScene->setVisible(
|
_useTransitionTargetScene->setVisible(
|
||||||
_entryData->GetType() == MacroConditionScene::Type::CURRENT ||
|
type == MacroConditionScene::Type::CURRENT ||
|
||||||
_entryData->GetType() == MacroConditionScene::Type::PREVIOUS ||
|
type == MacroConditionScene::Type::PREVIOUS ||
|
||||||
_entryData->GetType() ==
|
type == MacroConditionScene::Type::CURRENT_PATTERN ||
|
||||||
MacroConditionScene::Type::CURRENT_PATTERN ||
|
type == MacroConditionScene::Type::PREVIOUS_PATTERN);
|
||||||
_entryData->GetType() ==
|
|
||||||
MacroConditionScene::Type::PREVIOUS_PATTERN);
|
|
||||||
const bool isUsingPattern =
|
const bool isUsingPattern =
|
||||||
_entryData->GetType() ==
|
type == MacroConditionScene::Type::CURRENT_PATTERN ||
|
||||||
MacroConditionScene::Type::CURRENT_PATTERN ||
|
type == MacroConditionScene::Type::PREVIOUS_PATTERN ||
|
||||||
_entryData->GetType() ==
|
type == MacroConditionScene::Type::PREVIEW_PATTERN;
|
||||||
MacroConditionScene::Type::PREVIOUS_PATTERN ||
|
|
||||||
_entryData->GetType() ==
|
|
||||||
MacroConditionScene::Type::PREVIEW_PATTERN;
|
|
||||||
_pattern->setVisible(isUsingPattern);
|
_pattern->setVisible(isUsingPattern);
|
||||||
_regex->setVisible(isUsingPattern);
|
_regex->setVisible(isUsingPattern);
|
||||||
|
|
||||||
if (_entryData->GetType() == MacroConditionScene::Type::PREVIOUS ||
|
if (type == MacroConditionScene::Type::PREVIOUS ||
|
||||||
_entryData->GetType() ==
|
type == MacroConditionScene::Type::PREVIOUS_PATTERN) {
|
||||||
MacroConditionScene::Type::PREVIOUS_PATTERN) {
|
|
||||||
_useTransitionTargetScene->setText(obs_module_text(
|
_useTransitionTargetScene->setText(obs_module_text(
|
||||||
"AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour"));
|
"AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour"));
|
||||||
}
|
}
|
||||||
if (_entryData->GetType() == MacroConditionScene::Type::CURRENT ||
|
if (type == MacroConditionScene::Type::CURRENT ||
|
||||||
_entryData->GetType() ==
|
type == MacroConditionScene::Type::CURRENT_PATTERN) {
|
||||||
MacroConditionScene::Type::CURRENT_PATTERN) {
|
|
||||||
_useTransitionTargetScene->setText(obs_module_text(
|
_useTransitionTargetScene->setText(obs_module_text(
|
||||||
"AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour"));
|
"AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto canvas = _entryData->_scene.GetCanvas();
|
||||||
|
const bool isUsingMainCanvas = IsMainCanvas(canvas);
|
||||||
|
_useTransitionTargetScene->setVisible(isUsingMainCanvas);
|
||||||
|
|
||||||
|
const QString fmt = obs_module_text(
|
||||||
|
"AdvSceneSwitcher.condition.scene.canvasNotSupported");
|
||||||
|
_canvasWarning->setText(
|
||||||
|
fmt.arg(QString::fromStdString(GetWeakCanvasName(canvas))));
|
||||||
|
_canvasWarning->setVisible(
|
||||||
|
canvas && !isUsingMainCanvas &&
|
||||||
|
(type == MacroConditionScene::Type::PREVIOUS ||
|
||||||
|
type == MacroConditionScene::Type::PREVIEW ||
|
||||||
|
type == MacroConditionScene::Type::CHANGED ||
|
||||||
|
type == MacroConditionScene::Type::NOT_CHANGED ||
|
||||||
|
type == MacroConditionScene::Type::PREVIOUS_PATTERN ||
|
||||||
|
type == MacroConditionScene::Type::PREVIEW_PATTERN));
|
||||||
|
|
||||||
adjustSize();
|
adjustSize();
|
||||||
updateGeometry();
|
updateGeometry();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,19 +75,21 @@ private slots:
|
||||||
void PatternChanged();
|
void PatternChanged();
|
||||||
void UseTransitionTargetSceneChanged(int state);
|
void UseTransitionTargetSceneChanged(int state);
|
||||||
void RegexChanged(const RegexConfig &);
|
void RegexChanged(const RegexConfig &);
|
||||||
|
void CanvasChanged(const OBSWeakCanvas &);
|
||||||
signals:
|
signals:
|
||||||
void HeaderInfoChanged(const QString &);
|
void HeaderInfoChanged(const QString &);
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
|
void SetWidgetVisibility();
|
||||||
|
|
||||||
SceneSelectionWidget *_scenes;
|
SceneSelectionWidget *_scenes;
|
||||||
QComboBox *_sceneType;
|
QComboBox *_sceneType;
|
||||||
QLineEdit *_pattern;
|
QLineEdit *_pattern;
|
||||||
QCheckBox *_useTransitionTargetScene;
|
QCheckBox *_useTransitionTargetScene;
|
||||||
RegexConfigWidget *_regex;
|
RegexConfigWidget *_regex;
|
||||||
std::shared_ptr<MacroConditionScene> _entryData;
|
QLabel *_canvasWarning;
|
||||||
|
|
||||||
private:
|
std::shared_ptr<MacroConditionScene> _entryData;
|
||||||
void SetWidgetVisibility();
|
|
||||||
bool _loading = true;
|
bool _loading = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user