From ae74f68db784be990a625791cedd6d1c1776dcbb Mon Sep 17 00:00:00 2001 From: WarmUpTill <19472752+WarmUpTill@users.noreply.github.com> Date: Fri, 21 Mar 2025 23:20:06 +0100 Subject: [PATCH] Add option to close projector windows --- data/locale/en-US.ini | 7 +- data/locale/fr-FR.ini | 2 - data/locale/ja-JP.ini | 2 - data/locale/pt-BR.ini | 2 - data/locale/zh-CN.ini | 2 - plugins/base/macro-action-projector.cpp | 174 ++++++++++++++++++++---- plugins/base/macro-action-projector.hpp | 21 ++- 7 files changed, 169 insertions(+), 41 deletions(-) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 4672802c..587080fe 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -1113,6 +1113,8 @@ AdvSceneSwitcher.action.variable.entry.userInput.customPrompt="{{useCustomPrompt AdvSceneSwitcher.action.variable.entry.userInput.placeholder="{{useInputPlaceholder}}Fill with placeholder{{inputPlaceholder}}" AdvSceneSwitcher.action.variable.entry.randomNumber="Generate random number in range from{{randomNumberStart}}to{{randomNumberEnd}}" AdvSceneSwitcher.action.projector="Projector" +AdvSceneSwitcher.action.projector.action.open="Open" +AdvSceneSwitcher.action.projector.action.close="Close" AdvSceneSwitcher.action.projector.type.source="Source" AdvSceneSwitcher.action.projector.type.scene="Scene" AdvSceneSwitcher.action.projector.type.preview="Preview" @@ -1121,8 +1123,9 @@ AdvSceneSwitcher.action.projector.type.multiview="Multiview" AdvSceneSwitcher.action.projector.display="Display" AdvSceneSwitcher.action.projector.windowed="Windowed" AdvSceneSwitcher.action.projector.fullscreen="Fullscreen" -AdvSceneSwitcher.action.projector.entry="Open{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}" -AdvSceneSwitcher.action.projector.entry.monitor="on{{monitors}}" +AdvSceneSwitcher.action.projector.entry.close="{{actions}}projector with name{{projectorWindowName}}{{regex}}" +AdvSceneSwitcher.action.projector.entry.open.windowed="{{actions}}{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}" +AdvSceneSwitcher.action.projector.entry.open.fullscreen="{{actions}}{{windowTypes}}projector of{{types}}{{scenes}}{{sources}}on{{monitors}}" AdvSceneSwitcher.action.midi="MIDI" AdvSceneSwitcher.action.midi.entry="Send message to{{device}}:" AdvSceneSwitcher.action.midi.entry.listen="Set MIDI message selection to messages incoming on{{listenDevices}}:{{listenButton}}" diff --git a/data/locale/fr-FR.ini b/data/locale/fr-FR.ini index e7de6e4d..d9d92cdc 100644 --- a/data/locale/fr-FR.ini +++ b/data/locale/fr-FR.ini @@ -718,8 +718,6 @@ AdvSceneSwitcher.action.projector.type.multiview="Multivue" AdvSceneSwitcher.action.projector.display="Affichage" AdvSceneSwitcher.action.projector.windowed="Fenêtré" AdvSceneSwitcher.action.projector.fullscreen="Plein écran" -AdvSceneSwitcher.action.projector.entry="Ouvrir le projecteur{{windowTypes}}de{{types}}{{scenes}}{{sources}}" -AdvSceneSwitcher.action.projector.entry.monitor="sur{{monitors}}" AdvSceneSwitcher.action.midi="MIDI" AdvSceneSwitcher.action.midi.entry="Envoyer un message à{{device}}:" AdvSceneSwitcher.action.midi.entry.listen="Définir la sélection de messages MIDI sur les messages entrants de{{listenDevices}}:{{listenButton}}" diff --git a/data/locale/ja-JP.ini b/data/locale/ja-JP.ini index b68ed098..6284bcdc 100644 --- a/data/locale/ja-JP.ini +++ b/data/locale/ja-JP.ini @@ -1068,8 +1068,6 @@ AdvSceneSwitcher.action.projector.type.multiview="マルチビュー" AdvSceneSwitcher.action.projector.display="ディスプレイ" AdvSceneSwitcher.action.projector.windowed="ウィンドウ" AdvSceneSwitcher.action.projector.fullscreen="フルスクリーン" -AdvSceneSwitcher.action.projector.entry="{{types}}{{scenes}}{{sources}}の{{windowTypes}}プロジェクターを開きます" -AdvSceneSwitcher.action.projector.entry.monitor="{{monitors}}上で" ; AdvSceneSwitcher.action.midi="MIDI" AdvSceneSwitcher.action.midi.entry="メッセージを{{device}}に送信します:" AdvSceneSwitcher.action.midi.entry.listen="MIDI メッセージの選択を {{listenDevices}} で受信するメッセージに設定します:{{listenButton}}" diff --git a/data/locale/pt-BR.ini b/data/locale/pt-BR.ini index ab6e01c3..d3db15b0 100644 --- a/data/locale/pt-BR.ini +++ b/data/locale/pt-BR.ini @@ -1049,8 +1049,6 @@ AdvSceneSwitcher.action.projector.type.multiview="Multivisão" AdvSceneSwitcher.action.projector.display="Exibição" AdvSceneSwitcher.action.projector.windowed="Janela" AdvSceneSwitcher.action.projector.fullscreen="Tela cheia" -AdvSceneSwitcher.action.projector.entry="Abrir{{windowTypes}}projetor de{{types}}{{scenes}}{{sources}}" -AdvSceneSwitcher.action.projector.entry.monitor="em{{monitors}}" AdvSceneSwitcher.action.midi="MIDI" AdvSceneSwitcher.action.midi.entry="Enviar mensagem para{{device}}:" AdvSceneSwitcher.action.midi.entry.listen="Definir seleção de mensagem MIDI para mensagens recebidas em{{listenDevices}}:{{listenButton}}" diff --git a/data/locale/zh-CN.ini b/data/locale/zh-CN.ini index cf0607aa..4819c894 100644 --- a/data/locale/zh-CN.ini +++ b/data/locale/zh-CN.ini @@ -644,8 +644,6 @@ AdvSceneSwitcher.action.projector.type.multiview="多视图" AdvSceneSwitcher.action.projector.display="显示" AdvSceneSwitcher.action.projector.windowed="窗口" AdvSceneSwitcher.action.projector.fullscreen="全屏" -AdvSceneSwitcher.action.projector.entry="打开为{{windowTypes}} 的 {{types}}{{scenes}}{{sources}}投影" -AdvSceneSwitcher.action.projector.entry.monitor="在 {{monitors}} 上 (从不咕咕的阿坤说:谨慎开启!)" AdvSceneSwitcher.action.midi="MIDI" AdvSceneSwitcher.action.midi.entry="发送消息到 {{device}}:" AdvSceneSwitcher.action.midi.entry.listen="将MIDI消息选择设置为 {{listenDevices}} 上传入的消息: {{listenButton}}" diff --git a/plugins/base/macro-action-projector.cpp b/plugins/base/macro-action-projector.cpp index ae1408a6..5732cfa6 100644 --- a/plugins/base/macro-action-projector.cpp +++ b/plugins/base/macro-action-projector.cpp @@ -5,6 +5,8 @@ #include "source-helpers.hpp" #include +#include +#include namespace advss { @@ -28,8 +30,34 @@ const static std::map selectionTypes = "AdvSceneSwitcher.action.projector.type.multiview"}, }; +static void closeOBSProjectorWindows(const std::string expectedWindowTitle, + const RegexConfig ®ex) +{ + for (QWindow *widget : QApplication::allWindows()) { + if (!widget->property("isOBSProjectorWindow").toBool()) { + continue; + } + + auto const windowTitle = widget->title().toStdString(); + if (!regex.Enabled() && expectedWindowTitle != windowTitle) { + continue; + } + + if (!regex.Matches(windowTitle, expectedWindowTitle)) { + continue; + } + + widget->close(); + } +} + bool MacroActionProjector::PerformAction() { + if (_action == Action::CLOSE) { + closeOBSProjectorWindows(_projectorWindowName, _regex); + return true; + } + std::string name = ""; const char *type = ""; @@ -78,10 +106,16 @@ bool MacroActionProjector::PerformAction() void MacroActionProjector::LogAction() const { + if (_action == Action::CLOSE) { + ablog(LOG_INFO, "closing projector window \"%s\"", + _projectorWindowName.c_str()); + return; + } + auto it = selectionTypes.find(_type); if (it != selectionTypes.end()) { ablog(LOG_INFO, - "performed projector action \"%s\" with" + "open projector \"%s\" with" "source \"%s\"," "scene \"%s\"," "monitor %d", @@ -96,24 +130,30 @@ void MacroActionProjector::LogAction() const bool MacroActionProjector::Save(obs_data_t *obj) const { MacroAction::Save(obj); + obs_data_set_int(obj, "action", static_cast(_action)); obs_data_set_int(obj, "type", static_cast(_type)); obs_data_set_int(obj, "monitor", _monitor); obs_data_set_string(obj, "monitorName", _monitorName.c_str()); obs_data_set_bool(obj, "fullscreen", _fullscreen); _scene.Save(obj); _source.Save(obj); + _projectorWindowName.Save(obj, "projectorWindowName"); + _regex.Save(obj); return true; } bool MacroActionProjector::Load(obs_data_t *obj) { MacroAction::Load(obj); + _action = static_cast(obs_data_get_int(obj, "action")); _type = static_cast(obs_data_get_int(obj, "type")); _monitor = obs_data_get_int(obj, "monitor"); _monitorName = obs_data_get_string(obj, "monitorName"); _fullscreen = obs_data_get_bool(obj, "fullscreen"); _scene.Load(obj); _source.Load(obj); + _projectorWindowName.Load(obj, "projectorWindowName"); + _regex.Load(obj); return true; } @@ -131,6 +171,7 @@ void MacroActionProjector::ResolveVariablesToFixedValues() { _source.ResolveVariables(); _scene.ResolveVariables(); + _projectorWindowName.ResolveVariables(); } void MacroActionProjector::SetMonitor(int idx) @@ -166,14 +207,22 @@ bool MacroActionProjector::MonitorSetupChanged() const QString::fromStdString(_monitorName); } -static inline void populateSelectionTypes(QComboBox *list) +static void populateActionSelection(QComboBox *list) { - for (auto entry : selectionTypes) { - list->addItem(obs_module_text(entry.second.c_str())); + list->addItem(obs_module_text( + "AdvSceneSwitcher.action.projector.action.open")); + list->addItem(obs_module_text( + "AdvSceneSwitcher.action.projector.action.close")); +} + +static void populateSelectionTypes(QComboBox *list) +{ + for (const auto &[_, name] : selectionTypes) { + list->addItem(obs_module_text(name.c_str())); } } -static inline void populateWindowTypes(QComboBox *list) +static void populateWindowTypes(QComboBox *list) { list->addItem( obs_module_text("AdvSceneSwitcher.action.projector.windowed")); @@ -184,14 +233,18 @@ static inline void populateWindowTypes(QComboBox *list) MacroActionProjectorEdit::MacroActionProjectorEdit( QWidget *parent, std::shared_ptr entryData) : QWidget(parent), - _windowTypes(new QComboBox()), + _actions(new QComboBox()), _types(new QComboBox()), + _windowTypes(new QComboBox()), _scenes(new SceneSelectionWidget(window(), true, false, true, true, true)), _sources(new SourceSelectionWidget(window(), QStringList(), true)), - _monitorSelection(new QHBoxLayout()), - _monitors(new QComboBox()) + _monitors(new QComboBox()), + _projectorWindowName(new VariableLineEdit(this)), + _regex(new RegexConfigWidget(this)), + _layout(new QHBoxLayout(this)) { + populateActionSelection(_actions); populateWindowTypes(_windowTypes); populateSelectionTypes(_types); auto sources = GetSourceNames(); @@ -201,6 +254,8 @@ MacroActionProjectorEdit::MacroActionProjectorEdit( _monitors->setPlaceholderText( obs_module_text("AdvSceneSwitcher.selectDisplay")); + QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ActionChanged(int))); QWidget::connect(_windowTypes, SIGNAL(currentIndexChanged(int)), this, SLOT(WindowTypeChanged(int))); QWidget::connect(_types, SIGNAL(currentIndexChanged(int)), this, @@ -212,24 +267,15 @@ MacroActionProjectorEdit::MacroActionProjectorEdit( SLOT(SourceChanged(const SourceSelection &))); QWidget::connect(_monitors, SIGNAL(currentIndexChanged(int)), this, SLOT(MonitorChanged(int))); - - std::unordered_map widgetPlaceholders = { - {"{{windowTypes}}", _windowTypes}, {"{{types}}", _types}, - {"{{scenes}}", _scenes}, {"{{sources}}", _sources}, - {"{{monitors}}", _monitors}, - }; - - PlaceWidgets(obs_module_text( - "AdvSceneSwitcher.action.projector.entry.monitor"), - _monitorSelection, widgetPlaceholders); - - QHBoxLayout *mainLayout = new QHBoxLayout; - PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.projector.entry"), - mainLayout, widgetPlaceholders); - mainLayout->insertLayout(mainLayout->count() - 1, _monitorSelection); - setLayout(mainLayout); + QWidget::connect(_projectorWindowName, SIGNAL(editingFinished()), this, + SLOT(ProjectorWindowNameChanged())); + QWidget::connect(_regex, + SIGNAL(RegexConfigChanged(const RegexConfig &)), this, + SLOT(RegexChanged(const RegexConfig &))); _entryData = entryData; + SetWidgetLayout(); + setLayout(_layout); UpdateEntryData(); _loading = false; } @@ -239,11 +285,15 @@ void MacroActionProjectorEdit::UpdateEntryData() if (!_entryData) { return; } + + _actions->setCurrentIndex(static_cast(_entryData->_action)); _windowTypes->setCurrentIndex(_entryData->_fullscreen ? 1 : 0); _types->setCurrentIndex(static_cast(_entryData->_type)); _scenes->SetScene(_entryData->_scene); _sources->SetSource(_entryData->_source); _monitors->setCurrentIndex(_entryData->GetMonitor()); + _projectorWindowName->setText(_entryData->_projectorWindowName); + _regex->SetRegexConfig(_entryData->_regex); SetWidgetVisibility(); } @@ -265,12 +315,26 @@ void MacroActionProjectorEdit::MonitorChanged(int value) _entryData->SetMonitor(value); } +void MacroActionProjectorEdit::ProjectorWindowNameChanged() +{ + GUARD_LOADING_AND_LOCK(); + _entryData->_projectorWindowName = + _projectorWindowName->text().toStdString(); +} + +void MacroActionProjectorEdit::RegexChanged(const RegexConfig ®ex) +{ + GUARD_LOADING_AND_LOCK(); + _entryData->_regex = regex; +} + void MacroActionProjectorEdit::WindowTypeChanged(int) { GUARD_LOADING_AND_LOCK(); _entryData->_fullscreen = _windowTypes->currentText() == obs_module_text("AdvSceneSwitcher.action.projector.fullscreen"); + SetWidgetLayout(); SetWidgetVisibility(); } @@ -281,17 +345,69 @@ void MacroActionProjectorEdit::TypeChanged(int value) SetWidgetVisibility(); } +void MacroActionProjectorEdit::ActionChanged(int idx) +{ + GUARD_LOADING_AND_LOCK(); + _entryData->_action = static_cast(idx); + SetWidgetLayout(); + SetWidgetVisibility(); +} + +void MacroActionProjectorEdit::SetWidgetLayout() +{ + const std::unordered_map widgetPlaceholders = { + {"{{actions}}", _actions}, + {"{{windowTypes}}", _windowTypes}, + {"{{types}}", _types}, + {"{{scenes}}", _scenes}, + {"{{sources}}", _sources}, + {"{{monitors}}", _monitors}, + {"{{projectorWindowName}}", _projectorWindowName}, + {"{{regex}}", _regex}, + }; + + for (const auto &[_, widget] : widgetPlaceholders) { + _layout->removeWidget(widget); + } + ClearLayout(_layout); + + const char *layoutText; + if (_entryData->_action == MacroActionProjector::Action::CLOSE) { + layoutText = "AdvSceneSwitcher.action.projector.entry.close"; + } else if (_entryData->_fullscreen) { + layoutText = + "AdvSceneSwitcher.action.projector.entry.open.fullscreen"; + } else { + layoutText = + "AdvSceneSwitcher.action.projector.entry.open.windowed"; + } + + PlaceWidgets(obs_module_text(layoutText), _layout, widgetPlaceholders); +} + void MacroActionProjectorEdit::SetWidgetVisibility() { if (!_entryData) { return; } - _scenes->setVisible(_entryData->_type == - MacroActionProjector::Type::SCENE); - _sources->setVisible(_entryData->_type == - MacroActionProjector::Type::SOURCE); - SetLayoutVisible(_monitorSelection, _entryData->_fullscreen); + _projectorWindowName->setVisible(_entryData->_action == + MacroActionProjector::Action::CLOSE); + _regex->setVisible(_entryData->_action == + MacroActionProjector::Action::CLOSE); + _types->setVisible(_entryData->_action == + MacroActionProjector::Action::OPEN); + _windowTypes->setVisible(_entryData->_action == + MacroActionProjector::Action::OPEN); + _scenes->setVisible( + _entryData->_action == MacroActionProjector::Action::OPEN && + _entryData->_type == MacroActionProjector::Type::SCENE); + _sources->setVisible( + _entryData->_action == MacroActionProjector::Action::OPEN && + _entryData->_type == MacroActionProjector::Type::SOURCE); + _monitors->setVisible(_entryData->_action == + MacroActionProjector::Action::OPEN && + _entryData->_fullscreen); adjustSize(); updateGeometry(); diff --git a/plugins/base/macro-action-projector.hpp b/plugins/base/macro-action-projector.hpp index 8e658258..c5138d5a 100644 --- a/plugins/base/macro-action-projector.hpp +++ b/plugins/base/macro-action-projector.hpp @@ -1,7 +1,9 @@ #pragma once #include "macro-action-edit.hpp" +#include "regex-config.hpp" #include "scene-selection.hpp" #include "source-selection.hpp" +#include "variable-line-edit.hpp" namespace advss { @@ -19,6 +21,11 @@ public: void SetMonitor(int); int GetMonitor() const; + enum class Action { + OPEN, + CLOSE, + }; + enum class Type { SOURCE, SCENE, @@ -27,10 +34,13 @@ public: MULTIVIEW, }; + Action _action = Action::OPEN; Type _type = Type::SCENE; SourceSelection _source; SceneSelection _scene; bool _fullscreen = true; + StringVariable _projectorWindowName = "Windowed Projector"; + RegexConfig _regex = RegexConfig::PartialMatchRegexConfig(true); private: bool MonitorSetupChanged() const; @@ -60,21 +70,28 @@ public: } private slots: + void ActionChanged(int value); void WindowTypeChanged(int value); void TypeChanged(int value); void SceneChanged(const SceneSelection &); void SourceChanged(const SourceSelection &); void MonitorChanged(int value); + void ProjectorWindowNameChanged(); + void RegexChanged(const RegexConfig &); private: + void SetWidgetLayout(); void SetWidgetVisibility(); - QComboBox *_windowTypes; + QComboBox *_actions; QComboBox *_types; + QComboBox *_windowTypes; SceneSelectionWidget *_scenes; SourceSelectionWidget *_sources; - QHBoxLayout *_monitorSelection; QComboBox *_monitors; + VariableLineEdit *_projectorWindowName; + RegexConfigWidget *_regex; + QHBoxLayout *_layout; std::shared_ptr _entryData; bool _loading = true;