#include "platform-funcs.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 &titles = *reinterpret_cast *>(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 getOBSWindows() { struct OBSWindowListHelper { std::vector windows; std::atomic_bool done; }; static OBSWindowListHelper obsWindowListHelper1 = {{}, {true}}; static OBSWindowListHelper obsWindowListHelper2 = {{}, {false}}; auto getQtWindowList = [](void *param) { auto list = reinterpret_cast(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 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 &windows) { windows.resize(0); EnumWindowsWithMetro(GetTitleCB, reinterpret_cast(&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 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 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; 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