From 90dd59abe6e6bb0fe36b3a03589cd48594b806b1 Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Sun, 16 Apr 2023 18:19:39 +0200 Subject: [PATCH] Rework window condition Layout changes to make controlling the checkboxes less confusing. Added variable support for the winddow title selection. Previously window title matching was mandatory, which is now optional. This would make it difficult to set up condition which are independent of the window title. For example, a condition wich triggers on focus window change. Add support for configuring the regex options used for the window title options. Also adds the option to check the content displayed in a given window using the Windows API as an alternative to the OCR of the video condition. --- data/locale/en-US.ini | 11 +- src/linux/advanced-scene-switcher-nix.cpp | 6 + src/macro-core/macro-condition-window.cpp | 297 ++++++++++++++++++---- src/macro-core/macro-condition-window.hpp | 30 ++- src/osx/advanced-scene-switcher-osx.mm | 6 + src/platform-funcs.hpp | 2 + src/win/advanced-scene-switcher-win.cpp | 91 ++++++- 7 files changed, 378 insertions(+), 65 deletions(-) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 0560e127..8cc11eb1 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -161,9 +161,14 @@ AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="During transi AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}" AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}" AdvSceneSwitcher.condition.window="Window" -AdvSceneSwitcher.condition.window.entry.line1="{{windows}} exist and ..." -AdvSceneSwitcher.condition.window.entry.line2="... is {{fullscreen}} fullscreen {{maximized}} maximized {{focused}} focused {{windowFocusChanged}} foreground window changed" -AdvSceneSwitcher.condition.window.entry.line3="Current foreground window: {{focusWindow}}" +AdvSceneSwitcher.condition.window.entry.window="{{checkTitle}}Window title matches{{windowRegex}}{{windows}}" +AdvSceneSwitcher.condition.window.entry.fullscreen="{{fullscreen}}Window is fullscreen" +AdvSceneSwitcher.condition.window.entry.maximized="{{maximized}}Window is maximized" +AdvSceneSwitcher.condition.window.entry.focused="{{focused}}Window is focused" +AdvSceneSwitcher.condition.window.entry.focusedChange="{{windowFocusChanged}}Focus window changed" +AdvSceneSwitcher.condition.window.entry.text="{{checkText}}Window contains text{{textRegex}}{{windowText}}" +AdvSceneSwitcher.condition.window.entry.text.note="This option might not work on every text being displayed in a window.\nIf that is the case, you can consider using the Video conditions OCR check instead." +AdvSceneSwitcher.condition.window.entry.currentFocus="Current focus window:{{focusWindow}}" AdvSceneSwitcher.condition.file="File" AdvSceneSwitcher.condition.file.type.match="matches" AdvSceneSwitcher.condition.file.type.contentChange="content changed" diff --git a/src/linux/advanced-scene-switcher-nix.cpp b/src/linux/advanced-scene-switcher-nix.cpp index 08245660..53169405 100644 --- a/src/linux/advanced-scene-switcher-nix.cpp +++ b/src/linux/advanced-scene-switcher-nix.cpp @@ -332,6 +332,12 @@ bool IsFullscreen(const std::string &title) return windowStatesAreSet(title, states); } +std::optional GetTextInWindow(const std::string &window) +{ + // Not implemented + return {}; +} + #ifdef USE_PROCPS void GetProcessList(QStringList &processes) { diff --git a/src/macro-core/macro-condition-window.cpp b/src/macro-core/macro-condition-window.cpp index 5e2cef66..3342b4c4 100644 --- a/src/macro-core/macro-condition-window.cpp +++ b/src/macro-core/macro-condition-window.cpp @@ -14,41 +14,70 @@ bool MacroConditionWindow::_registered = MacroConditionFactory::Register( {MacroConditionWindow::Create, MacroConditionWindowEdit::Create, "AdvSceneSwitcher.condition.window"}); -bool MacroConditionWindow::CheckWindowTitleSwitchDirect( - const std::string ¤tWindowTitle) +static bool matchRegex(const RegexConfig &conf, const std::string &msg, + const std::string &expr) { - bool focus = (!_focus || _window == currentWindowTitle); - bool fullscreen = (!_fullscreen || IsFullscreen(_window)); - bool max = (!_maximized || IsMaximized(_window)); - - return focus && fullscreen && max; + auto regex = conf.GetRegularExpression(expr); + if (!regex.isValid()) { + return false; + } + auto match = regex.match(QString::fromStdString(msg)); + return match.hasMatch(); } -bool MacroConditionWindow::CheckWindowTitleSwitchRegex( - const std::string ¤tWindowTitle, - const std::vector &windowList) +static bool windowContainsText(const std::string &window, + const std::string &matchText, + const RegexConfig &conf) { - bool match = false; - for (auto &window : windowList) { - try { - std::regex expr(_window); - if (!std::regex_match(window, expr)) { - continue; - } - } catch (const std::regex_error &) { - } - - bool focus = (!_focus || window == currentWindowTitle); - bool fullscreen = (!_fullscreen || IsFullscreen(window)); - bool max = (!_maximized || IsMaximized(window)); - - if (focus && fullscreen && max) { - match = true; - break; - } + auto text = GetTextInWindow(window); + if (!text.has_value()) { + return false; } - return match; + if (conf.Enabled()) { + return matchRegex(conf, *text, matchText); + } + return text == matchText; +} + +bool MacroConditionWindow::WindowMatches(const std::string &window) +{ + const bool focusCheckOK = (!_focus || window == switcher->currentTitle); + if (!focusCheckOK) { + return false; + } + const bool fullscreenCheckOK = (!_fullscreen || IsFullscreen(window)); + if (!fullscreenCheckOK) { + return false; + } + const bool maxCheckOK = (!_maximized || IsMaximized(window)); + if (!maxCheckOK) { + return false; + } + const bool textCheckOK = + (!_checkText || windowContainsText(window, _text, _textRegex)); + if (!textCheckOK) { + return false; + } + + if (_checkText) { + auto text = GetTextInWindow(window); + SetVariableValue(text.value_or("")); + } + + return true; +} + +bool MacroConditionWindow::WindowRegexMatches( + const std::vector &windowList) +{ + for (const auto &window : windowList) { + if (matchRegex(_windowRegex, window, _window) && + WindowMatches(window)) { + return true; + } + } + return false; } static bool foregroundWindowChanged() @@ -58,19 +87,19 @@ static bool foregroundWindowChanged() bool MacroConditionWindow::CheckCondition() { - const std::string ¤tWindowTitle = switcher->currentTitle; - SetVariableValue(currentWindowTitle); + SetVariableValue(""); + if (!_checkText) { + SetVariableValue(switcher->currentTitle); + } std::vector windowList; GetWindowList(windowList); bool match = false; - if (std::find(windowList.begin(), windowList.end(), _window) != - windowList.end()) { - match = CheckWindowTitleSwitchDirect(currentWindowTitle); + if (_windowRegex.Enabled()) { + match = WindowRegexMatches(windowList); } else { - match = CheckWindowTitleSwitchRegex(currentWindowTitle, - windowList); + match = WindowMatches(_window); } match = match && (!_windowFocusChanged || foregroundWindowChanged()); return match; @@ -79,22 +108,42 @@ bool MacroConditionWindow::CheckCondition() bool MacroConditionWindow::Save(obs_data_t *obj) const { MacroCondition::Save(obj); - obs_data_set_string(obj, "window", _window.c_str()); + obs_data_set_bool(obj, "checkTitle", _checkTitle); + _window.Save(obj, "window"); + _windowRegex.Save(obj, "windowRegexConfig"); obs_data_set_bool(obj, "fullscreen", _fullscreen); obs_data_set_bool(obj, "maximized", _maximized); obs_data_set_bool(obj, "focus", _focus); obs_data_set_bool(obj, "windowFocusChanged", _windowFocusChanged); + obs_data_set_bool(obj, "checkWindowText", _checkText); + _text.Save(obj, "text"); + _textRegex.Save(obj, "textRegexConfig"); + obs_data_set_int(obj, "version", 1); return true; } bool MacroConditionWindow::Load(obs_data_t *obj) { MacroCondition::Load(obj); - _window = obs_data_get_string(obj, "window"); + if (!obs_data_has_user_value(obj, "version")) { + _checkTitle = true; + _windowRegex.CreateBackwardsCompatibleRegex(true, true); + } else { + _checkTitle = obs_data_get_bool(obj, "checkTitle"); + _windowRegex.Load(obj, "windowRegexConfig"); + } + _window.Load(obj, "window"); _fullscreen = obs_data_get_bool(obj, "fullscreen"); _maximized = obs_data_get_bool(obj, "maximized"); _focus = obs_data_get_bool(obj, "focus"); _windowFocusChanged = obs_data_get_bool(obj, "windowFocusChanged"); +#ifdef _WIN32 + _checkText = obs_data_get_bool(obj, "checkWindowText"); +#else + _checkText = false; +#endif + _text.Load(obj, "text"); + _textRegex.Load(obj, "textRegexConfig"); return true; } @@ -107,19 +156,33 @@ MacroConditionWindowEdit::MacroConditionWindowEdit( QWidget *parent, std::shared_ptr entryData) : QWidget(parent), _windowSelection(new QComboBox()), + _windowRegex(new RegexConfigWidget(this)), + _checkTitle(new QCheckBox()), _fullscreen(new QCheckBox()), _maximized(new QCheckBox()), _focused(new QCheckBox()), _windowFocusChanged(new QCheckBox()), + _checkText(new QCheckBox()), + _text(new VariableTextEdit(this)), + _textRegex(new RegexConfigWidget(this)), _focusWindow(new QLabel()), - _focusLayout(new QHBoxLayout()) + _currentFocusLayout(new QHBoxLayout()) { _windowSelection->setEditable(true); _windowSelection->setMaxVisibleItems(20); + _checkText->setToolTip(obs_module_text( + "AdvSceneSwitcher.condition.window.entry.text.note")); + _text->setToolTip(obs_module_text( + "AdvSceneSwitcher.condition.window.entry.text.note")); + QWidget::connect(_windowSelection, SIGNAL(currentTextChanged(const QString &)), this, SLOT(WindowChanged(const QString &))); + QWidget::connect(_windowRegex, SIGNAL(RegexConfigChanged(RegexConfig)), + this, SLOT(WindowRegexChanged(RegexConfig))); + QWidget::connect(_checkTitle, SIGNAL(stateChanged(int)), this, + SLOT(CheckTitleChanged(int))); QWidget::connect(_fullscreen, SIGNAL(stateChanged(int)), this, SLOT(FullscreenChanged(int))); QWidget::connect(_maximized, SIGNAL(stateChanged(int)), this, @@ -128,6 +191,12 @@ MacroConditionWindowEdit::MacroConditionWindowEdit( SLOT(FocusedChanged(int))); QWidget::connect(_windowFocusChanged, SIGNAL(stateChanged(int)), this, SLOT(WindowFocusChanged(int))); + QWidget::connect(_checkText, SIGNAL(stateChanged(int)), this, + SLOT(CheckTextChanged(int))); + QWidget::connect(_text, SIGNAL(textChanged()), this, + SLOT(WindowTextChanged())); + QWidget::connect(_textRegex, SIGNAL(RegexConfigChanged(RegexConfig)), + this, SLOT(TextRegexChanged(RegexConfig))); QWidget::connect(&_timer, SIGNAL(timeout()), this, SLOT(UpdateFocusWindow())); @@ -135,28 +204,71 @@ MacroConditionWindowEdit::MacroConditionWindowEdit( std::unordered_map widgetPlaceholders = { {"{{windows}}", _windowSelection}, + {"{{windowRegex}}", _windowRegex}, + {"{{checkTitle}}", _checkTitle}, {"{{fullscreen}}", _fullscreen}, {"{{maximized}}", _maximized}, {"{{focused}}", _focused}, {"{{windowFocusChanged}}", _windowFocusChanged}, {"{{focusWindow}}", _focusWindow}, + {"{{checkText}}", _checkText}, + {"{{windowText}}", _text}, + {"{{textRegex}}", _textRegex}, }; - auto *line1Layout = new QHBoxLayout; + auto titleLayout = new QHBoxLayout; + titleLayout->setContentsMargins(0, 0, 0, 0); PlaceWidgets(obs_module_text( - "AdvSceneSwitcher.condition.window.entry.line1"), - line1Layout, widgetPlaceholders); - auto *line2Layout = new QHBoxLayout; + "AdvSceneSwitcher.condition.window.entry.window"), + titleLayout, widgetPlaceholders); + auto fullscreenLayout = new QHBoxLayout; + fullscreenLayout->setContentsMargins(0, 0, 0, 0); + PlaceWidgets( + obs_module_text( + "AdvSceneSwitcher.condition.window.entry.fullscreen"), + fullscreenLayout, widgetPlaceholders); + auto maximizedLayout = new QHBoxLayout; + maximizedLayout->setContentsMargins(0, 0, 0, 0); + PlaceWidgets( + obs_module_text( + "AdvSceneSwitcher.condition.window.entry.maximized"), + maximizedLayout, widgetPlaceholders); + auto focusedLayout = new QHBoxLayout; + focusedLayout->setContentsMargins(0, 0, 0, 0); PlaceWidgets(obs_module_text( - "AdvSceneSwitcher.condition.window.entry.line2"), - line2Layout, widgetPlaceholders); - PlaceWidgets(obs_module_text( - "AdvSceneSwitcher.condition.window.entry.line3"), - _focusLayout, widgetPlaceholders); - auto *mainLayout = new QVBoxLayout; - mainLayout->addLayout(line1Layout); - mainLayout->addLayout(line2Layout); - mainLayout->addLayout(_focusLayout); + "AdvSceneSwitcher.condition.window.entry.focused"), + focusedLayout, widgetPlaceholders); + auto focusChangedLayout = new QHBoxLayout; + focusChangedLayout->setContentsMargins(0, 0, 0, 0); + PlaceWidgets( + obs_module_text( + "AdvSceneSwitcher.condition.window.entry.focusedChange"), + focusChangedLayout, widgetPlaceholders); + auto textLayout = new QHBoxLayout; + textLayout->setContentsMargins(0, 0, 0, 0); + PlaceWidgets( + obs_module_text("AdvSceneSwitcher.condition.window.entry.text"), + textLayout, widgetPlaceholders); + _text->setSizePolicy(QSizePolicy::MinimumExpanding, + QSizePolicy::Preferred); + textLayout->setStretchFactor(_text, 10); + _currentFocusLayout->setContentsMargins(0, 0, 0, 0); + PlaceWidgets( + obs_module_text( + "AdvSceneSwitcher.condition.window.entry.currentFocus"), + _currentFocusLayout, widgetPlaceholders); + + auto mainLayout = new QVBoxLayout; + mainLayout->addLayout(titleLayout); + mainLayout->addLayout(fullscreenLayout); + mainLayout->addLayout(maximizedLayout); + mainLayout->addLayout(focusedLayout); + mainLayout->addLayout(focusChangedLayout); + mainLayout->addLayout(textLayout); +#ifndef _WIN32 + SetLayoutVisible(textLayout, false); +#endif + mainLayout->addLayout(_currentFocusLayout); setLayout(mainLayout); _entryData = entryData; @@ -178,6 +290,72 @@ void MacroConditionWindowEdit::WindowChanged(const QString &text) QString::fromStdString(_entryData->GetShortDesc())); } +void MacroConditionWindowEdit::WindowRegexChanged(RegexConfig conf) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_windowRegex = conf; + adjustSize(); + updateGeometry(); +} + +void MacroConditionWindowEdit::CheckTitleChanged(int state) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + const QSignalBlocker b1(_windowSelection); + const QSignalBlocker b2(_windowRegex); + if (!state) { + _entryData->_window = ".*"; + _entryData->_windowRegex.SetEnabled(true); + _windowSelection->setCurrentText(".*"); + _windowRegex->EnableChanged(true); + } + _entryData->_checkTitle = state; + SetWidgetVisibility(); +} + +void MacroConditionWindowEdit::CheckTextChanged(int state) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_checkText = state; + SetWidgetVisibility(); +} + +void MacroConditionWindowEdit::WindowTextChanged() +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_text = _text->toPlainText().toStdString(); + adjustSize(); + updateGeometry(); +} + +void MacroConditionWindowEdit::TextRegexChanged(RegexConfig conf) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_textRegex = conf; + adjustSize(); + updateGeometry(); +} + void MacroConditionWindowEdit::FullscreenChanged(int state) { if (_loading || !_entryData) { @@ -230,9 +408,14 @@ void MacroConditionWindowEdit::SetWidgetVisibility() if (!_entryData) { return; } - SetLayoutVisible(_focusLayout, + SetLayoutVisible(_currentFocusLayout, _entryData->_focus || _entryData->_windowFocusChanged); + _windowSelection->setVisible(_entryData->_checkTitle); + _windowRegex->setVisible(_entryData->_checkTitle); + _textRegex->setVisible(_entryData->_checkText); + _text->setVisible(_entryData->_checkText); adjustSize(); + updateGeometry(); } void MacroConditionWindowEdit::UpdateEntryData() @@ -241,11 +424,17 @@ void MacroConditionWindowEdit::UpdateEntryData() return; } - _windowSelection->setCurrentText(_entryData->_window.c_str()); + _windowSelection->setCurrentText( + _entryData->_window.UnresolvedValue().c_str()); + _windowRegex->SetRegexConfig(_entryData->_windowRegex); + _checkTitle->setChecked(_entryData->_checkTitle); _fullscreen->setChecked(_entryData->_fullscreen); _maximized->setChecked(_entryData->_maximized); _focused->setChecked(_entryData->_focus); _windowFocusChanged->setChecked(_entryData->_windowFocusChanged); + _checkText->setChecked(_entryData->_checkText); + _text->setPlainText(_entryData->_text); + _textRegex->SetRegexConfig(_entryData->_textRegex); SetWidgetVisibility(); } diff --git a/src/macro-core/macro-condition-window.hpp b/src/macro-core/macro-condition-window.hpp index c105381b..2eed1a9b 100644 --- a/src/macro-core/macro-condition-window.hpp +++ b/src/macro-core/macro-condition-window.hpp @@ -1,5 +1,7 @@ #pragma once #include "macro-condition-edit.hpp" +#include "variable-text-edit.hpp" +#include "regex-config.hpp" #include #include @@ -20,19 +22,23 @@ public: } private: - bool - CheckWindowTitleSwitchDirect(const std::string ¤tWindowTitle); - bool - CheckWindowTitleSwitchRegex(const std::string ¤tWindowTitle, - const std::vector &windowList); + bool WindowMatches(const std::string &window); + bool WindowRegexMatches(const std::vector &windowList); public: - std::string _window; + StringVariable _window; + RegexConfig _windowRegex; + bool _checkTitle = true; bool _fullscreen = false; bool _maximized = false; bool _focus = true; bool _windowFocusChanged = false; + // For now only supported on Windows + bool _checkText = false; + StringVariable _text; + RegexConfig _textRegex = RegexConfig::PartialMatchRegexConfig(); + private: static bool _registered; static const std::string id; @@ -56,22 +62,32 @@ public: private slots: void WindowChanged(const QString &text); + void WindowRegexChanged(RegexConfig); + void CheckTitleChanged(int state); void FullscreenChanged(int state); void MaximizedChanged(int state); void FocusedChanged(int state); void WindowFocusChanged(int state); + void CheckTextChanged(int state); + void WindowTextChanged(); + void TextRegexChanged(RegexConfig); void UpdateFocusWindow(); signals: void HeaderInfoChanged(const QString &); protected: QComboBox *_windowSelection; + RegexConfigWidget *_windowRegex; + QCheckBox *_checkTitle; QCheckBox *_fullscreen; QCheckBox *_maximized; QCheckBox *_focused; QCheckBox *_windowFocusChanged; + QCheckBox *_checkText; + VariableTextEdit *_text; + RegexConfigWidget *_textRegex; QLabel *_focusWindow; - QHBoxLayout *_focusLayout; + QHBoxLayout *_currentFocusLayout; QTimer _timer; std::shared_ptr _entryData; diff --git a/src/osx/advanced-scene-switcher-osx.mm b/src/osx/advanced-scene-switcher-osx.mm index 10f8a300..db4b4d89 100644 --- a/src/osx/advanced-scene-switcher-osx.mm +++ b/src/osx/advanced-scene-switcher-osx.mm @@ -255,6 +255,12 @@ bool IsFullscreen(const std::string &title) return false; } +std::optional GetTextInWindow(const std::string &window) +{ + // Not implemented + return {}; +} + int SecondsSinceLastInput() { double time = CGEventSourceSecondsSinceLastEventType( diff --git a/src/platform-funcs.hpp b/src/platform-funcs.hpp index e06723a1..194d2b3a 100644 --- a/src/platform-funcs.hpp +++ b/src/platform-funcs.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace advss { @@ -18,6 +19,7 @@ void GetWindowList(QStringList &windows); void GetCurrentWindowTitle(std::string &title); bool IsFullscreen(const std::string &title); bool IsMaximized(const std::string &title); +std::optional GetTextInWindow(const std::string &window); int SecondsSinceLastInput(); void GetProcessList(QStringList &processes); void GetForegroundProcessName(std::string &name); diff --git a/src/win/advanced-scene-switcher-win.cpp b/src/win/advanced-scene-switcher-win.cpp index dd456da2..af33a8eb 100644 --- a/src/win/advanced-scene-switcher-win.cpp +++ b/src/win/advanced-scene-switcher-win.cpp @@ -1,8 +1,8 @@ #include "platform-funcs.hpp" #include "hotkey.hpp" -#define WIN32_LEAN_AND_MEAN #include +#include #include #include #include @@ -178,6 +178,91 @@ bool IsMaximized(const std::string &title) return false; } +static std::wstring GetControlText(HWND hwnd, IUIAutomationElement *element) +{ + VARIANT var; + std::wstring text = L""; + HRESULT hr = element->GetCurrentPropertyValue(UIA_NamePropertyId, &var); + if (SUCCEEDED(hr)) { + if (var.vt == VT_BSTR && var.bstrVal) { + text = var.bstrVal; + } + VariantClear(&var); + } + + hr = element->GetCurrentPropertyValue(UIA_ValueValuePropertyId, &var); + if (SUCCEEDED(hr)) { + if (var.vt == VT_BSTR && var.bstrVal) { + if (!text.empty()) { + text += L"\n"; + } + text += +var.bstrVal; + } + VariantClear(&var); + } + + return text; +} + +std::optional GetTextInWindow(const std::string &window) +{ + HWND hwnd = getHWNDfromTitle(window); + if (!hwnd) { + return {}; + } + + IUIAutomation *automation = nullptr; + auto hr = CoCreateInstance(__uuidof(CUIAutomation), nullptr, + CLSCTX_INPROC_SERVER, + __uuidof(IUIAutomation), + reinterpret_cast(&automation)); + if (FAILED(hr)) { + return {}; + } + + IUIAutomationElement *rootElement = nullptr; + hr = automation->ElementFromHandle(hwnd, &rootElement); + if (FAILED(hr)) { + automation->Release(); + return {}; + } + + IUIAutomationTreeWalker *walker = nullptr; + hr = automation->get_ControlViewWalker(&walker); + if (FAILED(hr)) { + rootElement->Release(); + automation->Release(); + return {}; + } + + IUIAutomationElement *element = nullptr; + std::wstring result; + + hr = walker->GetFirstChildElement(rootElement, &element); + while (SUCCEEDED(hr) && element != nullptr) { + auto text = GetControlText(hwnd, element); + if (!text.empty()) { + result += text + L"\n"; + } + + IUIAutomationElement *nextElement = nullptr; + hr = walker->GetNextSiblingElement(element, &nextElement); + element->Release(); + element = nextElement; + } + + walker->Release(); + rootElement->Release(); + automation->Release(); + + // Convert to std::string + int len = os_wcs_to_utf8(result.c_str(), 0, nullptr, 0); + std::string tmp; + tmp.resize(len); + os_wcs_to_utf8(result.c_str(), 0, &tmp[0], len + 1); + return tmp; +} + bool IsFullscreen(const std::string &title) { RECT appBounds; @@ -539,6 +624,8 @@ static void SetupMouseEeventFilter() void PlatformInit() { SetupMouseEeventFilter(); + + CoInitialize(NULL); } void PlatformCleanup() @@ -547,6 +634,8 @@ void PlatformCleanup() delete mouseInputFilter; mouseInputFilter = nullptr; } + + CoUninitialize(); } } // namespace advss