SceneSwitcher/lib/win/advanced-scene-switcher-win.cpp
WarmUpTill 7d0332dd0e Restructure library and plugins
The "core" macro conditions and actions have been extracted out to the
"base" plugin.

The library now mostly contains functionality which is required across
all plugins and (e.g. definitions for macro segments).

The goal is to reduce the complexity and cross-dependencies and group
the source files in a better way.

This should relsove the "library limit of 65535 objects exceeded" build
issue occuring in some Windows build environments.
2024-01-27 14:10:34 +01:00

456 lines
10 KiB
C++

#include "platform-funcs.hpp"
#include <windows.h>
#include <UIAutomation.h>
#include <util/platform.h>
#include <TlHelp32.h>
#include <Psapi.h>
#include <locale>
#include <codecvt>
#include <string>
#include <vector>
#include <QStringList>
#include <QRegularExpression>
#include <obs-frontend-api.h>
#include <QApplication>
#include <QWidget>
#include <mutex>
namespace advss {
#define MAX_SEARCH 1000
static bool GetWindowTitle(HWND window, std::string &title)
{
size_t len = (size_t)GetWindowTextLengthW(window);
std::wstring wtitle;
wtitle.resize(len);
if (!GetWindowTextW(window, &wtitle[0], (int)len + 1)) {
return false;
}
len = os_wcs_to_utf8(wtitle.c_str(), 0, nullptr, 0);
title.resize(len);
os_wcs_to_utf8(wtitle.c_str(), 0, &title[0], len + 1);
return true;
}
static bool WindowValid(HWND window)
{
LONG_PTR styles;
DWORD id;
if (!IsWindowVisible(window)) {
return false;
}
GetWindowThreadProcessId(window, &id);
if (id == GetCurrentProcessId()) {
return false;
}
styles = GetWindowLongPtr(window, GWL_STYLE);
if (styles & WS_CHILD) {
return false;
}
return true;
}
static BOOL CALLBACK GetTitleCB(HWND hwnd, LPARAM lParam)
{
if (!WindowValid(hwnd)) {
return TRUE;
}
std::string title;
GetWindowTitle(hwnd, title);
if (title.empty()) {
return TRUE;
}
std::vector<std::string> &titles =
*reinterpret_cast<std::vector<std::string> *>(lParam);
titles.push_back(title);
return TRUE;
}
static VOID EnumWindowsWithMetro(__in WNDENUMPROC lpEnumFunc,
__in LPARAM lParam)
{
HWND childWindow = NULL;
int i = 0;
while (i < MAX_SEARCH &&
(childWindow = FindWindowEx(NULL, childWindow, NULL, NULL))) {
if (!lpEnumFunc(childWindow, lParam)) {
return;
}
i++;
}
}
// Asynchronously updates the OBS window list and returns the state of the last
// successful update
const std::vector<std::string> getOBSWindows()
{
struct OBSWindowListHelper {
std::vector<std::string> windows;
std::atomic_bool done;
};
static OBSWindowListHelper obsWindowListHelper1 = {{}, {true}};
static OBSWindowListHelper obsWindowListHelper2 = {{}, {false}};
auto getQtWindowList = [](void *param) {
auto list = reinterpret_cast<OBSWindowListHelper *>(param);
for (auto w : QApplication::topLevelWidgets()) {
auto title = w->windowTitle();
if (!title.isEmpty()) {
list->windows.emplace_back(title.toStdString());
}
}
list->done = true;
};
static OBSWindowListHelper *lastDoneHelper = &obsWindowListHelper2;
static OBSWindowListHelper *pendingHelper = &obsWindowListHelper1;
static std::mutex mutex;
/* ------------------------------------------------------------------ */
std::lock_guard<std::mutex> lock(mutex);
if (pendingHelper->done) { // Check if swap is needed
auto temp = lastDoneHelper;
lastDoneHelper = pendingHelper;
pendingHelper = temp;
pendingHelper->done = false;
pendingHelper->windows.clear();
obs_queue_task(OBS_TASK_UI, getQtWindowList, pendingHelper,
false);
}
return lastDoneHelper->windows;
}
void GetWindowList(std::vector<std::string> &windows)
{
windows.resize(0);
EnumWindowsWithMetro(GetTitleCB, reinterpret_cast<LPARAM>(&windows));
// Also add OBS windows
for (const auto &window : getOBSWindows()) {
if (!window.empty()) {
windows.emplace_back(window);
}
}
// Add entry for OBS Studio itself - see GetCurrentWindowTitle()
windows.emplace_back("OBS");
}
void GetWindowList(QStringList &windows)
{
windows.clear();
std::vector<std::string> w;
GetWindowList(w);
for (auto window : w) {
windows << QString::fromStdString(window);
}
}
void GetCurrentWindowTitle(std::string &title)
{
HWND window = GetForegroundWindow();
DWORD pid;
DWORD thid;
thid = GetWindowThreadProcessId(window, &pid);
// Calling GetWindowTitle() on the OBS windows might cause a deadlock in
// the following scenario:
//
// The thread using GetWindowTitle() will send a WM_GETTEXT message to
// the main thread that created the window and wait for it to be
// processed.
// The main thread itself might be blocked from processing the message,
// however, when it itself is waiting for the thread using
// GetWindowTitle() to return.
//
// So instead rely on Qt to get the title of the active window.
if (GetCurrentProcessId() == pid) {
auto window = QApplication::activeWindow();
if (window) {
title = window->windowTitle().toStdString();
} else {
title = "OBS";
}
return;
}
GetWindowTitle(window, title);
}
static HWND getHWNDfromTitle(const std::string &title)
{
HWND hwnd = NULL;
wchar_t wTitle[512];
os_utf8_to_wcs(title.c_str(), 0, wTitle, 512);
hwnd = FindWindowEx(NULL, NULL, NULL, wTitle);
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;
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<std::string> 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<void **>(&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;
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;
}
void GetProcessList(QStringList &processes)
{
HANDLE procSnapshot;
PROCESSENTRY32 procEntry;
procSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (procSnapshot == INVALID_HANDLE_VALUE) {
return;
}
procEntry.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(procSnapshot, &procEntry)) {
CloseHandle(procSnapshot);
return;
}
do {
QString tempexe = QString::fromWCharArray(procEntry.szExeFile);
if (tempexe == "System") {
continue;
}
if (tempexe == "[System Process]") {
continue;
}
if (processes.contains(tempexe)) {
continue;
}
processes.append(tempexe);
} while (Process32Next(procSnapshot, &procEntry));
CloseHandle(procSnapshot);
}
static void GetForegroundProcessName(QString &proc)
{
// only checks if the current foreground window is from the same executable,
// may return true for any window from a program
HWND foregroundWindow = GetForegroundWindow();
DWORD processId = 0;
GetWindowThreadProcessId(foregroundWindow, &processId);
HANDLE process = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
if (process == NULL) {
return;
}
WCHAR executablePath[600];
GetModuleFileNameEx(process, 0, executablePath, 600);
CloseHandle(process);
proc = QString::fromWCharArray(executablePath)
.split(QRegularExpression("(/|\\\\)"))
.back();
}
void GetForegroundProcessName(std::string &proc)
{
QString temp;
GetForegroundProcessName(temp);
proc = temp.toStdString();
}
bool IsInFocus(const QString &executable)
{
// only checks if the current foreground window is from the same executable,
// may return true for any window from a program
QString foregroundProc;
GetForegroundProcessName(foregroundProc);
// True if executable switch equals current window
bool equals = (executable == foregroundProc);
// True if executable switch matches current window
bool matches = foregroundProc.contains(QRegularExpression(executable));
return (equals || matches);
}
static int getLastInputTime()
{
LASTINPUTINFO lastInputInfo;
lastInputInfo.cbSize = sizeof(LASTINPUTINFO);
if (GetLastInputInfo(&lastInputInfo)) {
return lastInputInfo.dwTime;
}
return 0;
}
static int getTime()
{
return GetTickCount();
}
int SecondsSinceLastInput()
{
return (getTime() - getLastInputTime()) / 1000;
}
void PlatformInit()
{
CoInitialize(NULL);
}
void PlatformCleanup()
{
CoUninitialize();
}
} // namespace advss