diff --git a/CMakeLists.txt b/CMakeLists.txt
index 863266434..aed79fc6a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,6 +20,12 @@ if(BUILD_OUT_OF_TREE)
include(helpers)
endif()
+# OBS 31 no longer defines find_qt so check if we need to include it for in-tree
+# builds
+if(NOT COMMAND foo)
+ include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/helpers_common.cmake")
+endif()
+
set(LIB_NAME "${PROJECT_NAME}-lib")
add_library(${PROJECT_NAME} MODULE)
add_library(${LIB_NAME} SHARED)
diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini
index 8f8e28860..24c341dd2 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."
# Macro Actions
AdvSceneSwitcher.action.unknown="Unknown action"
@@ -1971,6 +1973,13 @@ AdvSceneSwitcher.tempVar.queue.size="Size"
AdvSceneSwitcher.tempVar.queue.running="Is running"
AdvSceneSwitcher.tempVar.queue.running.description="Returns \"true\" if the queue is started and \"false\" if it is stopped."
+AdvSceneSwitcher.tempVar.gameCapture.title="Title"
+AdvSceneSwitcher.tempVar.gameCapture.title.description="Window title of the application captured by the source."
+AdvSceneSwitcher.tempVar.gameCapture.title="Class"
+AdvSceneSwitcher.tempVar.gameCapture.title.description="Window class of the application captured by the source."
+AdvSceneSwitcher.tempVar.gameCapture.title="Executable"
+AdvSceneSwitcher.tempVar.gameCapture.title.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 f2ec114b0..81075217d 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 000000000..a21871fee
--- /dev/null
+++ b/plugins/base/macro-condition-game-capture.cpp
@@ -0,0 +1,150 @@
+#include "macro-condition-game-capture.hpp"
+#include "layout-helpers.hpp"
+
+namespace advss {
+
+const std::string MacroConditionGameCapture::id = "game_capture";
+
+bool MacroConditionGameCapture::_registered = MacroConditionFactory::Register(
+ MacroConditionGameCapture::id,
+ {MacroConditionGameCapture::Create,
+ MacroConditionGameCaptureEdit::Create,
+ "AdvSceneSwitcher.condition.gameCapture"});
+
+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);
+ return true;
+}
+
+void MacroConditionGameCapture::HookedSignalReceived(void *data, calldata_t *cd)
+{
+ auto condition = static_cast(data);
+ std::lock_guard lock(condition->_mtx);
+ if (!calldata_get_string(cd, "title", &condition->_title)) {
+ condition->_title = "";
+ }
+ if (!calldata_get_string(cd, "class", &condition->_class)) {
+ condition->_class = "";
+ }
+ if (!calldata_get_string(cd, "executable", &condition->_executable)) {
+ condition->_executable = "";
+ }
+ 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);
+ _hookSignal = OBSSignal(sh, "unhooked", UnhookedSignalReceived, this);
+}
+
+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 000000000..97af046a4
--- /dev/null
+++ b/plugins/base/macro-condition-game-capture.hpp
@@ -0,0 +1,71 @@
+#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);
+
+ obs_source_t *_lastSource = nullptr;
+ OBSSignal _hookSignal;
+
+ bool _hooked = false;
+ const char *_title = "";
+ const char *_class = "";
+ const char *_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