From 4e51b56b9b56ce0c90704a24e005f7a193cde9ff Mon Sep 17 00:00:00 2001 From: WarmUpTill <19472752+WarmUpTill@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:36:55 +0100 Subject: [PATCH] Add game capture condition --- data/locale/en-US.ini | 9 + plugins/base/CMakeLists.txt | 2 + plugins/base/macro-condition-game-capture.cpp | 197 ++++++++++++++++++ plugins/base/macro-condition-game-capture.hpp | 74 +++++++ 4 files changed, 282 insertions(+) create mode 100644 plugins/base/macro-condition-game-capture.cpp create mode 100644 plugins/base/macro-condition-game-capture.hpp diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 955bf139..315a4be0 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -769,6 +769,8 @@ AdvSceneSwitcher.condition.streamDeck.checkData="Check data field" AdvSceneSwitcher.condition.streamDeck.startListen="Start listening" AdvSceneSwitcher.condition.streamDeck.stopListen="Stop listening" AdvSceneSwitcher.condition.streamDeck.pluginDownload="

The Stream Deck plugin can be found here on GitHub.

" +AdvSceneSwitcher.condition.gameCapture="Game capture" +AdvSceneSwitcher.condition.gameCapture.entry="{{sources}}hooked a game." AdvSceneSwitcher.condition.screenshot="Screenshot" AdvSceneSwitcher.condition.screenshot.entry="A screenshot was taken" @@ -1980,6 +1982,13 @@ AdvSceneSwitcher.tempVar.queue.running.description="Returns \"true\" if the queu AdvSceneSwitcher.tempVar.screenshot.lastScreenshotPath="Last screenshot path" AdvSceneSwitcher.tempVar.screenshot.lastScreenshotPath.description="The filesystem path to the last screenshot taken." +AdvSceneSwitcher.tempVar.gameCapture.title="Title" +AdvSceneSwitcher.tempVar.gameCapture.title.description="Window title of the application captured by the source." +AdvSceneSwitcher.tempVar.gameCapture.class="Class" +AdvSceneSwitcher.tempVar.gameCapture.class.description="Window class of the application captured by the source." +AdvSceneSwitcher.tempVar.gameCapture.executable="Executable" +AdvSceneSwitcher.tempVar.gameCapture.executable.description="Executable name of the application captured by the source." + AdvSceneSwitcher.selectScene="--select scene--" AdvSceneSwitcher.selectPreviousScene="Previous Scene" AdvSceneSwitcher.selectCurrentScene="Current Scene" diff --git a/plugins/base/CMakeLists.txt b/plugins/base/CMakeLists.txt index 7997700d..a066ba8c 100644 --- a/plugins/base/CMakeLists.txt +++ b/plugins/base/CMakeLists.txt @@ -89,6 +89,8 @@ target_sources( macro-condition-filter.hpp macro-condition-folder.cpp macro-condition-folder.hpp + macro-condition-game-capture.cpp + macro-condition-game-capture.hpp macro-condition-hotkey.cpp macro-condition-hotkey.hpp macro-condition-idle.cpp diff --git a/plugins/base/macro-condition-game-capture.cpp b/plugins/base/macro-condition-game-capture.cpp new file mode 100644 index 00000000..f2593e61 --- /dev/null +++ b/plugins/base/macro-condition-game-capture.cpp @@ -0,0 +1,197 @@ +#include "macro-condition-game-capture.hpp" +#include "layout-helpers.hpp" + +namespace advss { + +const std::string MacroConditionGameCapture::id = "game_capture"; + +#ifdef _WIN32 +bool MacroConditionGameCapture::_registered = MacroConditionFactory::Register( + MacroConditionGameCapture::id, + {MacroConditionGameCapture::Create, + MacroConditionGameCaptureEdit::Create, + "AdvSceneSwitcher.condition.gameCapture"}); +#else +bool MacroConditionGameCapture::_registered = + false; // For now the 'game_capture' source only exists on Windows +#endif + +bool MacroConditionGameCapture::CheckCondition() +{ + auto source = OBSGetStrongRef(_source.GetSource()); + if (source != _lastSource) { + SetupSignalHandler(source); + return false; + } + + std::lock_guard lock(_mtx); + if (_hooked) { + SetTempVarValue("title", _title); + SetTempVarValue("class", _class); + SetTempVarValue("executable", _executable); + } + + return _hooked; +} + +bool MacroConditionGameCapture::Save(obs_data_t *obj) const +{ + MacroCondition::Save(obj); + _source.Save(obj); + return true; +} + +bool MacroConditionGameCapture::Load(obs_data_t *obj) +{ + MacroCondition::Load(obj); + _source.Load(obj); + SetupSignalHandler(OBSGetStrongRef(_source.GetSource())); + return true; +} + +void MacroConditionGameCapture::GetCalldataInfo(calldata_t *cd) +{ + const char *title = ""; + if (!calldata_get_string(cd, "title", &title)) { + blog(LOG_WARNING, "%s failed to get title", __func__); + } + _title = title; + const char *className = ""; + if (!calldata_get_string(cd, "class", &className)) { + blog(LOG_WARNING, "%s failed to get class", __func__); + } + _class = className; + const char *executable = ""; + if (!calldata_get_string(cd, "executable", &executable)) { + blog(LOG_WARNING, "%s failed to get executable", __func__); + } + _executable = executable; +} + +void MacroConditionGameCapture::HookedSignalReceived(void *data, calldata_t *cd) +{ + auto condition = static_cast(data); + std::lock_guard lock(condition->_mtx); + condition->GetCalldataInfo(cd); + condition->_hooked = true; +} + +void MacroConditionGameCapture::UnhookedSignalReceived(void *data, calldata_t *) +{ + auto condition = static_cast(data); + std::lock_guard lock(condition->_mtx); + condition->_hooked = false; +} + +void MacroConditionGameCapture::SetupTempVars() +{ + MacroCondition::SetupTempVars(); + AddTempvar( + "title", + obs_module_text("AdvSceneSwitcher.tempVar.gameCapture.title"), + obs_module_text( + "AdvSceneSwitcher.tempVar.gameCapture.title.description")); + AddTempvar( + "class", + obs_module_text("AdvSceneSwitcher.tempVar.gameCapture.class"), + obs_module_text( + "AdvSceneSwitcher.tempVar.gameCapture.class.description")); + AddTempvar( + "executable", + obs_module_text( + "AdvSceneSwitcher.tempVar.gameCapture.executable"), + obs_module_text( + "AdvSceneSwitcher.tempVar.gameCapture.executable.description")); +} + +void MacroConditionGameCapture::SetupSignalHandler(obs_source_t *source) +{ + signal_handler_t *sh = obs_source_get_signal_handler(source); + _hookSignal = OBSSignal(sh, "hooked", HookedSignalReceived, this); + _unhookSignal = OBSSignal(sh, "unhooked", UnhookedSignalReceived, this); + _lastSource = source; + + SetupInitialState(source); +} + +void MacroConditionGameCapture::SetupInitialState(obs_source_t *source) +{ + std::lock_guard lock(_mtx); + + _hooked = false; + if (!source) { + return; + } + + calldata_t cd; + calldata_init(&cd); + auto ph = obs_source_get_proc_handler(source); + if (!proc_handler_call(ph, "get_hooked", &cd)) { + blog(LOG_WARNING, + "%s failed to call proc_handler for 'get_hooked'", + __func__); + return; + } + + if (!calldata_get_bool(&cd, "hooked", &_hooked)) { + blog(LOG_WARNING, "%s failed to get hooked state", __func__); + } + GetCalldataInfo(&cd); + + calldata_free(&cd); +} + +static QStringList getGameCaptureSourceNames() +{ + auto sourceEnum = [](void *param, obs_source_t *source) -> bool /* -- */ + { + QStringList *list = reinterpret_cast(param); + + if (strcmp(obs_source_get_unversioned_id(source), + "game_capture") == 0) { + *list << obs_source_get_name(source); + } + return true; + }; + + QStringList list; + obs_enum_sources(sourceEnum, &list); + return list; +} + +MacroConditionGameCaptureEdit::MacroConditionGameCaptureEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent), + _sources(new SourceSelectionWidget(this, QStringList(), true)) +{ + auto sources = getGameCaptureSourceNames(); + sources.sort(); + _sources->SetSourceNameList(sources); + + QWidget::connect(_sources, + SIGNAL(SourceChanged(const SourceSelection &)), this, + SLOT(SourceChanged(const SourceSelection &))); + + auto layout = new QHBoxLayout; + PlaceWidgets( + obs_module_text("AdvSceneSwitcher.condition.gameCapture.entry"), + layout, {{"{{sources}}", _sources}}); + setLayout(layout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroConditionGameCaptureEdit::SourceChanged(const SourceSelection &source) +{ + GUARD_LOADING_AND_LOCK(); + _entryData->_source = source; +} + +void MacroConditionGameCaptureEdit::UpdateEntryData() +{ + _sources->SetSource(_entryData->_source); +} + +} // namespace advss diff --git a/plugins/base/macro-condition-game-capture.hpp b/plugins/base/macro-condition-game-capture.hpp new file mode 100644 index 00000000..f78076ae --- /dev/null +++ b/plugins/base/macro-condition-game-capture.hpp @@ -0,0 +1,74 @@ +#pragma once +#include "macro-condition-edit.hpp" +#include "source-selection.hpp" + +#include +#include + +namespace advss { + +class MacroConditionGameCapture : public MacroCondition { +public: + MacroConditionGameCapture(Macro *m) : MacroCondition(m) {} + bool CheckCondition(); + bool Save(obs_data_t *obj) const; + bool Load(obs_data_t *obj); + std::string GetId() const { return id; }; + static std::shared_ptr Create(Macro *m) + { + return std::make_shared(m); + } + + SourceSelection _source; + +private: + static void HookedSignalReceived(void *data, calldata_t *); + static void UnhookedSignalReceived(void *data, calldata_t *); + + void SetupTempVars(); + void SetupSignalHandler(obs_source_t *source); + void SetupInitialState(obs_source_t *source); + void GetCalldataInfo(calldata_t *cd); + + obs_source_t *_lastSource = nullptr; + OBSSignal _hookSignal; + OBSSignal _unhookSignal; + + bool _hooked = false; + std::string _title = ""; + std::string _class = ""; + std::string _executable = ""; + std::mutex _mtx; + + static bool _registered; + static const std::string id; +}; + +class MacroConditionGameCaptureEdit : public QWidget { + Q_OBJECT + +public: + MacroConditionGameCaptureEdit( + QWidget *parent, + std::shared_ptr cond = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr cond) + { + return new MacroConditionGameCaptureEdit( + parent, + std::dynamic_pointer_cast( + cond)); + } + +private slots: + void SourceChanged(const SourceSelection &); + +private: + SourceSelectionWidget *_sources; + std::shared_ptr _entryData; + + bool _loading = true; +}; + +} // namespace advss