Refactor window platform helper functions

This commit is contained in:
WarmUpTill 2026-05-18 19:35:41 +02:00
parent ee1d0136d9
commit 930e8b38d4
10 changed files with 388 additions and 470 deletions

View File

@ -178,47 +178,19 @@ void AdvSceneSwitcher::on_ignoreWindows_currentRowChanged(int idx)
}
}
void checkWindowTitleSwitchDirect(WindowSwitch &s,
std::string &currentWindowTitle, bool &match,
OBSWeakSource &scene,
OBSWeakSource &transition)
static bool windowInfoMatchesSwitch(const WindowInfo &info,
const WindowSwitch &s)
{
bool focus = (!s.focus || s.window == currentWindowTitle);
bool fullscreen = (!s.fullscreen || IsFullscreen(s.window));
bool max = (!s.maximized || IsMaximized(s.window));
if (focus && fullscreen && max) {
match = true;
scene = s.getScene();
transition = s.transition;
if (s.focus && !info.focused) {
return false;
}
}
void checkWindowTitleSwitchRegex(WindowSwitch &s,
std::string &currentWindowTitle,
std::vector<std::string> windowList,
bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
for (auto &window : windowList) {
try {
std::regex expr(s.window);
if (!std::regex_match(window, expr)) {
continue;
}
} catch (const std::regex_error &) {
}
bool focus = (!s.focus || window == currentWindowTitle);
bool fullscreen = (!s.fullscreen || IsFullscreen(window));
bool max = (!s.maximized || IsMaximized(window));
if (focus && fullscreen && max) {
match = true;
scene = s.getScene();
transition = s.transition;
}
if (s.fullscreen && !info.fullscreen) {
return false;
}
if (s.maximized && !info.maximized) {
return false;
}
return true;
}
bool SwitcherData::checkWindowTitleSwitch(OBSWeakSource &scene,
@ -228,23 +200,37 @@ bool SwitcherData::checkWindowTitleSwitch(OBSWeakSource &scene,
return false;
}
std::string currentWindowTitle = switcher->currentTitle;
bool match = false;
const auto windowList = GetWindowList();
WindowQueryOptions options;
options.focus = true;
options.fullscreen = true;
options.maximized = true;
const auto windows = GetWindows(options);
for (WindowSwitch &s : windowSwitches) {
if (!s.initialized()) {
continue;
}
if (std::find(windowList.begin(), windowList.end(), s.window) !=
windowList.end()) {
checkWindowTitleSwitchDirect(s, currentWindowTitle,
match, scene, transition);
} else {
checkWindowTitleSwitchRegex(s, currentWindowTitle,
windowList, match, scene,
transition);
for (const auto &info : windows) {
bool titleMatch = false;
try {
std::regex expr(s.window);
titleMatch = info.title == s.window ||
std::regex_match(info.title, expr);
} catch (const std::regex_error &) {
titleMatch = info.title == s.window;
}
if (!titleMatch || !windowInfoMatchesSwitch(info, s)) {
continue;
}
match = true;
scene = s.getScene();
transition = s.transition;
break;
}
if (match) {

View File

@ -236,17 +236,65 @@ std::string getWindowName(Window window)
return windowTitle;
}
std::vector<std::string> GetWindowList()
std::vector<WindowInfo> GetWindows(const WindowQueryOptions &options)
{
std::vector<std::string> windows;
for (auto window : getTopLevelWindows()) {
const std::string foregroundTitle = GetCurrentWindowTitle();
const auto topLevel = getTopLevelWindows();
auto display = disp();
std::vector<WindowInfo> result;
result.reserve(topLevel.size());
for (auto window : topLevel) {
auto name = getWindowName(window);
if (name.empty()) {
continue;
}
windows.emplace_back(name);
WindowInfo info;
info.title = name;
if (options.focus) {
info.focused = (name == foregroundTitle);
}
if (options.fullscreen || options.maximized) {
QStringList states = getStates(window);
if (options.fullscreen) {
info.fullscreen = states.contains(
"_NET_WM_STATE_FULLSCREEN");
}
if (options.maximized) {
info.maximized =
states.contains(
"_NET_WM_STATE_MAXIMIZED_VERT") &&
states.contains(
"_NET_WM_STATE_MAXIMIZED_HORZ");
}
}
if (options.geometry && display) {
XWindowAttributes attrs;
if (XGetWindowAttributes(display, window, &attrs)) {
int x = 0;
int y = 0;
Window child;
XTranslateCoordinates(
display, window,
DefaultRootWindow(display), 0, 0, &x,
&y, &child);
info.x = x;
info.y = y;
info.width = attrs.width;
info.height = attrs.height;
}
}
// windowClass and text not implemented on Linux
result.emplace_back(std::move(info));
}
return windows;
return result;
}
int getActiveWindow(Window *&window)
@ -290,103 +338,6 @@ std::string GetCurrentWindowTitle()
return name;
}
bool windowStatesAreSet(const std::string &windowTitle,
std::vector<QString> &expectedStates)
{
if (!ewmhIsSupported()) {
return false;
}
std::vector<Window> windows = getTopLevelWindows();
for (auto &window : windows) {
auto name = getWindowName(window);
if (name.empty()) {
continue;
}
bool equals = windowTitle == name;
bool matches = QString::fromStdString(name).contains(
QRegularExpression(
QString::fromStdString(windowTitle)));
if (!(equals || matches)) {
continue;
}
QStringList states = getStates(window);
if (states.isEmpty()) {
if (expectedStates.empty()) {
return true;
}
return false;
}
for (const auto &state : expectedStates) {
if (!states.contains(state)) {
return false;
}
}
return true;
}
return false;
}
bool IsMaximized(const std::string &title)
{
std::vector<QString> states;
states.emplace_back("_NET_WM_STATE_MAXIMIZED_VERT");
states.emplace_back("_NET_WM_STATE_MAXIMIZED_HORZ");
return windowStatesAreSet(title, states);
}
bool IsFullscreen(const std::string &title)
{
std::vector<QString> states;
states.emplace_back("_NET_WM_STATE_FULLSCREEN");
return windowStatesAreSet(title, states);
}
std::optional<std::string> GetTextInWindow(const std::string &)
{
// Not implemented
return {};
}
std::optional<WindowGeometry> GetWindowGeometry(const std::string &title)
{
auto display = disp();
if (!display) {
return {};
}
for (auto window : getTopLevelWindows()) {
if (getWindowName(window) != title) {
continue;
}
XWindowAttributes attrs;
if (!XGetWindowAttributes(display, window, &attrs)) {
return {};
}
int x = 0;
int y = 0;
Window child;
XTranslateCoordinates(display, window,
DefaultRootWindow(display), 0, 0, &x, &y,
&child);
WindowGeometry geo;
geo.x = x;
geo.y = y;
geo.width = attrs.width;
geo.height = attrs.height;
return geo;
}
return {};
}
static void getProcessListProcps(QStringList &processes)
{
#ifdef PROCPS_AVAILABLE

View File

@ -14,41 +14,13 @@
namespace advss {
std::vector<std::string> GetWindowList()
static std::string nsStringToStdString(NSString *str)
{
std::vector<std::string> windows;
@autoreleasepool {
CFArrayRef cfApps = CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSMutableArray *apps = (__bridge NSMutableArray *)cfApps;
for (NSDictionary *app in apps) {
std::string name([[app objectForKey:@"kCGWindowName"]
UTF8String],
[[app objectForKey:@"kCGWindowName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
std::string owner(
[[app objectForKey:@"kCGWindowOwnerName"]
UTF8String],
[[app objectForKey:@"kCGWindowOwnerName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
if (!name.empty() &&
find(windows.begin(), windows.end(), name) ==
windows.end()) {
windows.emplace_back(name);
}
if (!owner.empty() &&
find(windows.begin(), windows.end(), owner) ==
windows.end()) {
windows.emplace_back(owner);
}
}
apps = nil;
CFRelease(cfApps);
if (!str) {
return "";
}
return windows;
const char *utf8 = [str UTF8String];
return utf8 ? utf8 : "";
}
std::string GetCurrentWindowTitle()
@ -148,54 +120,6 @@ bool isWindowMaximizedOnScreen(NSDictionary *app, NSScreen *screen)
maximizedTolerance;
}
bool nameMachesPattern(std::string windowName, std::string pattern)
{
return QString::fromStdString(windowName)
.contains(QRegularExpression(QString::fromStdString(pattern)));
}
bool IsMaximized(const std::string &title)
{
@autoreleasepool {
NSArray *screens = [NSScreen screens];
CFArrayRef cfApps = CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSMutableArray *apps = (__bridge NSMutableArray *)cfApps;
for (NSDictionary *app in apps) {
std::string name([[app objectForKey:@"kCGWindowName"]
UTF8String],
[[app objectForKey:@"kCGWindowName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
std::string owner(
[[app objectForKey:@"kCGWindowOwnerName"]
UTF8String],
[[app objectForKey:@"kCGWindowOwnerName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
bool equals = (title == name || title == owner);
bool matches = nameMachesPattern(name, title) ||
nameMachesPattern(owner, title);
if (equals || matches) {
for (NSScreen *screen in screens) {
if (isWindowOriginOnScreen(app,
screen) &&
isWindowMaximizedOnScreen(app,
screen)) {
apps = nil;
CFRelease(cfApps);
return true;
}
}
}
}
apps = nil;
CFRelease(cfApps);
}
return false;
}
bool isWindowFullscreenOnScreen(NSDictionary *app, NSScreen *screen)
{
NSRect screenFrame = [screen frame];
@ -207,52 +131,100 @@ bool isWindowFullscreenOnScreen(NSDictionary *app, NSScreen *screen)
return NSEqualSizes(windowBounds.size, screenFrame.size);
}
bool IsFullscreen(const std::string &title)
std::vector<WindowInfo> GetWindows(const WindowQueryOptions &options)
{
std::vector<WindowInfo> result;
const std::string foregroundTitle = GetCurrentWindowTitle();
@autoreleasepool {
NSArray *screens = [NSScreen screens];
CFArrayRef cfApps = CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSMutableArray *apps = (__bridge NSMutableArray *)cfApps;
for (NSDictionary *app in apps) {
std::string name([[app objectForKey:@"kCGWindowName"]
UTF8String],
[[app objectForKey:@"kCGWindowName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
std::string owner(
[[app objectForKey:@"kCGWindowOwnerName"]
UTF8String],
[[app objectForKey:@"kCGWindowOwnerName"]
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
bool equals = (title == name || title == owner);
bool matches = nameMachesPattern(name, title) ||
nameMachesPattern(owner, title);
if (equals || matches) {
for (NSScreen *screen in screens) {
if (isWindowOriginOnScreen(app, screen,
true) &&
isWindowFullscreenOnScreen(
app, screen)) {
apps = nil;
CFRelease(cfApps);
return true;
// Track titles already added to avoid duplicates (name + owner)
std::vector<std::string> seen;
for (NSDictionary *app in apps) {
std::string name = nsStringToStdString(
[app objectForKey:@"kCGWindowName"]);
std::string owner = nsStringToStdString(
[app objectForKey:@"kCGWindowOwnerName"]);
// Collect geometry once (used for multiple fields)
NSRect bounds = NSZeroRect;
if (options.geometry || options.fullscreen ||
options.maximized) {
CGRectMakeWithDictionaryRepresentation(
(CFDictionaryRef)[app
objectForKey:@"kCGWindowBounds"],
&bounds);
}
// Process both the window name and the owner name as
// separate entries, matching the old GetWindowList() behaviour
for (int pass = 0; pass < 2; ++pass) {
const std::string &title = (pass == 0) ? name
: owner;
if (title.empty()) {
continue;
}
if (std::find(seen.begin(), seen.end(),
title) != seen.end()) {
continue;
}
seen.emplace_back(title);
WindowInfo info;
info.title = title;
if (options.focus) {
info.focused =
(title == foregroundTitle);
}
if (options.geometry || options.fullscreen ||
options.maximized) {
info.x = (int)bounds.origin.x;
info.y = (int)bounds.origin.y;
info.width = (int)bounds.size.width;
info.height = (int)bounds.size.height;
}
if (options.fullscreen) {
for (NSScreen *screen in screens) {
if (isWindowOriginOnScreen(
app, screen,
true) &&
isWindowFullscreenOnScreen(
app, screen)) {
info.fullscreen = true;
break;
}
}
}
if (options.maximized) {
for (NSScreen *screen in screens) {
if (isWindowOriginOnScreen(
app, screen) &&
isWindowMaximizedOnScreen(
app, screen)) {
info.maximized = true;
break;
}
}
}
// windowClass and text not implemented on macOS
result.emplace_back(std::move(info));
}
}
apps = nil;
CFRelease(cfApps);
}
return false;
}
std::optional<std::string> GetTextInWindow(const std::string &)
{
// Not implemented
return {};
return result;
}
int SecondsSinceLastInput()
@ -341,8 +313,7 @@ QStringList GetProcessPathsFromName(const QString &name)
continue;
}
const char *nameStr = appName.UTF8String;
if (!nameStr ||
name != QString::fromUtf8(nameStr)) {
if (!nameStr || name != QString::fromUtf8(nameStr)) {
continue;
}
NSURL *url = app.executableURL;

View File

@ -11,20 +11,31 @@ namespace advss {
enum class HotkeyType;
struct WindowGeometry {
struct WindowQueryOptions {
bool geometry = false;
bool focus = false;
bool fullscreen = false;
bool maximized = false;
bool windowClass = false; // Windows only, no-op on other platforms
bool text = false; // Windows only, expensive, no-op on other platforms
};
struct WindowInfo {
std::string title;
bool focused = false;
bool fullscreen = false;
bool maximized = false;
int x = 0;
int y = 0;
int width = 0;
int height = 0;
std::string windowClass;
std::optional<std::string> text;
};
EXPORT std::vector<std::string> GetWindowList();
EXPORT std::vector<WindowInfo>
GetWindows(const WindowQueryOptions &options = {});
EXPORT std::string GetCurrentWindowTitle();
EXPORT bool IsFullscreen(const std::string &title);
EXPORT bool IsMaximized(const std::string &title);
EXPORT std::optional<std::string> GetTextInWindow(const std::string &window);
EXPORT std::optional<WindowGeometry>
GetWindowGeometry(const std::string &title);
EXPORT int SecondsSinceLastInput();
EXPORT QStringList GetProcessList();
EXPORT std::string GetForegroundProcessName();

View File

@ -173,10 +173,10 @@ void PopulateTransitionSelection(QComboBox *sel, bool addCurrent, bool addAny,
void PopulateWindowSelection(QComboBox *sel, bool addSelect)
{
const auto windows = GetWindowList();
const auto windows = GetWindows();
for (const std::string &window : windows) {
sel->addItem(window.c_str());
for (const auto &info : windows) {
sel->addItem(info.title.c_str());
}
sel->model()->sort(0);

View File

@ -138,7 +138,7 @@ const std::vector<std::string> getOBSWindows()
return lastDoneHelper->windows;
}
std::vector<std::string> GetWindowList()
static std::vector<std::string> getAllWindowTitles()
{
std::vector<std::string> windows;
EnumWindowsWithMetro(GetTitleCB, reinterpret_cast<LPARAM>(&windows));
@ -155,6 +155,129 @@ std::vector<std::string> GetWindowList()
return windows;
}
static bool isWindowMaximized(HWND hwnd)
{
if (!hwnd || hwnd == GetDesktopWindow() || hwnd == GetShellWindow()) {
return false;
}
if (IsZoomed(hwnd)) {
return true;
}
RECT appBounds;
MONITORINFO monitorInfo = {0};
monitorInfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST),
&monitorInfo);
GetWindowRect(hwnd, &appBounds);
return monitorInfo.rcMonitor.bottom == appBounds.bottom &&
monitorInfo.rcMonitor.top == appBounds.top &&
monitorInfo.rcMonitor.left == appBounds.left &&
monitorInfo.rcMonitor.right == appBounds.right;
}
static bool isWindowFullscreen(HWND hwnd)
{
if (!hwnd || hwnd == GetDesktopWindow() || hwnd == GetShellWindow()) {
return false;
}
RECT appBounds;
MONITORINFO monitorInfo = {0};
monitorInfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST),
&monitorInfo);
GetWindowRect(hwnd, &appBounds);
return monitorInfo.rcMonitor.bottom == appBounds.bottom &&
monitorInfo.rcMonitor.top == appBounds.top &&
monitorInfo.rcMonitor.left == appBounds.left &&
monitorInfo.rcMonitor.right == appBounds.right;
}
static std::string getWindowClass(HWND hwnd)
{
std::wstring wClass;
wClass.resize(256);
int len = GetClassNameW(hwnd, &wClass[0], (int)wClass.size());
if (len <= 0) {
return "";
}
wClass.resize(len);
size_t utf8len = os_wcs_to_utf8(wClass.c_str(), 0, nullptr, 0);
std::string result;
result.resize(utf8len);
os_wcs_to_utf8(wClass.c_str(), 0, &result[0], utf8len + 1);
return result;
}
static HWND getHWNDfromTitle(const std::string &title);
static std::optional<std::string> GetTextInWindow(const std::string &window);
std::vector<WindowInfo> GetWindows(const WindowQueryOptions &options)
{
const std::string foregroundTitle = GetCurrentWindowTitle();
const auto titles = getAllWindowTitles();
std::vector<WindowInfo> result;
result.reserve(titles.size());
for (const auto &title : titles) {
WindowInfo info;
info.title = title;
const bool needHwnd = options.geometry || options.fullscreen ||
options.maximized ||
options.windowClass || options.text ||
options.focus;
if (options.focus) {
info.focused = (title == foregroundTitle);
}
if (!needHwnd || (!options.geometry && !options.fullscreen &&
!options.maximized && !options.windowClass &&
!options.text)) {
result.emplace_back(std::move(info));
continue;
}
HWND hwnd = getHWNDfromTitle(title);
if (!hwnd) {
result.emplace_back(std::move(info));
continue;
}
if (options.geometry || options.fullscreen ||
options.maximized) {
RECT rect;
if (GetWindowRect(hwnd, &rect)) {
info.x = rect.left;
info.y = rect.top;
info.width = rect.right - rect.left;
info.height = rect.bottom - rect.top;
}
}
if (options.maximized) {
info.maximized = isWindowMaximized(hwnd);
}
if (options.fullscreen) {
info.fullscreen = isWindowFullscreen(hwnd);
}
if (options.windowClass) {
info.windowClass = getWindowClass(hwnd);
}
if (options.text) {
info.text = GetTextInWindow(title);
}
result.emplace_back(std::move(info));
}
return result;
}
std::string GetCurrentWindowTitle()
{
HWND window = GetForegroundWindow();
@ -193,36 +316,6 @@ static HWND getHWNDfromTitle(const std::string &title)
return hwnd;
}
bool IsMaximized(const std::string &title)
{
RECT appBounds;
MONITORINFO monitorInfo = {0};
HWND hwnd = NULL;
hwnd = getHWNDfromTitle(title);
if (!hwnd) {
return false;
}
monitorInfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST),
&monitorInfo);
if (hwnd && hwnd != GetDesktopWindow() && hwnd != GetShellWindow()) {
if (IsZoomed(hwnd)) {
return true;
}
GetWindowRect(hwnd, &appBounds);
if (monitorInfo.rcMonitor.bottom == appBounds.bottom &&
monitorInfo.rcMonitor.top == appBounds.top &&
monitorInfo.rcMonitor.left == appBounds.left &&
monitorInfo.rcMonitor.right == appBounds.right) {
return true;
}
}
return false;
}
static std::wstring GetControlText(HWND hwnd, IUIAutomationElement *element)
{
VARIANT var;
@ -249,7 +342,7 @@ static std::wstring GetControlText(HWND hwnd, IUIAutomationElement *element)
return text;
}
std::optional<std::string> GetTextInWindow(const std::string &window)
static std::optional<std::string> GetTextInWindow(const std::string &window)
{
HWND hwnd = getHWNDfromTitle(window);
if (!hwnd) {
@ -316,55 +409,6 @@ std::optional<std::string> GetTextInWindow(const std::string &window)
return tmp;
}
bool IsFullscreen(const std::string &title)
{
RECT appBounds;
MONITORINFO monitorInfo = {0};
HWND hwnd = NULL;
hwnd = getHWNDfromTitle(title);
if (!hwnd) {
return false;
}
monitorInfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST),
&monitorInfo);
if (hwnd && hwnd != GetDesktopWindow() && hwnd != GetShellWindow()) {
GetWindowRect(hwnd, &appBounds);
if (monitorInfo.rcMonitor.bottom == appBounds.bottom &&
monitorInfo.rcMonitor.top == appBounds.top &&
monitorInfo.rcMonitor.left == appBounds.left &&
monitorInfo.rcMonitor.right == appBounds.right) {
return true;
}
}
return false;
}
std::optional<WindowGeometry> GetWindowGeometry(const std::string &title)
{
HWND hwnd = getHWNDfromTitle(title);
if (!hwnd) {
return {};
}
RECT rect;
if (!GetWindowRect(hwnd, &rect)) {
return {};
}
WindowGeometry geo;
geo.x = rect.left;
geo.y = rect.top;
geo.width = rect.right - rect.left;
geo.height = rect.bottom - rect.top;
return geo;
}
QStringList GetProcessList()
{
QStringList processes;

View File

@ -42,19 +42,20 @@ void CloseWindow(const std::string &) {}
std::optional<std::string> MacroActionWindow::GetMatchingWindow() const
{
const auto windowList = GetWindowList();
const auto windows = GetWindows();
if (!_regex.Enabled()) {
if (std::find(windowList.begin(), windowList.end(),
std::string(_window)) == windowList.end()) {
return {};
for (const auto &info : windows) {
if (info.title == std::string(_window)) {
return info.title;
}
}
return _window;
return {};
}
for (const auto &window : windowList) {
if (_regex.Matches(window, _window)) {
return window;
for (const auto &info : windows) {
if (_regex.Matches(info.title, _window)) {
return info.title;
}
}

View File

@ -14,21 +14,6 @@ bool MacroConditionWindow::_registered = MacroConditionFactory::Register(
{MacroConditionWindow::Create, MacroConditionWindowEdit::Create,
"AdvSceneSwitcher.condition.window"});
static bool windowContainsText(const std::string &window,
const std::string &matchText,
const RegexConfig &regex)
{
auto text = GetTextInWindow(window);
if (!text.has_value()) {
return false;
}
if (regex.Enabled()) {
return regex.Matches(*text, matchText);
}
return text == matchText;
}
void MacroConditionWindow::SetCheckText(bool value)
{
#ifdef _WIN32
@ -46,92 +31,76 @@ bool MacroConditionWindow::GetCheckText()
}
bool MacroConditionWindow::WindowMatchesRequirements(
const std::string &window) const
const WindowInfo &info) const
{
const bool focusCheckOK =
(!_focus || window == ForegroundWindowTitle());
if (!focusCheckOK) {
if (_focus && !info.focused) {
return false;
}
const bool fullscreenCheckOK = (!_fullscreen || IsFullscreen(window));
if (!fullscreenCheckOK) {
if (_fullscreen && !info.fullscreen) {
return false;
}
const bool maxCheckOK = (!_maximized || IsMaximized(window));
if (!maxCheckOK) {
if (_maximized && !info.maximized) {
return false;
}
const bool textCheckOK =
(!_checkText || windowContainsText(window, _text, _textRegex));
if (!textCheckOK) {
return false;
if (_checkText) {
if (!info.text.has_value()) {
return false;
}
if (_textRegex.Enabled()) {
if (!_textRegex.Matches(*info.text, _text)) {
return false;
}
} else {
if (*info.text != std::string(_text)) {
return false;
}
}
}
return true;
}
bool MacroConditionWindow::WindowMatches(
const std::vector<std::string> &windowList)
bool MacroConditionWindow::FindMatch(const std::vector<WindowInfo> &windows)
{
bool match = !_checkTitle ||
std::find(windowList.begin(), windowList.end(),
std::string(_window)) != windowList.end();
match = match && WindowMatchesRequirements(_window);
SetVariableValueBasedOnMatch(_window);
return match;
}
#ifdef _WIN32
std::string GetWindowClassByWindowTitle(const std::string &window);
#endif
bool MacroConditionWindow::WindowRegexMatches(
const std::vector<std::string> &windowList)
{
// No need to test if checking for window title is required as if the
// user has disabled window title matching the option will always be
// enabled in the backend and use the regular expression ".*".
for (const auto &window : windowList) {
if (_windowRegex.Matches(window, _window) &&
WindowMatchesRequirements(window)) {
SetVariableValueBasedOnMatch(window);
return true;
// When regex is enabled the title check is always active (the backend
// uses ".*" when the user disables the title check), so we can use a
// single predicate for both modes.
for (const auto &info : windows) {
const bool titleOK =
_windowRegex.Enabled()
? _windowRegex.Matches(info.title, _window)
: (!_checkTitle ||
info.title == std::string(_window));
if (!titleOK || !WindowMatchesRequirements(info)) {
continue;
}
SetVariableValueBasedOnMatch(&info);
return true;
}
SetVariableValueBasedOnMatch("");
SetVariableValueBasedOnMatch(nullptr);
return false;
}
void MacroConditionWindow::SetVariableValueBasedOnMatch(
const std::string &matchWindow)
void MacroConditionWindow::SetVariableValueBasedOnMatch(const WindowInfo *info)
{
SetTempVarValue("window", matchWindow);
const auto geo = GetWindowGeometry(matchWindow);
if (geo) {
SetTempVarValue("windowX", std::to_string(geo->x));
SetTempVarValue("windowY", std::to_string(geo->y));
SetTempVarValue("windowWidth", std::to_string(geo->width));
SetTempVarValue("windowHeight", std::to_string(geo->height));
}
const std::string title = info ? info->title : "";
SetTempVarValue("window", title);
SetTempVarValue("windowX", info ? std::to_string(info->x) : "");
SetTempVarValue("windowY", info ? std::to_string(info->y) : "");
SetTempVarValue("windowWidth", info ? std::to_string(info->width) : "");
SetTempVarValue("windowHeight",
info ? std::to_string(info->height) : "");
#ifdef _WIN32
SetTempVarValue("windowClass",
GetWindowClassByWindowTitle(matchWindow));
SetTempVarValue("windowClass", info ? info->windowClass : "");
if (_checkText) {
const auto text = GetTextInWindow(matchWindow);
if (text) {
SetTempVarValue("windowText", *text);
}
SetTempVarValue("windowText",
(info && info->text) ? *info->text : "");
}
#endif
if (!IsReferencedInVars()) {
return;
}
if (_checkText) {
const auto text = GetTextInWindow(matchWindow);
SetVariableValue(text.value_or(""));
SetVariableValue((info && info->text) ? *info->text : "");
} else {
SetVariableValue(ForegroundWindowTitle());
}
@ -144,13 +113,18 @@ static bool foregroundWindowChanged()
bool MacroConditionWindow::CheckCondition()
{
const auto windowList = GetWindowList();
bool match = false;
if (_windowRegex.Enabled()) {
match = WindowRegexMatches(windowList);
} else {
match = WindowMatches(windowList);
}
WindowQueryOptions options;
options.focus = _focus;
options.fullscreen = _fullscreen;
options.maximized = _maximized;
options.geometry = true;
#ifdef _WIN32
options.windowClass = true;
options.text = _checkText;
#endif
const auto windows = GetWindows(options);
bool match = FindMatch(windows);
match = match && (!_windowFocusChanged || foregroundWindowChanged());
return match;
}

View File

@ -1,5 +1,6 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "platform-funcs.hpp"
#include "variable-text-edit.hpp"
#include "regex-config.hpp"
#include "window-selection.hpp"
@ -37,10 +38,9 @@ public:
RegexConfig _textRegex = RegexConfig::PartialMatchRegexConfig();
private:
bool WindowMatchesRequirements(const std::string &window) const;
bool WindowMatches(const std::vector<std::string> &windowList);
bool WindowRegexMatches(const std::vector<std::string> &windowList);
void SetVariableValueBasedOnMatch(const std::string &matchWindow);
bool WindowMatchesRequirements(const WindowInfo &info) const;
bool FindMatch(const std::vector<WindowInfo> &windows);
void SetVariableValueBasedOnMatch(const WindowInfo *info);
void SetupTempVars();
// For now only supported on Windows

View File

@ -205,26 +205,6 @@ static std::vector<HWND> getHWNDfromTitle(const std::string &title)
return hwnds;
}
std::string GetWindowClassByWindowTitle(const std::string &window)
{
auto hwnds = getHWNDfromTitle(window);
if (hwnds.empty()) {
return "";
}
auto hwnd = hwnds.at(0);
std::wstring wClass;
wClass.resize(1024);
if (!GetClassNameW(hwnd, &wClass[0], wClass.capacity())) {
return "";
}
size_t len = os_wcs_to_utf8(wClass.c_str(), 0, nullptr, 0);
std::string className;
className.resize(len);
os_wcs_to_utf8(wClass.c_str(), 0, &className[0], len + 1);
return className;
}
void SetFocusWindow(const std::string &title)
{
auto hwnds = getHWNDfromTitle(title);