Skip to content

Commit

Permalink
Add window action
Browse files Browse the repository at this point in the history
It allows to set the focus window and close windows.
Only supported on Windows for now.
  • Loading branch information
WarmUpTill committed Mar 2, 2024
1 parent 3a1c353 commit bf218e9
Show file tree
Hide file tree
Showing 5 changed files with 336 additions and 0 deletions.
6 changes: 6 additions & 0 deletions data/locale/en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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="Focus window"
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"
Expand Down
2 changes: 2 additions & 0 deletions plugins/base/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
218 changes: 218 additions & 0 deletions plugins/base/macro-action-window.cpp
Original file line number Diff line number Diff line change
@@ -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 &regex)
{
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
71 changes: 71 additions & 0 deletions plugins/base/macro-action-window.hpp
Original file line number Diff line number Diff line change
@@ -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
39 changes: 39 additions & 0 deletions plugins/base/utils/windows/windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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{};
Expand Down

0 comments on commit bf218e9

Please sign in to comment.