diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 073c04736..83d642910 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -962,6 +962,12 @@ AdvSceneSwitcher.action.queue.type.start="Start" AdvSceneSwitcher.action.queue.type.stop="Stop" AdvSceneSwitcher.action.queue.entry.add="{{actions}}actions of macro{{macros}}to queue{{queues}}" AdvSceneSwitcher.action.queue.entry.other="{{actions}}queue{{queues}}" +AdvSceneSwitcher.action.window="Window" +AdvSceneSwitcher.action.window.type.setFocusWindow="Set focus window to" +AdvSceneSwitcher.action.window.type.maximizeWindow="Maximize window" +AdvSceneSwitcher.action.window.type.minimizeWindow="Minimize window" +AdvSceneSwitcher.action.window.type.closeWindow="Close window" +AdvSceneSwitcher.action.window.entry="{{actions}}{{windows}}{{regex}}" ; Hotkey AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher" diff --git a/plugins/base/CMakeLists.txt b/plugins/base/CMakeLists.txt index 29e8fff1c..ba2f4ee98 100644 --- a/plugins/base/CMakeLists.txt +++ b/plugins/base/CMakeLists.txt @@ -69,6 +69,8 @@ target_sources( macro-action-wait.hpp macro-action-websocket.cpp macro-action-websocket.hpp + macro-action-window.cpp + macro-action-window.hpp macro-condition-audio.cpp macro-condition-audio.hpp macro-condition-cursor.cpp diff --git a/plugins/base/macro-action-window.cpp b/plugins/base/macro-action-window.cpp new file mode 100644 index 000000000..5791ef699 --- /dev/null +++ b/plugins/base/macro-action-window.cpp @@ -0,0 +1,218 @@ +#include "macro-action-window.hpp" +#include "layout-helpers.hpp" +#include "platform-funcs.hpp" +#include "selection-helpers.hpp" + +namespace advss { + +const std::string MacroActionWindow::id = "window"; + +#ifdef _WIN32 +bool MacroActionWindow::_registered = MacroActionFactory::Register( + MacroActionWindow::id, + {MacroActionWindow::Create, MacroActionWindowEdit::Create, + "AdvSceneSwitcher.action.window"}); +#else +bool MacroActionWindow::_registered = false; +#endif // _WIN32 + +const static std::map<MacroActionWindow::Action, std::string> actionTypes = { + {MacroActionWindow::Action::SET_FOCUS_WINDOW, + "AdvSceneSwitcher.action.window.type.setFocusWindow"}, + {MacroActionWindow::Action::MAXIMIZE_WINDOW, + "AdvSceneSwitcher.action.window.type.maximizeWindow"}, + {MacroActionWindow::Action::MINIMIZE_WINDOW, + "AdvSceneSwitcher.action.window.type.minimizeWindow"}, + {MacroActionWindow::Action::CLOSE_WINDOW, + "AdvSceneSwitcher.action.window.type.closeWindow"}, +}; + +#ifdef _WIN32 +void SetFocusWindow(const std::string &); +void MaximizeWindow(const std::string &); +void MinimizeWindow(const std::string &); +void CloseWindow(const std::string &); +#else +void SetFocusWindow(const std::string &) {} +void MaximizeWindow(const std::string &) {} +void MinimizeWindow(const std::string &) {} +void CloseWindow(const std::string &) {} +#endif // _WIN32 + +std::optional<std::string> MacroActionWindow::GetMatchingWindow() const +{ + std::vector<std::string> windowList; + GetWindowList(windowList); + + if (!_regex.Enabled()) { + if (std::find(windowList.begin(), windowList.end(), + std::string(_window)) == windowList.end()) { + return {}; + } + return _window; + } + + for (const auto &window : windowList) { + if (_regex.Matches(window, _window)) { + return window; + } + } + + return {}; +} + +bool MacroActionWindow::PerformAction() +{ + auto window = GetMatchingWindow(); + if (!window) { + return true; + } + + switch (_action) { + case Action::SET_FOCUS_WINDOW: + SetFocusWindow(*window); + break; + case Action::MAXIMIZE_WINDOW: + MaximizeWindow(*window); + break; + case Action::MINIMIZE_WINDOW:; + MinimizeWindow(*window); + break; + case Action::CLOSE_WINDOW: + CloseWindow(*window); + break; + default: + break; + } + + return true; +} + +void MacroActionWindow::LogAction() const +{ + auto window = GetMatchingWindow(); + switch (_action) { + case Action::CLOSE_WINDOW: + blog(LOG_INFO, "close window \"%s\"", + window ? window->c_str() : "nothing matched"); + break; + case Action::SET_FOCUS_WINDOW: + blog(LOG_INFO, "set focus window \"%s\"", + window ? window->c_str() : "nothing matched"); + break; + default: + break; + } +} + +bool MacroActionWindow::Save(obs_data_t *obj) const +{ + MacroAction::Save(obj); + obs_data_set_int(obj, "action", static_cast<int>(_action)); + _window.Save(obj, "window"); + _regex.Save(obj, "regex"); + return true; +} + +bool MacroActionWindow::Load(obs_data_t *obj) +{ + MacroAction::Load(obj); + _action = static_cast<Action>(obs_data_get_int(obj, "action")); + _window.Load(obj, "window"); + _regex.Load(obj, "regex"); + return true; +} + +std::string MacroActionWindow::GetShortDesc() const +{ + return _window; +} + +static inline void populateActionSelection(QComboBox *list) +{ + for (const auto &[_, name] : actionTypes) { + list->addItem(obs_module_text(name.c_str())); + } +} + +MacroActionWindowEdit::MacroActionWindowEdit( + QWidget *parent, std::shared_ptr<MacroActionWindow> entryData) + : QWidget(parent), + _actions(new QComboBox()), + _windows(new QComboBox()), + _regex(new RegexConfigWidget(this)) +{ + populateActionSelection(_actions); + + _windows->setEditable(true); + _windows->setMaxVisibleItems(20); + PopulateWindowSelection(_windows); + + QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ActionChanged(int))); + QWidget::connect(_windows, SIGNAL(currentTextChanged(const QString &)), + this, SLOT(WindowChanged(const QString &))); + QWidget::connect(_regex, + SIGNAL(RegexConfigChanged(const RegexConfig &)), this, + SLOT(RegexChanged(const RegexConfig &))); + + auto layout = new QHBoxLayout; + PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.window.entry"), + layout, + {{"{{actions}}", _actions}, + {"{{windows}}", _windows}, + {"{{regex}}", _regex}}); + setLayout(layout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroActionWindowEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + _actions->setCurrentIndex(static_cast<int>(_entryData->_action)); + _windows->setCurrentText(QString::fromStdString(_entryData->_window)); + _regex->SetRegexConfig(_entryData->_regex); + adjustSize(); + updateGeometry(); +} + +void MacroActionWindowEdit::WindowChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_window = text.toStdString(); + emit HeaderInfoChanged( + QString::fromStdString(_entryData->GetShortDesc())); +} + +void MacroActionWindowEdit::RegexChanged(const RegexConfig ®ex) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_regex = regex; + adjustSize(); + updateGeometry(); +} + +void MacroActionWindowEdit::ActionChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_action = static_cast<MacroActionWindow::Action>(value); +} + +} // namespace advss diff --git a/plugins/base/macro-action-window.hpp b/plugins/base/macro-action-window.hpp new file mode 100644 index 000000000..ee8633b7b --- /dev/null +++ b/plugins/base/macro-action-window.hpp @@ -0,0 +1,71 @@ +#pragma once +#include "macro-action-edit.hpp" +#include "regex-config.hpp" +#include "variable-string.hpp" + +namespace advss { + +class MacroActionWindow : public MacroAction { +public: + MacroActionWindow(Macro *m) : MacroAction(m) {} + bool PerformAction(); + void LogAction() const; + 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<MacroAction> Create(Macro *m) + { + return std::make_shared<MacroActionWindow>(m); + } + + enum class Action { + SET_FOCUS_WINDOW, + MAXIMIZE_WINDOW, + MINIMIZE_WINDOW, + CLOSE_WINDOW, + }; + Action _action = Action::SET_FOCUS_WINDOW; + StringVariable _window; + RegexConfig _regex; + +private: + std::optional<std::string> GetMatchingWindow() const; + + static bool _registered; + static const std::string id; +}; + +class MacroActionWindowEdit : public QWidget { + Q_OBJECT + +public: + MacroActionWindowEdit( + QWidget *parent, + std::shared_ptr<MacroActionWindow> entryData = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr<MacroAction> action) + { + return new MacroActionWindowEdit( + parent, + std::dynamic_pointer_cast<MacroActionWindow>(action)); + } + +private slots: + void ActionChanged(int value); + void WindowChanged(const QString &text); + void RegexChanged(const RegexConfig &); +signals: + void HeaderInfoChanged(const QString &); + +private: + QComboBox *_actions; + QComboBox *_windows; + RegexConfigWidget *_regex; + + std::shared_ptr<MacroActionWindow> _entryData; + bool _loading = true; +}; + +} // namespace advss diff --git a/plugins/base/utils/windows/windows.cpp b/plugins/base/utils/windows/windows.cpp index e82515e38..c007b92b7 100644 --- a/plugins/base/utils/windows/windows.cpp +++ b/plugins/base/utils/windows/windows.cpp @@ -204,6 +204,45 @@ std::string GetWindowClassByWindowTitle(const std::string &window) return className; } +void SetFocusWindow(const std::string &title) +{ + auto handle = getHWNDfromTitle(title); + if (!handle) { + return; + } + ShowWindow(handle, SW_RESTORE); + SetForegroundWindow(handle); + SetFocus(handle); + SetActiveWindow(handle); +} + +void CloseWindow(const std::string &title) +{ + auto handle = getHWNDfromTitle(title); + if (!handle) { + return; + } + SendMessage(handle, WM_CLOSE, 0, 0); +} + +void MaximizeWindow(const std::string &title) +{ + auto handle = getHWNDfromTitle(title); + if (!handle) { + return; + } + ShowWindow(handle, SW_MAXIMIZE); +} + +void MinimizeWindow(const std::string &title) +{ + auto handle = getHWNDfromTitle(title); + if (!handle) { + return; + } + ShowWindow(handle, SW_MINIMIZE); +} + class RawMouseInputFilter; static RawMouseInputFilter *mouseInputFilter; static std::chrono::high_resolution_clock::time_point lastMouseLeftClickTime{};